Invoke the auth handler to cache user permissions etc.

various changes for sake of "synced" roles feature
This commit is contained in:
Lance Edgar 2021-10-14 10:39:54 -04:00
parent 80589cde2f
commit 22aa55c24b
4 changed files with 74 additions and 12 deletions

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar # Copyright © 2010-2021 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -93,9 +93,6 @@ def set_session_timeout(request, timeout):
class TailboneAuthorizationPolicy(object): class TailboneAuthorizationPolicy(object):
def permits(self, context, principals, permission): def permits(self, context, principals, permission):
from rattail.db import model
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):
if context.request.user and context.request.user.uuid == userid: if context.request.user and context.request.user.uuid == userid:
@ -103,11 +100,15 @@ class TailboneAuthorizationPolicy(object):
else: else:
# this is pretty rare, but can happen in dev after # this is pretty rare, but can happen in dev after
# re-creating the database, which means new user uuids. # 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 # TODO: the odds of this query returning a user in that
# case, are probably nil, and we should just skip this bit? # case, are probably nil, and we should just skip this bit?
user = Session.query(model.User).get(userid) user = Session.query(model.User).get(userid)
if user: if user:
if has_permission(Session(), user, permission): if auth.has_permission(Session(), user, permission):
return True return True
if Everyone in principals: if Everyone in principals:
return has_permission(Session(), None, permission) return has_permission(Session(), None, permission)

View file

@ -32,7 +32,6 @@ import datetime
import rattail import rattail
from rattail.db import model from rattail.db import model
from rattail.db.auth import cache_permissions
import colander import colander
import deform import deform
@ -69,6 +68,7 @@ def new_request(event):
""" """
request = event.request request = event.request
rattail_config = request.registry.settings.get('rattail_config') rattail_config = request.registry.settings.get('rattail_config')
# TODO: why would this ever be null?
if rattail_config: if rattail_config:
request.rattail_config = 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_admin = bool(request.user) and request.user.is_admin()
request.is_root = request.is_admin and request.session.get('is_root', False) 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): def before_render(event):

View file

@ -52,10 +52,13 @@ class RoleView(PrincipalMasterView):
""" """
model_class = model.Role model_class = model.Role
has_versions = True has_versions = True
touchable = True
grid_columns = [ grid_columns = [
'name', 'name',
'session_timeout', 'session_timeout',
'sync_me',
'node_type',
'notes', 'notes',
] ]
@ -63,6 +66,8 @@ class RoleView(PrincipalMasterView):
'name', 'name',
'session_timeout', 'session_timeout',
'notes', 'notes',
'sync_me',
'node_type',
'users', 'users',
'permissions', 'permissions',
] ]
@ -93,6 +98,11 @@ class RoleView(PrincipalMasterView):
We must prevent edit for certain built-in roles etc., depending on We must prevent edit for certain built-in roles etc., depending on
current user's permissions. 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 # only "root" can edit Administrator
if role is administrator_role(self.Session()): if role is administrator_role(self.Session()):
return self.request.is_root return self.request.is_root
@ -116,6 +126,11 @@ class RoleView(PrincipalMasterView):
""" """
We must prevent deletion for all built-in roles. 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()): if role is administrator_role(self.Session()):
return False return False
if role is authenticated_role(self.Session()): if role is authenticated_role(self.Session()):
@ -147,6 +162,27 @@ class RoleView(PrincipalMasterView):
# name # name
f.set_validator('name', self.unique_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 # notes
f.set_type('notes', 'text_wrapped') f.set_type('notes', 'text_wrapped')
@ -173,11 +209,6 @@ class RoleView(PrincipalMasterView):
elif self.deleting: elif self.deleting:
f.remove_field('permissions') 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): def render_users(self, role, field):
if role is guest_role(self.Session()): if role is guest_role(self.Session()):
@ -417,6 +448,7 @@ class RoleView(PrincipalMasterView):
route_prefix = cls.get_route_prefix() route_prefix = cls.get_route_prefix()
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_title = cls.get_model_title()
# extra permissions for editing built-in roles etc. # extra permissions for editing built-in roles etc.
config.add_tailbone_permission(permission_prefix, '{}.edit_authenticated'.format(permission_prefix), config.add_tailbone_permission(permission_prefix, '{}.edit_authenticated'.format(permission_prefix),
@ -425,6 +457,9 @@ class RoleView(PrincipalMasterView):
"Edit the \"Guest\" Role") "Edit the \"Guest\" Role")
config.add_tailbone_permission(permission_prefix, '{}.edit_my'.format(permission_prefix), config.add_tailbone_permission(permission_prefix, '{}.edit_my'.format(permission_prefix),
"Edit Role(s) to which current user belongs") "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 # download permissions matrix
config.add_tailbone_permission(permission_prefix, '{}.download_permissions_matrix'.format(permission_prefix), config.add_tailbone_permission(permission_prefix, '{}.download_permissions_matrix'.format(permission_prefix),

View file

@ -320,6 +320,14 @@ class UserView(PrincipalMasterView):
if self.request.is_root or uuid != admin.uuid: if self.request.is_root or uuid != admin.uuid:
user._roles.append(model.UserRole(role_uuid=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 # remove any roles which were *not* specified, although must take care
# not to remove admin role, unless acting as root # not to remove admin role, unless acting as root
for uuid in old_roles: for uuid in old_roles:
@ -328,6 +336,14 @@ class UserView(PrincipalMasterView):
role = self.Session.query(model.Role).get(uuid) role = self.Session.query(model.Role).get(uuid)
user.roles.remove(role) 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): def render_person(self, user, field):
person = user.person person = user.person
if not person: if not person: