1
0
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:
Dain Nilsson 2013-05-17 17:14:03 +02:00
parent 68f8c9c987
commit 5e79f93ea2
7 changed files with 211 additions and 85 deletions

View File

@ -26,13 +26,12 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import os import os
import re
from wtforms import Form from wtforms import Form
from wtforms.fields import (SelectField, TextField, BooleanField, IntegerField, from wtforms.fields import (SelectField, TextField, BooleanField, IntegerField,
PasswordField) PasswordField)
from wtforms.widgets import PasswordInput from wtforms.widgets import PasswordInput
from wtforms.validators import NumberRange, URL, EqualTo, Regexp, Optional 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, from yubiadmin.util.config import (python_handler, python_list_handler,
FileConfig) FileConfig)
from yubiadmin.util.form import ConfigForm, FileForm, ListField from yubiadmin.util.form import ConfigForm, FileForm, ListField
@ -291,31 +290,29 @@ class AssignYubiKeyForm(Form):
self.auth.commit() self.auth.commit()
class YubiAuthUsers(App): class YubiAuthUsers(CollectionApp):
user_range = re.compile('(\d+)-(\d+)') base_url = '/auth/users'
item_name = 'Users'
caption = 'YubiAuth Users'
columns = ['Username', 'YubiKeys']
template = 'auth/list'
def __init__(self): def __init__(self):
self.auth = YubiAuth() self.auth = YubiAuth()
def __call__(self, request): def size(self):
sub_cmd = request.path_info_pop() return self.auth.session.query(User).count()
if sub_cmd == 'create':
return self.create(request) def get(self, offset, limit):
elif sub_cmd == 'delete': users = self.auth.session.query(User).order_by(User.name) \
return self.delete(request) .offset(offset).limit(limit)
elif sub_cmd == 'delete_confirm':
return self.delete_confirm(request) return map(lambda user: {
elif sub_cmd == 'user': 'id': user.id,
return self.show_user(request) 'Username': '<a href="/auth/users/show/%d">%s</a>' % (user.id,
else: user.name),
match = self.user_range.match(sub_cmd) if sub_cmd else None 'YubiKeys': ', '.join(user.yubikeys.keys())
if match: }, users)
offset = int(match.group(1)) - 1
limit = int(match.group(2)) - offset
else:
offset = 0
limit = 10
return self.list_users(offset, limit)
def create(self, request): def create(self, request):
return self.render_forms(request, [CreateUserForm(self.auth)], return self.render_forms(request, [CreateUserForm(self.auth)],
@ -333,28 +330,7 @@ class YubiAuthUsers(App):
self.auth.commit() self.auth.commit()
return self.redirect('/auth/users') return self.redirect('/auth/users')
def list_users(self, offset, limit): def show(self, request):
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):
id = int(request.path_info_pop()) id = int(request.path_info_pop())
user = self.auth.get_user(id) user = self.auth.get_user(id)
if 'unassign' in request.params: if 'unassign' in request.params:

View File

@ -29,11 +29,11 @@ import re
import os import os
from wtforms.fields import IntegerField from wtforms.fields import IntegerField
from wtforms.validators import NumberRange, IPAddress, URL 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, from yubiadmin.util.config import (RegexHandler, FileConfig, php_inserter,
parse_block, strip_comments, strip_quotes) parse_block, strip_comments, strip_quotes)
from yubiadmin.util.form import ConfigForm, FileForm, DBConfigForm, ListField 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__ = [ __all__ = [
'app' 'app'
@ -210,15 +210,25 @@ class YubikeyVal(App):
""" """
name = 'val' name = 'val'
sections = ['general', 'database', 'synchronization', 'ksms', 'advanced'] sections = ['general', 'clients', 'database', 'synchronization', 'ksms',
'advanced']
disabled = not os.path.isfile(YKVAL_CONFIG_FILE) disabled = not os.path.isfile(YKVAL_CONFIG_FILE)
def __init__(self):
self._clients = YubikeyValClients()
def general(self, request): def general(self, request):
""" """
General General
""" """
return self.render_forms(request, [SyncLevelsForm(), MiscForm()]) return self.render_forms(request, [SyncLevelsForm(), MiscForm()])
def clients(self, request):
"""
API Clients
"""
return self._clients(request)
def database(self, request): def database(self, request):
""" """
Database Settings Database Settings
@ -260,7 +270,40 @@ class YubikeyVal(App):
FileForm(YKVAL_CONFIG_FILE, 'Configuration') FileForm(YKVAL_CONFIG_FILE, 'Configuration')
]) ])
#Pulls the tab to the right: # Pulls the tab to the right:
advanced.advanced = True 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() app = YubikeyVal()

View 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');
}
});
});

View File

@ -1,40 +1,8 @@
<form action="/auth/users/delete" method="post"> {% from 'table.html' import table %}
<table class="table table-striped table-condensed"> <form action="/auth/users/delete" method="post">
<caption>YubiAuth users</caption>
<thead> {{ table(cols, items, caption, next, prev, shown, total, item_name) }}
<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 }}
&nbsp;
<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>
<input id="delete_btn" type="submit" class="btn btn-danger" value="Delete selected" /> <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> <a href="/auth/users/create" class="btn btn-primary pull-right">Create new user</a>

View 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 }}
&nbsp;
<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) }}

View 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>

View File

@ -26,12 +26,14 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import os import os
import re
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from webob import exc from webob import exc
from webob.dec import wsgify from webob.dec import wsgify
__all__ = [ __all__ = [
'App', 'App',
'CollectionApp',
'render', 'render',
'populate_forms', 'populate_forms',
] ]
@ -113,3 +115,57 @@ class App(object):
return render(template, target=request.path, fieldsets=forms, return render(template, target=request.path, fieldsets=forms,
alert=alert, **kwargs) 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)