Overhaul how available permissions are registered in app config.

Permissions must now be regsistered just like routes and views.  This
should make things much nicer going forward.
This commit is contained in:
Lance Edgar 2015-08-11 17:26:04 -05:00
parent 412ac6e12f
commit 9cfbc918e7
4 changed files with 76 additions and 30 deletions

View file

@ -117,6 +117,18 @@ def make_pyramid_config(settings):
config.include('pyramid_mako')
config.include('pyramid_tm')
# Add some permissions magic.
config.add_directive('add_tailbone_permission_group', 'tailbone.auth.add_permission_group')
config.add_directive('add_tailbone_permission', 'tailbone.auth.add_permission')
# TODO: This can finally be removed once all CRUD/index views have been
# converted to use the new master view etc.
for label, perms in settings.get('edbob.permissions'):
groupkey = label.lower().replace(' ', '_')
config.add_tailbone_permission_group(groupkey, label)
for key, label in perms:
config.add_tailbone_permission(groupkey, key, label)
# Configure FormAlchemy.
formalchemy.config.engine = TemplateEngine()
formalchemy.FieldSet.default_renderers[GPCType] = renderers.GPCFieldRenderer

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
# Copyright © 2010-2015 Lance Edgar
#
# This file is part of Rattail.
#
@ -21,28 +20,31 @@
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Authentication & Authorization
"""
from __future__ import unicode_literals
from edbob.util import prettify
from rattail.db import model
from rattail.db.auth import has_permission
from zope.interface import implementer
from pyramid.interfaces import IAuthorizationPolicy
from pyramid.security import Everyone, Authenticated
from .db import Session
from tailbone.db import Session
@implementer(IAuthorizationPolicy)
class TailboneAuthorizationPolicy(object):
def permits(self, context, principals, permission):
from rattail.db.model import User
from rattail.db.auth import has_permission
for userid in principals:
if userid not in (Everyone, Authenticated):
user = Session.query(User).get(userid)
user = Session.query(model.User).get(userid)
if user:
return has_permission(Session(), user, permission)
if Everyone in principals:
@ -51,3 +53,29 @@ class TailboneAuthorizationPolicy(object):
def principals_allowed_by_permission(self, context, permission):
raise NotImplementedError
def add_permission_group(config, key, label=None):
"""
Add a permission group to the app configuration.
"""
def action():
perms = config.get_settings().get('tailbone_permissions', {})
group = perms.setdefault(key, {'key': key})
group['label'] = label or prettify(key)
config.add_settings({'tailbone_permissions': perms})
config.action(None, action)
def add_permission(config, groupkey, key, label=None):
"""
Add a permission to the app configuration.
"""
def action():
perms = config.get_settings().get('tailbone_permissions', {})
group = perms.setdefault(groupkey, {'key': groupkey})
group.setdefault('label', prettify(groupkey))
perm = group.setdefault('perms', {}).setdefault(key, {'key': key})
perm['label'] = label or prettify(key)
config.add_settings({'tailbone_permissions': perms})
config.action(None, action)

View file

@ -441,28 +441,42 @@ class MasterView(View):
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key()
model_title = cls.get_model_title()
model_title_plural = cls.get_model_title_plural()
config.add_tailbone_permission_group(permission_prefix, model_title_plural)
# list/search
config.add_route(route_prefix, '{0}/'.format(url_prefix))
config.add_view(cls, attr='index', route_name=route_prefix,
permission='{0}.list'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.list'.format(permission_prefix),
"List/Search {0}".format(model_title_plural))
# create
config.add_route('{0}.create'.format(route_prefix), '{0}/new'.format(url_prefix))
config.add_view(cls, attr='create', route_name='{0}.create'.format(route_prefix),
permission='{0}.create'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.create'.format(permission_prefix),
"Create new {0}".format(model_title_plural))
# view
config.add_route('{0}.view'.format(route_prefix), '{0}/{{{1}}}'.format(url_prefix, model_key))
config.add_view(cls, attr='view', route_name='{0}.view'.format(route_prefix),
permission='{0}.view'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.view'.format(permission_prefix),
"View {0} Details".format(model_title))
# edit
config.add_route('{0}.edit'.format(route_prefix), '{0}/{{{1}}}/edit'.format(url_prefix, model_key))
config.add_view(cls, attr='edit', route_name='{0}.edit'.format(route_prefix),
permission='{0}.edit'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.edit'.format(permission_prefix),
"Edit {0}".format(model_title_plural))
# delete
config.add_route('{0}.delete'.format(route_prefix), '{0}/{{{1}}}/delete'.format(url_prefix, model_key))
config.add_view(cls, attr='delete', route_name='{0}.delete'.format(route_prefix),
permission='{0}.delete'.format(permission_prefix))
config.add_tailbone_permission(permission_prefix, '{0}.delete'.format(permission_prefix),
"Delete {0}".format(model_title_plural))

View file

@ -49,14 +49,10 @@ class PermissionsField(formalchemy.Field):
role.permissions = self.renderer.deserialize()
def OldPermissionsFieldRenderer(permissions, *args, **kwargs):
perms = permissions
def PermissionsFieldRenderer(permissions, *args, **kwargs):
class PermissionsFieldRenderer(formalchemy.FieldRenderer):
permissions = perms
def deserialize(self):
perms = []
i = len(self.name) + 1
@ -75,17 +71,18 @@ def OldPermissionsFieldRenderer(permissions, *args, **kwargs):
html += tags.hidden(self.name, value='') # ugly hack..or good idea?
else:
html = ''
for group, perms in self.permissions:
inner = HTML.tag('p', c=group)
for perm, title in perms:
checked = has_permission(
Session(), role, perm, include_guest=False)
for groupkey in sorted(permissions, key=lambda k: permissions[k]['label']):
inner = HTML.tag('p', c=permissions[groupkey]['label'])
perms = permissions[groupkey]['perms']
for key in sorted(perms, key=lambda p: perms[p]['label']):
checked = has_permission(Session(), role, key, include_guest=False)
label = perms[key]['label']
if readonly:
span = HTML.tag('span', c="[X]" if checked else "[ ]")
inner += HTML.tag('p', class_='perm', c=span + ' ' + title)
inner += HTML.tag('p', class_='perm', c=span + ' ' + label)
else:
inner += tags.checkbox(self.name + '-' + perm,
checked=checked, label=title)
inner += tags.checkbox(self.name + '-' + key,
checked=checked, label=label)
html += HTML.tag('div', class_='group', c=inner)
return html
@ -116,7 +113,8 @@ class RolesView(MasterView):
def configure_fieldset(self, fs):
fs.append(PermissionsField('permissions'))
fs.permissions.set(renderer=OldPermissionsFieldRenderer(self.old_permissions))
permissions = self.request.registry.settings.get('tailbone_permissions', {})
fs.permissions.set(renderer=PermissionsFieldRenderer(permissions))
fs.configure(
include=[
fs.name,
@ -159,15 +157,9 @@ class RoleVersionView(VersionView):
View which shows version history for a role.
"""
parent_class = model.Role
route_model_view = 'role.read'
route_model_view = 'roles.view'
def includeme(config):
# TODO: This can finally be removed once all CRUD/index views have been
# converted to use the new master view etc.
settings = config.get_settings()
RolesView.old_permissions = settings.get('edbob.permissions')
RolesView.defaults(config)
version_defaults(config, RoleVersionView, 'role')