Add support for Find Roles by Permission X feature
This commit is contained in:
parent
dd5162c151
commit
cab339e367
67
tailbone/templates/principal/find_by_perm.mako
Normal file
67
tailbone/templates/principal/find_by_perm.mako
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/base.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Find ${model_title_plural} by Permission</%def>
|
||||||
|
|
||||||
|
<%def name="extra_javascript()">
|
||||||
|
${parent.extra_javascript()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
<% gcount = len(permissions) %>
|
||||||
|
var permissions_by_group = {
|
||||||
|
% for g, (gkey, group) in enumerate(permissions, 1):
|
||||||
|
<% pcount = len(group['perms']) %>
|
||||||
|
'${gkey}': {
|
||||||
|
% for p, (pkey, perm) in enumerate(group['perms'], 1):
|
||||||
|
'${pkey}': "${perm['label']}"${',' if p < pcount else ''}
|
||||||
|
% endfor
|
||||||
|
}${',' if g < gcount else ''}
|
||||||
|
% endfor
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
$('#permission_group').selectmenu({
|
||||||
|
change: function(event, ui) {
|
||||||
|
var perms = $('#permission');
|
||||||
|
perms.find('option:first').siblings('option').remove();
|
||||||
|
$.each(permissions_by_group[ui.item.value], function(key, label) {
|
||||||
|
perms.append($('<option value="' + key + '">' + label + '</option>'));
|
||||||
|
});
|
||||||
|
perms.selectmenu('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#permission').selectmenu();
|
||||||
|
|
||||||
|
$('#find-by-perm-form').submit(function() {
|
||||||
|
$('.newgrid').remove();
|
||||||
|
$(this).find('#submit').button('disable').button('option', 'label', "Searching, please wait...");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${h.form(request.current_route_url(), id='find-by-perm-form')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
|
||||||
|
<div class="form">
|
||||||
|
${self.wtfield(form, 'permission_group')}
|
||||||
|
${self.wtfield(form, 'permission')}
|
||||||
|
<div class="buttons">
|
||||||
|
${h.submit('submit', "Find {}".format(model_title_plural))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
% if principals is not None:
|
||||||
|
<div class="newgrid half">
|
||||||
|
<br />
|
||||||
|
<h2>${model_title_plural} with that permission (${len(principals)} total):</h2>
|
||||||
|
${self.principal_table()}
|
||||||
|
</div>
|
||||||
|
% endif
|
11
tailbone/templates/principal/index.mako
Normal file
11
tailbone/templates/principal/index.mako
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
${parent.context_menu_items()}
|
||||||
|
% if request.has_perm('{}.find_by_perm'.format(permission_prefix)):
|
||||||
|
<li>${h.link_to("Find {} with Permission X".format(model_title_plural), url('{}.find_by_perm'.format(route_prefix)))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
21
tailbone/templates/roles/find_by_perm.mako
Normal file
21
tailbone/templates/roles/find_by_perm.mako
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/principal/find_by_perm.mako" />
|
||||||
|
|
||||||
|
<%def name="principal_table()">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for role in principals:
|
||||||
|
<tr>
|
||||||
|
<td>${h.link_to(role.name, url('roles.view', uuid=role.uuid))}</td>
|
||||||
|
</tr>
|
||||||
|
% endfor
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -1,66 +1,7 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/base.mako" />
|
<%inherit file="/principal/find_by_perm.mako" />
|
||||||
|
|
||||||
<%def name="title()">Find Users by Permission</%def>
|
<%def name="principal_table()">
|
||||||
|
|
||||||
<%def name="extra_javascript()">
|
|
||||||
${parent.extra_javascript()}
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
<% gcount = len(permissions) %>
|
|
||||||
var permissions_by_group = {
|
|
||||||
% for g, (gkey, group) in enumerate(permissions, 1):
|
|
||||||
<% pcount = len(group['perms']) %>
|
|
||||||
'${gkey}': {
|
|
||||||
% for p, (pkey, perm) in enumerate(group['perms'], 1):
|
|
||||||
'${pkey}': "${perm['label']}"${',' if p < pcount else ''}
|
|
||||||
% endfor
|
|
||||||
}${',' if g < gcount else ''}
|
|
||||||
% endfor
|
|
||||||
};
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
|
|
||||||
$('#permission_group').selectmenu({
|
|
||||||
change: function(event, ui) {
|
|
||||||
var perms = $('#permission');
|
|
||||||
perms.find('option:first').siblings('option').remove();
|
|
||||||
$.each(permissions_by_group[ui.item.value], function(key, label) {
|
|
||||||
perms.append($('<option value="' + key + '">' + label + '</option>'));
|
|
||||||
});
|
|
||||||
perms.selectmenu('refresh');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#permission').selectmenu();
|
|
||||||
|
|
||||||
$('#find-by-perm-form').submit(function() {
|
|
||||||
$('.newgrid').remove();
|
|
||||||
$(this).find('#submit').button('disable').button('option', 'label', "Searching, please wait...");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
${h.form(request.current_route_url(), id='find-by-perm-form')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
|
|
||||||
<div class="form">
|
|
||||||
${self.wtfield(form, 'permission_group')}
|
|
||||||
${self.wtfield(form, 'permission')}
|
|
||||||
<div class="buttons">
|
|
||||||
${h.submit('submit', "Find Users")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${h.end_form()}
|
|
||||||
|
|
||||||
% if users is not None:
|
|
||||||
<div class="newgrid half">
|
|
||||||
<br />
|
|
||||||
<h2>Users with that permission (${len(users)} total):</h2>
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -69,7 +10,7 @@ ${h.end_form()}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
% for user in users:
|
% for user in principals:
|
||||||
<tr>
|
<tr>
|
||||||
<td>${h.link_to(user.username, url('users.view', uuid=user.uuid))}</td>
|
<td>${h.link_to(user.username, url('users.view', uuid=user.uuid))}</td>
|
||||||
<td>${user.person or ''}</td>
|
<td>${user.person or ''}</td>
|
||||||
|
@ -77,5 +18,6 @@ ${h.end_form()}
|
||||||
% endfor
|
% endfor
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</%def>
|
||||||
% endif
|
|
||||||
|
${parent.body()}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<%inherit file="/master/index.mako" />
|
|
||||||
|
|
||||||
<%def name="context_menu_items()">
|
|
||||||
${parent.context_menu_items()}
|
|
||||||
% if request.has_perm('users.find_by_perm'):
|
|
||||||
<li>${h.link_to("Find Users with Permission X", url('users.find_by_perm'))}</li>
|
|
||||||
% endif
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
${parent.body()}
|
|
97
tailbone/views/principal.py
Normal file
97
tailbone/views/principal.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
"Principal" master view
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import wtforms
|
||||||
|
|
||||||
|
from tailbone.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
|
class PrincipalMasterView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view base class for security principal models, i.e. User and Role.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_fallback_templates(self, template):
|
||||||
|
return [
|
||||||
|
'/principal/{}.mako'.format(template),
|
||||||
|
] + super(PrincipalMasterView, self).get_fallback_templates(template)
|
||||||
|
|
||||||
|
def find_by_perm(self):
|
||||||
|
"""
|
||||||
|
View for finding all users who have been granted a given permission
|
||||||
|
"""
|
||||||
|
permissions = copy.deepcopy(self.request.registry.settings.get('tailbone_permissions', {}))
|
||||||
|
|
||||||
|
# sort groups, and permissions for each group, for UI's sake
|
||||||
|
sorted_perms = sorted(permissions.items(), key=lambda (k, v): v['label'].lower())
|
||||||
|
for key, group in sorted_perms:
|
||||||
|
group['perms'] = sorted(group['perms'].items(), key=lambda (k, v): v['label'].lower())
|
||||||
|
|
||||||
|
# group options are stable, permission options may depend on submitted group
|
||||||
|
group_choices = [(gkey, group['label']) for gkey, group in sorted_perms]
|
||||||
|
permission_choices = [('_any_', "(any)")]
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if self.request.POST.get('permission_group') in permissions:
|
||||||
|
permission_choices.extend([
|
||||||
|
(pkey, perm['label'])
|
||||||
|
for pkey, perm in permissions[self.request.POST['permission_group']]['perms']
|
||||||
|
])
|
||||||
|
|
||||||
|
class PermissionForm(wtforms.Form):
|
||||||
|
permission_group = wtforms.SelectField(choices=group_choices)
|
||||||
|
permission = wtforms.SelectField(choices=permission_choices)
|
||||||
|
|
||||||
|
principals = None
|
||||||
|
form = PermissionForm(self.request.POST)
|
||||||
|
if self.request.method == 'POST' and form.validate():
|
||||||
|
permission = form.permission.data
|
||||||
|
principals = self.find_principals_with_permission(self.Session(), permission)
|
||||||
|
|
||||||
|
context = {'form': form, 'permissions': sorted_perms, 'principals': principals}
|
||||||
|
return self.render_to_response('find_by_perm', context)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._principal_defaults(config)
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _principal_defaults(cls, config):
|
||||||
|
route_prefix = cls.get_route_prefix()
|
||||||
|
url_prefix = cls.get_url_prefix()
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
|
||||||
|
# find principal by permission
|
||||||
|
config.add_route('{}.find_by_perm'.format(route_prefix), '{}/find-by-perm'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='find_by_perm', route_name='{}.find_by_perm'.format(route_prefix),
|
||||||
|
permission='{}.find_by_perm'.format(permission_prefix))
|
||||||
|
config.add_tailbone_permission(permission_prefix, '{}.find_by_perm'.format(permission_prefix),
|
||||||
|
"Find all {} with permission X".format(model_title_plural))
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2016 Lance Edgar
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,6 +26,8 @@ Role Views
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.db.auth import has_permission, administrator_role, guest_role, authenticated_role
|
from rattail.db.auth import has_permission, administrator_role, guest_role, authenticated_role
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ from webhelpers.html import HTML, tags
|
||||||
|
|
||||||
from tailbone import forms
|
from tailbone import forms
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
from tailbone.views import MasterView
|
from tailbone.views.principal import PrincipalMasterView
|
||||||
from tailbone.views.continuum import VersionView, version_defaults
|
from tailbone.views.continuum import VersionView, version_defaults
|
||||||
from tailbone.newgrids import AlchemyGrid, GridAction
|
from tailbone.newgrids import AlchemyGrid, GridAction
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ class PermissionsField(formalchemy.Field):
|
||||||
role.permissions = self.renderer.deserialize()
|
role.permissions = self.renderer.deserialize()
|
||||||
|
|
||||||
|
|
||||||
class RolesView(MasterView):
|
class RolesView(PrincipalMasterView):
|
||||||
"""
|
"""
|
||||||
Master view for the Role model.
|
Master view for the Role model.
|
||||||
"""
|
"""
|
||||||
|
@ -108,6 +110,17 @@ class RolesView(MasterView):
|
||||||
self.request.session.flash("You may not delete the {} role.".format(role.name), 'error')
|
self.request.session.flash("You may not delete the {} role.".format(role.name), 'error')
|
||||||
return self.redirect(self.request.get_referrer(default=self.request.route_url('roles')))
|
return self.redirect(self.request.get_referrer(default=self.request.route_url('roles')))
|
||||||
|
|
||||||
|
def find_principals_with_permission(self, session, permission):
|
||||||
|
# TODO: this should search Permission table instead, and work backward to Role?
|
||||||
|
all_roles = session.query(model.Role)\
|
||||||
|
.order_by(model.Role.name)\
|
||||||
|
.options(orm.joinedload(model.Role._permissions))
|
||||||
|
roles = []
|
||||||
|
for role in all_roles:
|
||||||
|
if has_permission(session, role, permission):
|
||||||
|
roles.append(role)
|
||||||
|
return roles
|
||||||
|
|
||||||
|
|
||||||
class RoleVersionView(VersionView):
|
class RoleVersionView(VersionView):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -40,7 +40,7 @@ from webhelpers.html import HTML, tags
|
||||||
|
|
||||||
from tailbone import forms
|
from tailbone import forms
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
from tailbone.views import MasterView
|
from tailbone.views.principal import PrincipalMasterView
|
||||||
from tailbone.views.continuum import VersionView, version_defaults
|
from tailbone.views.continuum import VersionView, version_defaults
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class RolesField(formalchemy.Field):
|
||||||
user.roles = [roles.get(x) for x in data]
|
user.roles = [roles.get(x) for x in data]
|
||||||
|
|
||||||
|
|
||||||
class UsersView(MasterView):
|
class UsersView(PrincipalMasterView):
|
||||||
"""
|
"""
|
||||||
Master view for the User model.
|
Master view for the User model.
|
||||||
"""
|
"""
|
||||||
|
@ -188,41 +188,7 @@ class UsersView(MasterView):
|
||||||
del fs.password
|
del fs.password
|
||||||
del fs.confirm_password
|
del fs.confirm_password
|
||||||
|
|
||||||
def find_by_perm(self):
|
def find_principals_with_permission(self, session, permission):
|
||||||
"""
|
|
||||||
View for finding all users who have been granted a given permission
|
|
||||||
"""
|
|
||||||
permissions = copy.deepcopy(self.request.registry.settings.get('tailbone_permissions', {}))
|
|
||||||
|
|
||||||
# sort groups, and permissions for each group, for UI's sake
|
|
||||||
sorted_perms = sorted(permissions.items(), key=lambda (k, v): v['label'].lower())
|
|
||||||
for key, group in sorted_perms:
|
|
||||||
group['perms'] = sorted(group['perms'].items(), key=lambda (k, v): v['label'].lower())
|
|
||||||
|
|
||||||
# group options are stable, permission options may depend on submitted group
|
|
||||||
group_choices = [(gkey, group['label']) for gkey, group in sorted_perms]
|
|
||||||
permission_choices = [('_any_', "(any)")]
|
|
||||||
if self.request.method == 'POST':
|
|
||||||
if self.request.POST.get('permission_group') in permissions:
|
|
||||||
permission_choices.extend([
|
|
||||||
(pkey, perm['label'])
|
|
||||||
for pkey, perm in permissions[self.request.POST['permission_group']]['perms']
|
|
||||||
])
|
|
||||||
|
|
||||||
class PermissionForm(wtforms.Form):
|
|
||||||
permission_group = wtforms.SelectField(choices=group_choices)
|
|
||||||
permission = wtforms.SelectField(choices=permission_choices)
|
|
||||||
|
|
||||||
users = None
|
|
||||||
form = PermissionForm(self.request.POST)
|
|
||||||
if self.request.method == 'POST' and form.validate():
|
|
||||||
permission = form.permission.data
|
|
||||||
users = self.find_users_by_permission(self.Session(), permission)
|
|
||||||
|
|
||||||
context = {'form': form, 'permissions': sorted_perms, 'users': users}
|
|
||||||
return self.render_to_response('find_by_perm', context)
|
|
||||||
|
|
||||||
def find_users_by_permission(self, session, permission):
|
|
||||||
# TODO: this should search Permission table instead, and work backward to User?
|
# TODO: this should search Permission table instead, and work backward to User?
|
||||||
all_users = session.query(model.User)\
|
all_users = session.query(model.User)\
|
||||||
.filter(model.User.active == True)\
|
.filter(model.User.active == True)\
|
||||||
|
@ -236,25 +202,6 @@ class UsersView(MasterView):
|
||||||
users.append(user)
|
users.append(user)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def defaults(cls, config):
|
|
||||||
cls._user_defaults(config)
|
|
||||||
cls._defaults(config)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _user_defaults(cls, config):
|
|
||||||
route_prefix = cls.get_route_prefix()
|
|
||||||
url_prefix = cls.get_url_prefix()
|
|
||||||
permission_prefix = cls.get_permission_prefix()
|
|
||||||
model_title_plural = cls.get_model_title_plural()
|
|
||||||
|
|
||||||
# find users by permission
|
|
||||||
config.add_route('{}.find_by_perm'.format(route_prefix), '{}/find-by-perm'.format(url_prefix))
|
|
||||||
config.add_view(cls, attr='find_by_perm', route_name='{}.find_by_perm'.format(route_prefix),
|
|
||||||
permission='{}.find_by_perm'.format(permission_prefix))
|
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.find_by_perm'.format(permission_prefix),
|
|
||||||
"Find all {} with permission X".format(model_title_plural))
|
|
||||||
|
|
||||||
|
|
||||||
class UserVersionView(VersionView):
|
class UserVersionView(VersionView):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue