mirror of
https://github.com/Yubico/yubiadmin.git
synced 2024-11-29 10:24:11 +01:00
Started adding client table for YKVAL.
This commit is contained in:
parent
68f8c9c987
commit
5e79f93ea2
@ -26,13 +26,12 @@
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import re
|
||||
from wtforms import Form
|
||||
from wtforms.fields import (SelectField, TextField, BooleanField, IntegerField,
|
||||
PasswordField)
|
||||
from wtforms.widgets import PasswordInput
|
||||
from wtforms.validators import NumberRange, URL, EqualTo, Regexp, Optional
|
||||
from yubiadmin.util.app import App, render
|
||||
from yubiadmin.util.app import App, CollectionApp, render
|
||||
from yubiadmin.util.config import (python_handler, python_list_handler,
|
||||
FileConfig)
|
||||
from yubiadmin.util.form import ConfigForm, FileForm, ListField
|
||||
@ -291,31 +290,29 @@ class AssignYubiKeyForm(Form):
|
||||
self.auth.commit()
|
||||
|
||||
|
||||
class YubiAuthUsers(App):
|
||||
user_range = re.compile('(\d+)-(\d+)')
|
||||
class YubiAuthUsers(CollectionApp):
|
||||
base_url = '/auth/users'
|
||||
item_name = 'Users'
|
||||
caption = 'YubiAuth Users'
|
||||
columns = ['Username', 'YubiKeys']
|
||||
template = 'auth/list'
|
||||
|
||||
def __init__(self):
|
||||
self.auth = YubiAuth()
|
||||
|
||||
def __call__(self, request):
|
||||
sub_cmd = request.path_info_pop()
|
||||
if sub_cmd == 'create':
|
||||
return self.create(request)
|
||||
elif sub_cmd == 'delete':
|
||||
return self.delete(request)
|
||||
elif sub_cmd == 'delete_confirm':
|
||||
return self.delete_confirm(request)
|
||||
elif sub_cmd == 'user':
|
||||
return self.show_user(request)
|
||||
else:
|
||||
match = self.user_range.match(sub_cmd) if sub_cmd else None
|
||||
if match:
|
||||
offset = int(match.group(1)) - 1
|
||||
limit = int(match.group(2)) - offset
|
||||
else:
|
||||
offset = 0
|
||||
limit = 10
|
||||
return self.list_users(offset, limit)
|
||||
def size(self):
|
||||
return self.auth.session.query(User).count()
|
||||
|
||||
def get(self, offset, limit):
|
||||
users = self.auth.session.query(User).order_by(User.name) \
|
||||
.offset(offset).limit(limit)
|
||||
|
||||
return map(lambda user: {
|
||||
'id': user.id,
|
||||
'Username': '<a href="/auth/users/show/%d">%s</a>' % (user.id,
|
||||
user.name),
|
||||
'YubiKeys': ', '.join(user.yubikeys.keys())
|
||||
}, users)
|
||||
|
||||
def create(self, request):
|
||||
return self.render_forms(request, [CreateUserForm(self.auth)],
|
||||
@ -333,28 +330,7 @@ class YubiAuthUsers(App):
|
||||
self.auth.commit()
|
||||
return self.redirect('/auth/users')
|
||||
|
||||
def list_users(self, offset, limit):
|
||||
users = self.auth.session.query(User).order_by(User.name) \
|
||||
.offset(offset).limit(limit)
|
||||
num_users = self.auth.session.query(User).count()
|
||||
shown = (min(offset + 1, num_users), min(offset + limit, num_users))
|
||||
if offset > 0:
|
||||
st = max(0, offset - limit)
|
||||
ed = st + limit
|
||||
prev = '/auth/users/%d-%d' % (st + 1, ed)
|
||||
else:
|
||||
prev = None
|
||||
if num_users > shown[1]:
|
||||
next = '/auth/users/%d-%d' % (offset + limit + 1, shown[1] + limit)
|
||||
else:
|
||||
next = None
|
||||
|
||||
return render(
|
||||
'auth/list', script='auth', users=users, offset=offset,
|
||||
limit=limit, num_users=num_users, shown='%d-%d' % shown, prev=prev,
|
||||
next=next)
|
||||
|
||||
def show_user(self, request):
|
||||
def show(self, request):
|
||||
id = int(request.path_info_pop())
|
||||
user = self.auth.get_user(id)
|
||||
if 'unassign' in request.params:
|
||||
|
@ -29,11 +29,11 @@ import re
|
||||
import os
|
||||
from wtforms.fields import IntegerField
|
||||
from wtforms.validators import NumberRange, IPAddress, URL
|
||||
from yubiadmin.util.app import App
|
||||
from yubiadmin.util.app import App, CollectionApp
|
||||
from yubiadmin.util.config import (RegexHandler, FileConfig, php_inserter,
|
||||
parse_block, strip_comments, strip_quotes)
|
||||
from yubiadmin.util.form import ConfigForm, FileForm, DBConfigForm, ListField
|
||||
from yubiadmin.util.system import invoke_rc_d
|
||||
from yubiadmin.util.system import invoke_rc_d, run
|
||||
|
||||
__all__ = [
|
||||
'app'
|
||||
@ -210,15 +210,25 @@ class YubikeyVal(App):
|
||||
"""
|
||||
|
||||
name = 'val'
|
||||
sections = ['general', 'database', 'synchronization', 'ksms', 'advanced']
|
||||
sections = ['general', 'clients', 'database', 'synchronization', 'ksms',
|
||||
'advanced']
|
||||
disabled = not os.path.isfile(YKVAL_CONFIG_FILE)
|
||||
|
||||
def __init__(self):
|
||||
self._clients = YubikeyValClients()
|
||||
|
||||
def general(self, request):
|
||||
"""
|
||||
General
|
||||
"""
|
||||
return self.render_forms(request, [SyncLevelsForm(), MiscForm()])
|
||||
|
||||
def clients(self, request):
|
||||
"""
|
||||
API Clients
|
||||
"""
|
||||
return self._clients(request)
|
||||
|
||||
def database(self, request):
|
||||
"""
|
||||
Database Settings
|
||||
@ -260,7 +270,40 @@ class YubikeyVal(App):
|
||||
FileForm(YKVAL_CONFIG_FILE, 'Configuration')
|
||||
])
|
||||
|
||||
#Pulls the tab to the right:
|
||||
# Pulls the tab to the right:
|
||||
advanced.advanced = True
|
||||
|
||||
|
||||
class YubikeyValClients(CollectionApp):
|
||||
base_url = '/val/clients'
|
||||
item_name = 'Clients'
|
||||
caption = 'Client API Keys'
|
||||
columns = ['Client ID', 'API Key']
|
||||
template = 'val/client_list'
|
||||
|
||||
def __call__(self, request):
|
||||
self._data = None
|
||||
return super(YubikeyValClients, self).__call__(request)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
if self._data is None:
|
||||
self._data = []
|
||||
status, output = run('ykval-export-clients')
|
||||
for line in output.splitlines():
|
||||
parts = line.split(',')
|
||||
self._data.append({
|
||||
'id': parts[0],
|
||||
'Client ID': parts[0],
|
||||
'API Key': parts[3]
|
||||
})
|
||||
|
||||
return self._data
|
||||
|
||||
def size(self):
|
||||
return len(self.data)
|
||||
|
||||
def get(self, offset, limit):
|
||||
return self.data[offset:offset + limit]
|
||||
|
||||
app = YubikeyVal()
|
||||
|
17
yubiadmin/static/js/table.js
Normal file
17
yubiadmin/static/js/table.js
Normal file
@ -0,0 +1,17 @@
|
||||
$(document).ready(function() {
|
||||
$('#delete_btn').attr('disabled', 'disabled');
|
||||
|
||||
$('#toggle_all').change(function() {
|
||||
$('tbody :checkbox').prop('checked', $(this).is(':checked'));
|
||||
});
|
||||
|
||||
$(':checkbox').change(function() {
|
||||
if($('tbody :checkbox:checked').length > 0) {
|
||||
console.log('enable');
|
||||
$('#delete_btn').removeAttr('disabled');
|
||||
} else {
|
||||
console.log('disable');
|
||||
$('#delete_btn').attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
});
|
@ -1,40 +1,8 @@
|
||||
{% from 'table.html' import table %}
|
||||
|
||||
<form action="/auth/users/delete" method="post">
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
<caption>YubiAuth users</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%"><input type="checkbox" id="toggle_all" /></th>
|
||||
<th style="width: 15%">Username</th>
|
||||
<th style="width: 15%">YubiKeys</th>
|
||||
<th style="width: 65%; text-align: right;">
|
||||
Users {{ shown }} of {{ num_users }}
|
||||
|
||||
<div class="btn-group">
|
||||
{% if prev %}
|
||||
<a class="btn btn-small" href="{{ prev }}">Prev</a>
|
||||
{% else %}
|
||||
<a class="btn btn-small disabled">Prev</a>
|
||||
{% endif %}
|
||||
{% if next %}
|
||||
<a class="btn btn-small" href="{{ next }}">Next</a>
|
||||
{% else %}
|
||||
<a class="btn btn-small disabled">Next</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="user/{{ user.id }}"/></td>
|
||||
<td><a href="/auth/users/user/{{ user.id }}">{{ user.name }}</a></td>
|
||||
<td colspan="2">{{ user.yubikeys | join(', ') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}
|
||||
|
||||
<input id="delete_btn" type="submit" class="btn btn-danger" value="Delete selected" />
|
||||
<a href="/auth/users/create" class="btn btn-primary pull-right">Create new user</a>
|
||||
|
57
yubiadmin/templates/table.html
Normal file
57
yubiadmin/templates/table.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% macro header(cols, next=None, prev=None, shown=0, total=0, item_name='Items') %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" id="toggle_all" />
|
||||
</th>
|
||||
{% for col in cols %}
|
||||
<th style="width: 15%">
|
||||
{{ col }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% set col_len = cols|length %}
|
||||
<th style="width: {{ 95 - col_len * 15 }}%; text-align:right;">
|
||||
{{ item_name }} {{ shown }} of {{ total }}
|
||||
|
||||
<div class="btn-group">
|
||||
{% if prev %}
|
||||
<a class="btn btn-small" href="{{ prev }}">Prev</a>
|
||||
{% else %}
|
||||
<a class="btn btn-small disabled">Prev</a>
|
||||
{% endif %}
|
||||
{% if next %}
|
||||
<a class="btn btn-small" href="{{ next }}">Next</a>
|
||||
{% else %}
|
||||
<a class="btn btn-small disabled">Next</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro body(cols, items) %}
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="item/{{ item.id }}"/></td>
|
||||
{% for col in cols %}
|
||||
<td{% if loop.last %} colspan="2"{% endif %}>{{ item[col] }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro table(cols, items, caption=None, next=None, prev=None, shown=0, total=0, item_name='Items') %}
|
||||
<table class="table table-striped table-condensed">
|
||||
{% if caption %}
|
||||
<caption>{{ caption }}</caption>
|
||||
{% endif %}
|
||||
{{ header(cols, next, prev, shown, total, item_name) }}
|
||||
{{ body(cols, items) }}
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}
|
9
yubiadmin/templates/val/client_list.html
Normal file
9
yubiadmin/templates/val/client_list.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% from 'table.html' import table %}
|
||||
|
||||
<form action="/val/clients/delete" method="post">
|
||||
|
||||
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}
|
||||
|
||||
<input id="delete_btn" type="submit" class="btn btn-danger" value="Delete selected" />
|
||||
<a href="/val/clients/create" class="btn btn-primary pull-right">Create new API client</a>
|
||||
</form>
|
@ -26,12 +26,14 @@
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import re
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from webob import exc
|
||||
from webob.dec import wsgify
|
||||
|
||||
__all__ = [
|
||||
'App',
|
||||
'CollectionApp',
|
||||
'render',
|
||||
'populate_forms',
|
||||
]
|
||||
@ -113,3 +115,57 @@ class App(object):
|
||||
|
||||
return render(template, target=request.path, fieldsets=forms,
|
||||
alert=alert, **kwargs)
|
||||
|
||||
|
||||
ITEM_RANGE = re.compile('(\d+)-(\d+)')
|
||||
|
||||
|
||||
class CollectionApp(App):
|
||||
base_url = ''
|
||||
caption = 'Items'
|
||||
item_name = 'Items'
|
||||
columns = []
|
||||
template = 'table'
|
||||
script = 'table'
|
||||
|
||||
def size(self):
|
||||
return 0
|
||||
|
||||
def get(self, offset, limit):
|
||||
return [{}]
|
||||
|
||||
def __call__(self, request):
|
||||
sub_cmd = request.path_info_pop()
|
||||
if sub_cmd and hasattr(self, sub_cmd):
|
||||
return getattr(self, sub_cmd)(request)
|
||||
else:
|
||||
match = ITEM_RANGE.match(sub_cmd) if sub_cmd else None
|
||||
if match:
|
||||
offset = int(match.group(1)) - 1
|
||||
limit = int(match.group(2)) - offset
|
||||
else:
|
||||
offset = 0
|
||||
limit = 10
|
||||
return self.list(offset, limit)
|
||||
|
||||
def list(self, offset, limit):
|
||||
items = self.get(offset, limit)
|
||||
total = self.size()
|
||||
shown = (min(offset + 1, total), min(offset + limit, total))
|
||||
if offset > 0:
|
||||
st = max(0, offset - limit)
|
||||
ed = st + limit
|
||||
prev = '%s/%d-%d' % (self.base_url, st + 1, ed)
|
||||
else:
|
||||
prev = None
|
||||
if total > shown[1]:
|
||||
next = '%s/%d-%d' % (self.base_url, offset + limit + 1, shown[1]
|
||||
+ limit)
|
||||
else:
|
||||
next = None
|
||||
|
||||
return render(
|
||||
self.template, script=self.script, items=items, offset=offset,
|
||||
limit=limit, total=total, shown='%d-%d' % shown, prev=prev,
|
||||
next=next, base_url=self.base_url, caption=self.caption,
|
||||
cols=self.columns, item_name=self.item_name)
|
||||
|
Loading…
Reference in New Issue
Block a user