From 22aa55c24bf34757e340ceedea7446fd06540f01 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 14 Oct 2021 10:39:54 -0400 Subject: [PATCH] Invoke the auth handler to cache user permissions etc. various changes for sake of "synced" roles feature --- tailbone/auth.py | 11 +++++----- tailbone/subscribers.py | 14 +++++++++++-- tailbone/views/roles.py | 45 ++++++++++++++++++++++++++++++++++++----- tailbone/views/users.py | 16 +++++++++++++++ 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/tailbone/auth.py b/tailbone/auth.py index 338fac55..deda1ab7 100644 --- a/tailbone/auth.py +++ b/tailbone/auth.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -93,9 +93,6 @@ def set_session_timeout(request, timeout): class TailboneAuthorizationPolicy(object): def permits(self, context, principals, permission): - from rattail.db import model - from rattail.db.auth import has_permission - for userid in principals: if userid not in (Everyone, Authenticated): if context.request.user and context.request.user.uuid == userid: @@ -103,11 +100,15 @@ class TailboneAuthorizationPolicy(object): else: # this is pretty rare, but can happen in dev after # re-creating the database, which means new user uuids. + config = context.request.rattail_config + model = config.get_model() + app = config.get_app() + auth = app.get_auth_handler() # TODO: the odds of this query returning a user in that # case, are probably nil, and we should just skip this bit? user = Session.query(model.User).get(userid) if user: - if has_permission(Session(), user, permission): + if auth.has_permission(Session(), user, permission): return True if Everyone in principals: return has_permission(Session(), None, permission) diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py index b0834496..5468df7f 100644 --- a/tailbone/subscribers.py +++ b/tailbone/subscribers.py @@ -32,7 +32,6 @@ import datetime import rattail from rattail.db import model -from rattail.db.auth import cache_permissions import colander import deform @@ -69,6 +68,7 @@ def new_request(event): """ request = event.request rattail_config = request.registry.settings.get('rattail_config') + # TODO: why would this ever be null? if rattail_config: request.rattail_config = rattail_config @@ -86,7 +86,17 @@ def new_request(event): request.is_admin = bool(request.user) and request.user.is_admin() request.is_root = request.is_admin and request.session.get('is_root', False) - request.tailbone_cached_permissions = cache_permissions(Session(), request.user) + if rattail_config: + app = rattail_config.get_app() + auth = app.get_auth_handler() + request.tailbone_cached_permissions = auth.cache_permissions( + Session(), request.user) + else: + # TODO: not sure why this would really work, or even be + # needed, if there was no rattail config? + from rattail.db.auth import cache_permissions + request.tailbone_cached_permissions = cache_permissions( + Session(), request.user) def before_render(event): diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index 3cd62571..8dde78b8 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -52,10 +52,13 @@ class RoleView(PrincipalMasterView): """ model_class = model.Role has_versions = True + touchable = True grid_columns = [ 'name', 'session_timeout', + 'sync_me', + 'node_type', 'notes', ] @@ -63,6 +66,8 @@ class RoleView(PrincipalMasterView): 'name', 'session_timeout', 'notes', + 'sync_me', + 'node_type', 'users', 'permissions', ] @@ -93,6 +98,11 @@ class RoleView(PrincipalMasterView): We must prevent edit for certain built-in roles etc., depending on current user's permissions. """ + # role with node type specified, can only be edited from a + # node of the same type + if role.node_type and role.node_type != self.rattail_config.node_type(): + return False + # only "root" can edit Administrator if role is administrator_role(self.Session()): return self.request.is_root @@ -116,6 +126,11 @@ class RoleView(PrincipalMasterView): """ We must prevent deletion for all built-in roles. """ + # role with node type specified, can only be edited from a + # node of the same type + if role.node_type and role.node_type != self.rattail_config.node_type(): + return False + if role is administrator_role(self.Session()): return False if role is authenticated_role(self.Session()): @@ -147,6 +162,27 @@ class RoleView(PrincipalMasterView): # name f.set_validator('name', self.unique_name) + # session_timeout + f.set_renderer('session_timeout', self.render_session_timeout) + if self.editing and role is guest_role(self.Session()): + f.set_readonly('session_timeout') + + # sync_me, node_type + if not self.creating: + include = True + if role is administrator_role(self.Session()): + include = False + elif role is authenticated_role(self.Session()): + include = False + elif role is guest_role(self.Session()): + include = False + if not include: + f.remove('sync_me', 'node_type') + else: + if not self.has_perm('edit_node_sync'): + f.set_readonly('sync_me') + f.set_readonly('node_type') + # notes f.set_type('notes', 'text_wrapped') @@ -173,11 +209,6 @@ class RoleView(PrincipalMasterView): elif self.deleting: f.remove_field('permissions') - # session_timeout - f.set_renderer('session_timeout', self.render_session_timeout) - if self.editing and role is guest_role(self.Session()): - f.set_readonly('session_timeout') - def render_users(self, role, field): if role is guest_role(self.Session()): @@ -417,6 +448,7 @@ class RoleView(PrincipalMasterView): route_prefix = cls.get_route_prefix() url_prefix = cls.get_url_prefix() permission_prefix = cls.get_permission_prefix() + model_title = cls.get_model_title() # extra permissions for editing built-in roles etc. config.add_tailbone_permission(permission_prefix, '{}.edit_authenticated'.format(permission_prefix), @@ -425,6 +457,9 @@ class RoleView(PrincipalMasterView): "Edit the \"Guest\" Role") config.add_tailbone_permission(permission_prefix, '{}.edit_my'.format(permission_prefix), "Edit Role(s) to which current user belongs") + config.add_tailbone_permission(permission_prefix, + '{}.edit_node_sync'.format(permission_prefix), + "Edit the Node Type and Sync flags for a {}".format(model_title)) # download permissions matrix config.add_tailbone_permission(permission_prefix, '{}.download_permissions_matrix'.format(permission_prefix), diff --git a/tailbone/views/users.py b/tailbone/views/users.py index 6c8000ad..b30034b4 100644 --- a/tailbone/views/users.py +++ b/tailbone/views/users.py @@ -320,6 +320,14 @@ class UserView(PrincipalMasterView): if self.request.is_root or uuid != admin.uuid: user._roles.append(model.UserRole(role_uuid=uuid)) + # also record a change to the role, for datasync. + # this is done "just in case" the role is to be + # synced to all nodes + if self.Session().rattail_record_changes: + self.Session.add(model.Change(class_name='Role', + instance_uuid=uuid, + deleted=False)) + # remove any roles which were *not* specified, although must take care # not to remove admin role, unless acting as root for uuid in old_roles: @@ -328,6 +336,14 @@ class UserView(PrincipalMasterView): role = self.Session.query(model.Role).get(uuid) user.roles.remove(role) + # also record a change to the role, for datasync. + # this is done "just in case" the role is to be + # synced to all nodes + if self.Session().rattail_record_changes: + self.Session.add(model.Change(class_name='Role', + instance_uuid=uuid, + deleted=False)) + def render_person(self, user, field): person = user.person if not person: