Add support for Find Roles by Permission X feature

This commit is contained in:
Lance Edgar 2017-02-06 13:37:42 -06:00
parent dd5162c151
commit cab339e367
8 changed files with 221 additions and 134 deletions

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

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2016 Lance Edgar
# Copyright © 2010-2017 Lance Edgar
#
# This file is part of Rattail.
#
@ -26,6 +26,8 @@ Role Views
from __future__ import unicode_literals, absolute_import
from sqlalchemy import orm
from rattail.db import model
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.db import Session
from tailbone.views import MasterView
from tailbone.views.principal import PrincipalMasterView
from tailbone.views.continuum import VersionView, version_defaults
from tailbone.newgrids import AlchemyGrid, GridAction
@ -50,7 +52,7 @@ class PermissionsField(formalchemy.Field):
role.permissions = self.renderer.deserialize()
class RolesView(MasterView):
class RolesView(PrincipalMasterView):
"""
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')
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):
"""

View file

@ -40,7 +40,7 @@ from webhelpers.html import HTML, tags
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views.principal import PrincipalMasterView
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]
class UsersView(MasterView):
class UsersView(PrincipalMasterView):
"""
Master view for the User model.
"""
@ -188,41 +188,7 @@ class UsersView(MasterView):
del fs.password
del fs.confirm_password
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)
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):
def find_principals_with_permission(self, session, permission):
# TODO: this should search Permission table instead, and work backward to User?
all_users = session.query(model.User)\
.filter(model.User.active == True)\
@ -236,25 +202,6 @@ class UsersView(MasterView):
users.append(user)
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):
"""