{,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.
... | @@ -13,7 +13,14 @@ | ... | @@ -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 | BasicUser_allowed = BasicUser.allowed | ||
def allowed(self, object, object_roles=None): | def allowed(self, object, object_roles=None): | ||
... | @@ -22,81 +29,82 @@ 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 | and remove it, as it should never be acquired anyhow, before calling the | ||
original method | original method | ||
""" | """ | ||
# XXX-arnau: copy/paste (PropertiedUser) | # Skip "self._check_context(object)" | ||
if object_roles is not None: | if ( | ||
object_roles = set(object_roles) | object_roles is not _what_not_even_god_should_do and | ||
if 'Developer' in object_roles: | object_roles is not None and | ||
object_roles.remove('Developer') | DEVELOPER_ROLE_ID in set(object_roles or ()).intersection(self.getRoles()) | ||
product_config = getattr(getConfiguration(), 'product_config', None) | ): | ||
if product_config: | return 1 | ||
config = product_config.get('erp5') | |||
if config and self.getId() in config.developer_list: | |||
return 1 | |||
return BasicUser_allowed(self, object, object_roles) | return BasicUser_allowed(self, object, object_roles) | ||
BasicUser.allowed = allowed | BasicUser.allowed = allowed | ||
from App.config import getConfiguration | |||
from AccessControl.User import SimpleUser | |||
SimpleUser_getRoles = SimpleUser.getRoles | 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 | Add Developer Role if the user has been explicitely set as Developer in Zope | ||
configuration file | configuration file | ||
""" | """ | ||
role_tuple = SimpleUser_getRoles(self) | role_tuple = tuple( | ||
if role_tuple: | x | ||
product_config = getattr(getConfiguration(), 'product_config', None) | for x in SimpleUser_getRoles(self) | ||
if product_config: | if x != DEVELOPER_ROLE_ID | ||
config = product_config.get('erp5') | ) | ||
if config: | # Use our private transactional cache pool, to avoid code meddling with | ||
role_set = set(role_tuple) | # roles. Hide it in a default parameter value to make it harder to access | ||
user_id = self.getId() | # than just importing it from the module. | ||
if config and user_id in config.developer_list: | try: | ||
role_set.add('Developer') | tv = _transactional_variable_pool.instance | ||
elif user_id in role_set: | except AttributeError: | ||
role_set.remove('Developer') | tv = TransactionalVariable() | ||
_transactional_variable_pool.instance = tv | |||
return role_set | try: | ||
extra_role_tuple = tv['user_extra_role_tuple'] | |||
return 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.getRoles = getRoles | ||
SimpleUser_getRolesInContext = SimpleUser.getRolesInContext | |||
def getRolesInContext(self, object): | def getRolesInContext(self, object): | ||
""" | """ | ||
Return the list of roles assigned to the user, including local roles | Return the list of roles assigned to the user, including local roles | ||
assigned in context of the passed in object. | assigned in context of the passed in object. | ||
""" | """ | ||
userid=self.getId() | userid = self.getId() | ||
roles=self.getRoles() | result = set() | ||
local={} | object = aq_inner(object) | ||
object=getattr(object, 'aq_inner', object) | |||
while 1: | while 1: | ||
local_roles = getattr(object, '__ac_local_roles__', None) | local_role_dict = getattr(object, '__ac_local_roles__', None) | ||
if local_roles: | if local_role_dict: | ||
if callable(local_roles): | if callable(local_role_dict): | ||
local_roles=local_roles() | local_role_dict = local_role_dict() or {} | ||
dict=local_roles or {} | result.update(local_role_dict.get(userid, ())) | ||
for r in dict.get(userid, []): | parent = aq_parent(aq_inner(object)) | ||
local[r]=1 | |||
inner = getattr(object, 'aq_inner', object) | |||
parent = getattr(inner, '__parent__', None) | |||
if parent is not None: | if parent is not None: | ||
object = parent | object = parent | ||
continue | continue | ||
if hasattr(object, 'im_self'): | new = getattr(object, '__self__', None) | ||
object=object.im_self | if new is not None: | ||
object=getattr(object, 'aq_inner', object) | object = aq_inner(new) | ||
continue | continue | ||
break | break | ||
# Patched: Developer role should never be available as local role | # Patched: Developer role should never be available as local role | ||
local.pop('Developer', None) | result.discard(DEVELOPER_ROLE_ID) | ||
roles=list(roles) + local.keys() | result.update(self.getRoles()) | ||
|
|||
return roles | return list(result) | ||
SimpleUser.getRolesInContext = getRolesInContext | SimpleUser.getRolesInContext = getRolesInContext |