Commit 240a8d26 authored by Vincent Pelletier's avatar Vincent Pelletier

{,Propertied}User: Reduce the overhead from Developer role processing.

getRoles is called a lot (on every restricted access, so hundreds of times
per transaction), it is definitely not the right place to do extra
computation, especially when their result does not change from one call
to the next (configuration should only change on process restart, so not
during a transaction - and even if it someday did, it should be fine to
wait for next transaction for it to take effect).
Instead, do the extra work when creating the user (typically once per
transaction).
Also, modernise python syntax (simplifications & style).
Also, reduce code duplication from ERP5Security.ERP5UserFactory.
parent e793b164
......@@ -19,7 +19,6 @@ from Products.ERP5Type.Globals import InitializeClass
from Acquisition import aq_inner, aq_parent
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from App.config import getConfiguration
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IUserFactoryPlugin
......@@ -52,151 +51,6 @@ class ERP5User(PropertiedUser):
_user_path = None
_login_path = None
def getRolesInContext( self, object ):
""" Return the list of roles assigned to the user.
For ERP5, we check if a _getAcquireLocalRoles is defined on the object.
"""
user_id = self.getId()
# [ x.getId() for x in self.getGroups() ]
group_ids = self.getGroups()
principal_ids = list( group_ids )
principal_ids.insert( 0, user_id )
local = {}
object = aq_inner( object )
while 1:
local_roles = getattr( object, '__ac_local_roles__', None )
if local_roles:
if callable( local_roles ):
local_roles = local_roles()
dict = local_roles or {}
for principal_id in principal_ids:
for role in dict.get( principal_id, [] ):
local[ role ] = 1
# patch by Klaus for LocalRole blocking
if getattr(object, '_getAcquireLocalRoles', None) is not None:
if not object._getAcquireLocalRoles():
break
inner = aq_inner( object )
parent = aq_parent( inner )
if parent is not None:
object = parent
continue
new = getattr( object, 'im_self', None )
if new is not None:
object = aq_inner( new )
continue
break
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
return list( self.getRoles() ) + local.keys()
def allowed( self, object, object_roles=None ):
""" Check whether the user has access to object.
As for getRolesInContext, we take into account _getAcquireLocalRoles for
ERP5.
"""
if self.getUserName() == ERP5Security.SUPER_USER:
# super user is allowed to accesss any object
return 1
if object_roles is _what_not_even_god_should_do:
return 0
# Short-circuit the common case of anonymous access.
if object_roles is None or 'Anonymous' in object_roles:
return 1
# Check for Developer Role, see patches.User for rationale
# XXX-arnau: copy/paste
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5')
if config and self.getId() in config.developer_list:
return 1
# Provide short-cut access if object is protected by 'Authenticated'
# role and user is not nobody
if 'Authenticated' in object_roles and (
self.getUserName() != 'Anonymous User'):
return 1
# Check for ancient role data up front, convert if found.
# This should almost never happen, and should probably be
# deprecated at some point.
if 'Shared' in object_roles:
object_roles = self._shared_roles(object)
if object_roles is None or 'Anonymous' in object_roles:
return 1
# Check for a role match with the normal roles given to
# the user, then with local roles only if necessary. We
# want to avoid as much overhead as possible.
user_roles = self.getRoles()
for role in object_roles:
if role in user_roles:
if self._check_context(object):
return 1
return None
# Still have not found a match, so check local roles. We do
# this manually rather than call getRolesInContext so that
# we can incur only the overhead required to find a match.
inner_obj = aq_inner( object )
user_id = self.getId()
# [ x.getId() for x in self.getGroups() ]
group_ids = self.getGroups()
principal_ids = list( group_ids )
principal_ids.insert( 0, user_id )
while 1:
local_roles = getattr( inner_obj, '__ac_local_roles__', None )
if local_roles:
if callable( local_roles ):
local_roles = local_roles()
dict = local_roles or {}
for principal_id in principal_ids:
local_roles = dict.get( principal_id, [] )
for role in object_roles:
if role in local_roles:
if self._check_context( object ):
return 1
return 0
# patch by Klaus for LocalRole blocking
if getattr(inner_obj, '_getAcquireLocalRoles', None) is not None:
if not inner_obj._getAcquireLocalRoles():
break
inner = aq_inner( inner_obj )
parent = aq_parent( inner )
if parent is not None:
inner_obj = parent
continue
new = getattr( inner_obj, 'im_self', None )
if new is not None:
inner_obj = aq_inner( new )
continue
break
return None
def getUserValue(self):
""" -> user document
......
......@@ -17,7 +17,7 @@
# Locale roles acquisition patch for PAS
from Acquisition import aq_inner, aq_parent
from App.config import getConfiguration
try:
from Products.PluggableAuthService.PropertiedUser import PropertiedUser
from Products.PluggableAuthService.PropertiedUser import\
......@@ -25,7 +25,10 @@ try:
except ImportError:
PropertiedUser = None
def getRolesInContext( self, object ):
TRUE_LAMBDA = lambda: True
DEVELOPER_ROLE_ID = 'Developer'
def getRolesInContext(self, object):
""" Return the list of roles assigned to the user.
......@@ -37,83 +40,33 @@ def getRolesInContext( self, object ):
o Ripped off from AccessControl.User.BasicUser, which provides
no other extension mechanism. :(
"""
user_id = self.getId()
# [ x.getId() for x in self.getGroups() ]
group_ids = self.getGroups()
principal_ids = list( group_ids )
principal_ids.insert( 0, user_id )
local ={}
object = aq_inner( object )
principal_id_list = [self.getId()]
principal_id_list.extend(self.getGroups())
result = set()
object = aq_inner(object)
while 1:
local_roles = getattr( object, '__ac_local_roles__', None )
if local_roles:
if callable( local_roles ):
local_roles = local_roles()
dict = local_roles or {}
for principal_id in principal_ids:
for role in dict.get( principal_id, [] ):
local[ role ] = 1
# patch by Klaus for LocalRole blocking
_getAcquireLocalRoles = getattr(object, '_getAcquireLocalRoles', None)
if _getAcquireLocalRoles is not None:
if not _getAcquireLocalRoles():
break
inner = aq_inner( object )
parent = aq_parent( inner )
local_role_dict = getattr(object, '__ac_local_roles__', None)
if local_role_dict:
if callable(local_role_dict):
local_role_dict = local_role_dict() or {}
for principal_id in principal_id_list:
result.update(local_role_dict.get(principal_id, ()))
if getattr(object, '_getAcquireLocalRoles', TRUE_LAMBDA)():
parent = aq_parent(aq_inner(object))
if parent is not None:
object = parent
continue
new = getattr( object, 'im_self', None )
new = getattr(object, '__self__', None)
if new is not None:
object = aq_inner( new )
object = aq_inner(new)
continue
break
# Patched: Developer role should never be available as local role
result.discard(DEVELOPER_ROLE_ID)
result.update(self.getRoles())
return list(result)
# Patched: Developer role should not never be available as local role
local.pop('Developer', None)
return list( self.getRoles() ) + local.keys()
from App.config import getConfiguration
def getRoles( self ):
""" -> [ role ]
o Include only "global" roles.
"""
role_tuple = self._roles.keys()
if role_tuple:
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5')
if config:
role_set = set(role_tuple)
user_id = self.getId()
if config and user_id in config.developer_list:
role_set.add('Developer')
elif user_id in role_set:
role_set.remove('Developer')
return role_set
return role_tuple
def allowed(self, object, object_roles=None ):
def allowed(self, object, object_roles=None):
""" Check whether the user has access to object.
o The user must have one of the roles in object_roles to allow access.
......@@ -131,16 +84,7 @@ def allowed(self, object, object_roles=None ):
if object_roles is None or 'Anonymous' in object_roles:
return 1
# Check for Developer Role, see patches.User for rationale
# XXX-arnau: copy/paste
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5')
if config and self.getId() in config.developer_list:
return 1
# Provide short-cut access if object is protected by 'Authenticated'
# role and user is not nobody
......@@ -156,75 +100,59 @@ def allowed(self, object, object_roles=None ):
if object_roles is None or 'Anonymous' in object_roles:
return 1
# Check for a role match with the normal roles given to
# the user, then with local roles only if necessary. We
# want to avoid as much overhead as possible.
user_roles = self.getRoles()
for role in object_roles:
if role in user_roles:
if self._check_context(object):
return 1
return None
# Still have not found a match, so check local roles. We do
# this manually rather than call getRolesInContext so that
# we can incur only the overhead required to find a match.
inner_obj = aq_inner( object )
user_id = self.getId()
# [ x.getId() for x in self.getGroups() ]
group_ids = self.getGroups()
principal_ids = list( group_ids )
principal_ids.insert( 0, user_id )
# Check global roles.
if object_roles.intersection(self.getRoles()):
return self._check_context(object)
# Do not match Developer as a local role.
object_roles.discard(DEVELOPER_ROLE_ID)
check_context = self._check_context
# Check local roles.
inner_obj = aq_inner(object)
principal_id_list = [self.getId()]
principal_id_list.extend(self.getGroups())
while 1:
local_roles = getattr( inner_obj, '__ac_local_roles__', None )
if local_roles:
if callable( local_roles ):
local_roles = local_roles()
dict = local_roles or {}
for principal_id in principal_ids:
local_roles = dict.get( principal_id, [] )
for role in object_roles:
if role in local_roles:
if self._check_context( object ):
return 1
return 0
# patch by Klaus for LocalRole blocking
_getAcquireLocalRoles = getattr(object, '_getAcquireLocalRoles', None)
if _getAcquireLocalRoles is not None:
if not _getAcquireLocalRoles():
break
inner = aq_inner( inner_obj )
parent = aq_parent( inner )
local_role_dict = getattr(inner_obj, '__ac_local_roles__', None)
if local_role_dict:
if callable(local_role_dict):
local_role_dict = local_role_dict() or {}
for principal_id in principal_id_list:
for role in object_roles.intersection(
local_role_dict.get(principal_id, ()),
):
return int(bool(check_context(object)))
if getattr(inner_obj, '_getAcquireLocalRoles', TRUE_LAMBDA)():
parent = aq_parent(aq_inner(inner_obj))
if parent is not None:
inner_obj = parent
continue
new = getattr( inner_obj, 'im_self', None )
new = getattr(inner_obj, '__self__', None)
if new is not None:
inner_obj = aq_inner( new )
inner_obj = aq_inner(new)
continue
break
return None
orig_PropertiedUser__init__ = getattr(PropertiedUser, '__init__', None)
def PropertiedUser__init__(self, id, login=None):
orig_PropertiedUser__init__(self, id, login)
if id in getattr(
getattr(
getConfiguration(),
'product_config',
{},
).get('erp5'),
'developer_list',
(),
):
self._roles[DEVELOPER_ROLE_ID] = 1
orig_PropertiedUser__addRoles = getattr(PropertiedUser, '_addRoles', None)
def PropertiedUser__addRoles(self, roles=()):
orig_PropertiedUser__addRoles(self, (x for x in roles if x != DEVELOPER_ROLE_ID))
if PropertiedUser is not None:
PropertiedUser.getRolesInContext = getRolesInContext
PropertiedUser.allowed = allowed
PropertiedUser.getRoles = getRoles
PropertiedUser.__init__ = PropertiedUser__init__
PropertiedUser._addRoles = PropertiedUser__addRoles
......@@ -13,7 +13,14 @@
#
##############################################################################
from AccessControl.User import BasicUser
from threading import local
from Acquisition import aq_inner, aq_parent
from AccessControl.PermissionRole import _what_not_even_god_should_do
from AccessControl.User import BasicUser, SimpleUser
from App.config import getConfiguration
from ..TransactionalVariable import TransactionalVariable
DEVELOPER_ROLE_ID = 'Developer'
BasicUser_allowed = BasicUser.allowed
def allowed(self, object, object_roles=None):
......@@ -22,81 +29,82 @@ def allowed(self, object, object_roles=None):
and remove it, as it should never be acquired anyhow, before calling the
original method
"""
# XXX-arnau: copy/paste (PropertiedUser)
if object_roles is not None:
object_roles = set(object_roles)
if 'Developer' in object_roles:
object_roles.remove('Developer')
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5')
if config and self.getId() in config.developer_list:
# Skip "self._check_context(object)"
if (
object_roles is not _what_not_even_god_should_do and
object_roles is not None and
DEVELOPER_ROLE_ID in set(object_roles or ()).intersection(self.getRoles())
):
return 1
return BasicUser_allowed(self, object, object_roles)
BasicUser.allowed = allowed
from App.config import getConfiguration
from AccessControl.User import SimpleUser
SimpleUser_getRoles = SimpleUser.getRoles
def getRoles(self):
def getRoles(self, _transactional_variable_pool=local()):
"""
Add Developer Role if the user has been explicitely set as Developer in Zope
configuration file
"""
role_tuple = SimpleUser_getRoles(self)
if role_tuple:
product_config = getattr(getConfiguration(), 'product_config', None)
if product_config:
config = product_config.get('erp5')
if config:
role_set = set(role_tuple)
user_id = self.getId()
if config and user_id in config.developer_list:
role_set.add('Developer')
elif user_id in role_set:
role_set.remove('Developer')
return role_set
return role_tuple
role_tuple = tuple(
x
for x in SimpleUser_getRoles(self)
if x != DEVELOPER_ROLE_ID
)
# Use our private transactional cache pool, to avoid code meddling with
# roles. Hide it in a default parameter value to make it harder to access
# than just importing it from the module.
try:
tv = _transactional_variable_pool.instance
except AttributeError:
tv = TransactionalVariable()
_transactional_variable_pool.instance = tv
try:
extra_role_tuple = tv['user_extra_role_tuple']
except KeyError:
tv['user_extra_role_tuple'] = extra_role_tuple = (
(DEVELOPER_ROLE_ID, )
if self.getId() in getattr(
getattr(
getConfiguration(),
'product_config',
{},
).get('erp5'),
'developer_list',
(),
) else
()
)
return role_tuple + extra_role_tuple
SimpleUser.getRoles = getRoles
SimpleUser_getRolesInContext = SimpleUser.getRolesInContext
def getRolesInContext(self, object):
"""
Return the list of roles assigned to the user, including local roles
assigned in context of the passed in object.
"""
userid=self.getId()
roles=self.getRoles()
local={}
object=getattr(object, 'aq_inner', object)
userid = self.getId()
result = set()
object = aq_inner(object)
while 1:
local_roles = getattr(object, '__ac_local_roles__', None)
if local_roles:
if callable(local_roles):
local_roles=local_roles()
dict=local_roles or {}
for r in dict.get(userid, []):
local[r]=1
inner = getattr(object, 'aq_inner', object)
parent = getattr(inner, '__parent__', None)
local_role_dict = getattr(object, '__ac_local_roles__', None)
if local_role_dict:
if callable(local_role_dict):
local_role_dict = local_role_dict() or {}
result.update(local_role_dict.get(userid, ()))
parent = aq_parent(aq_inner(object))
if parent is not None:
object = parent
continue
if hasattr(object, 'im_self'):
object=object.im_self
object=getattr(object, 'aq_inner', object)
new = getattr(object, '__self__', None)
if new is not None:
object = aq_inner(new)
continue
break
# Patched: Developer role should never be available as local role
local.pop('Developer', None)
roles=list(roles) + local.keys()
return roles
result.discard(DEVELOPER_ROLE_ID)
result.update(self.getRoles())
return list(result)
SimpleUser.getRolesInContext = getRolesInContext
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment