diff --git a/tailbone/app.py b/tailbone/app.py index 7ba66730..43db4c6e 100644 --- a/tailbone/app.py +++ b/tailbone/app.py @@ -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 diff --git a/tailbone/auth.py b/tailbone/auth.py index 1bfc4fb1..2ccf1418 100644 --- a/tailbone/auth.py +++ b/tailbone/auth.py @@ -1,9 +1,8 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# -*- 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 . # ################################################################################ - """ 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) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index f1f47633..57aa647b 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -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)) diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index c4d7b929..f2d40c5b 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -49,14 +49,10 @@ class PermissionsField(formalchemy.Field): role.permissions = self.renderer.deserialize() -def OldPermissionsFieldRenderer(permissions, *args, **kwargs): +def PermissionsFieldRenderer(permissions, *args, **kwargs): - perms = permissions - 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')