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_mako')
config.include('pyramid_tm') 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. # Configure FormAlchemy.
formalchemy.config.engine = TemplateEngine() formalchemy.config.engine = TemplateEngine()
formalchemy.FieldSet.default_renderers[GPCType] = renderers.GPCFieldRenderer formalchemy.FieldSet.default_renderers[GPCType] = renderers.GPCFieldRenderer

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar # Copyright © 2010-2015 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -21,28 +20,31 @@
# along with Rattail. If not, see <http://www.gnu.org/licenses/>. # along with Rattail. If not, see <http://www.gnu.org/licenses/>.
# #
################################################################################ ################################################################################
""" """
Authentication & Authorization 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 zope.interface import implementer
from pyramid.interfaces import IAuthorizationPolicy from pyramid.interfaces import IAuthorizationPolicy
from pyramid.security import Everyone, Authenticated from pyramid.security import Everyone, Authenticated
from .db import Session from tailbone.db import Session
@implementer(IAuthorizationPolicy) @implementer(IAuthorizationPolicy)
class TailboneAuthorizationPolicy(object): class TailboneAuthorizationPolicy(object):
def permits(self, context, principals, permission): def permits(self, context, principals, permission):
from rattail.db.model import User
from rattail.db.auth import has_permission
for userid in principals: for userid in principals:
if userid not in (Everyone, Authenticated): if userid not in (Everyone, Authenticated):
user = Session.query(User).get(userid) user = Session.query(model.User).get(userid)
if user: if user:
return has_permission(Session(), user, permission) return has_permission(Session(), user, permission)
if Everyone in principals: if Everyone in principals:
@ -51,3 +53,29 @@ class TailboneAuthorizationPolicy(object):
def principals_allowed_by_permission(self, context, permission): def principals_allowed_by_permission(self, context, permission):
raise NotImplementedError 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() url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix() permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key() 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 # list/search
config.add_route(route_prefix, '{0}/'.format(url_prefix)) config.add_route(route_prefix, '{0}/'.format(url_prefix))
config.add_view(cls, attr='index', route_name=route_prefix, config.add_view(cls, attr='index', route_name=route_prefix,
permission='{0}.list'.format(permission_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 # create
config.add_route('{0}.create'.format(route_prefix), '{0}/new'.format(url_prefix)) 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), config.add_view(cls, attr='create', route_name='{0}.create'.format(route_prefix),
permission='{0}.create'.format(permission_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 # view
config.add_route('{0}.view'.format(route_prefix), '{0}/{{{1}}}'.format(url_prefix, model_key)) 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), config.add_view(cls, attr='view', route_name='{0}.view'.format(route_prefix),
permission='{0}.view'.format(permission_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 # edit
config.add_route('{0}.edit'.format(route_prefix), '{0}/{{{1}}}/edit'.format(url_prefix, model_key)) 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), config.add_view(cls, attr='edit', route_name='{0}.edit'.format(route_prefix),
permission='{0}.edit'.format(permission_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 # delete
config.add_route('{0}.delete'.format(route_prefix), '{0}/{{{1}}}/delete'.format(url_prefix, model_key)) 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), config.add_view(cls, attr='delete', route_name='{0}.delete'.format(route_prefix),
permission='{0}.delete'.format(permission_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() role.permissions = self.renderer.deserialize()
def OldPermissionsFieldRenderer(permissions, *args, **kwargs): def PermissionsFieldRenderer(permissions, *args, **kwargs):
perms = permissions
class PermissionsFieldRenderer(formalchemy.FieldRenderer): class PermissionsFieldRenderer(formalchemy.FieldRenderer):
permissions = perms
def deserialize(self): def deserialize(self):
perms = [] perms = []
i = len(self.name) + 1 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? html += tags.hidden(self.name, value='') # ugly hack..or good idea?
else: else:
html = '' html = ''
for group, perms in self.permissions: for groupkey in sorted(permissions, key=lambda k: permissions[k]['label']):
inner = HTML.tag('p', c=group) inner = HTML.tag('p', c=permissions[groupkey]['label'])
for perm, title in perms: perms = permissions[groupkey]['perms']
checked = has_permission( for key in sorted(perms, key=lambda p: perms[p]['label']):
Session(), role, perm, include_guest=False) checked = has_permission(Session(), role, key, include_guest=False)
label = perms[key]['label']
if readonly: if readonly:
span = HTML.tag('span', c="[X]" if checked else "[ ]") 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: else:
inner += tags.checkbox(self.name + '-' + perm, inner += tags.checkbox(self.name + '-' + key,
checked=checked, label=title) checked=checked, label=label)
html += HTML.tag('div', class_='group', c=inner) html += HTML.tag('div', class_='group', c=inner)
return html return html
@ -116,7 +113,8 @@ class RolesView(MasterView):
def configure_fieldset(self, fs): def configure_fieldset(self, fs):
fs.append(PermissionsField('permissions')) 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( fs.configure(
include=[ include=[
fs.name, fs.name,
@ -159,15 +157,9 @@ class RoleVersionView(VersionView):
View which shows version history for a role. View which shows version history for a role.
""" """
parent_class = model.Role parent_class = model.Role
route_model_view = 'role.read' route_model_view = 'roles.view'
def includeme(config): 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) RolesView.defaults(config)
version_defaults(config, RoleVersionView, 'role') version_defaults(config, RoleVersionView, 'role')