From bbd0f245c9b36d8db49b4c4ec506806a056625f6 Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Mon, 12 Jul 2010 17:06:13 +0000 Subject: [PATCH] Reimplement ERP5Site_getAuthenticatedMemberPersonValue without using acl_users.erp5_users Because some sites don't have an 'erp5_users' plugin in acl_users, and what ERP5Site_getAuthenticatedMemberPersonValue does is not specific to 'erp5_users'. This is done by moving code outside ERP5UserManager class so that it can be reused. Changes to ERP5UserManager.getUserLogin method are: - use a transactional cache instead of erp5_content_short: - a transactional cache is enough because authenticateCredentials already caches its result in erp5_content_short - no need to care about empty result from the catalog, which was done using _AuthenticationFailure (instead, we simply return an empty list) - cache person objects instead of their path - no need to use SUPER_USER since we use unrestrictedSearchResults git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@37065 20353a03-c40f-0410-a6d1-a30d3c3de9de --- .../erp5_base/Base_getOwnerTitle.xml | 9 +- ...Site_getAuthenticatedMemberPersonValue.xml | 19 +-- bt5/erp5_base/bt/revision | 2 +- product/ERP5Security/ERP5UserManager.py | 117 +++++++----------- product/ERP5Security/__init__.py | 3 + 5 files changed, 67 insertions(+), 83 deletions(-) diff --git a/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/Base_getOwnerTitle.xml b/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/Base_getOwnerTitle.xml index 20915129f1..96df0b328d 100644 --- a/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/Base_getOwnerTitle.xml +++ b/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/Base_getOwnerTitle.xml @@ -57,11 +57,10 @@ """\n owner_id_list = [i[0] for i in context.get_local_roles() if \'Owner\' in i[1]]\n if owner_id_list:\n - found_user_list = context.acl_users.erp5_users.getUserByLogin(owner_id_list[0])\n + from Products.ERP5Security.ERP5UserManager import getUserByLogin\n + found_user_list = getUserByLogin(context.getPortalObject(), tuple(owner_id_list))\n if found_user_list:\n return found_user_list[0].getTitle()\n -\n -return owner\n </string> </value> </item> <item> @@ -106,8 +105,10 @@ return owner\n <string>i</string> <string>_getitem_</string> <string>owner_id_list</string> + <string>Products.ERP5Security.ERP5UserManager</string> + <string>getUserByLogin</string> + <string>tuple</string> <string>found_user_list</string> - <string>owner</string> </tuple> </value> </item> diff --git a/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/ERP5Site_getAuthenticatedMemberPersonValue.xml b/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/ERP5Site_getAuthenticatedMemberPersonValue.xml index 8740a68a64..496b0f856b 100644 --- a/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/ERP5Site_getAuthenticatedMemberPersonValue.xml +++ b/bt5/erp5_base/SkinTemplateItem/portal_skins/erp5_base/ERP5Site_getAuthenticatedMemberPersonValue.xml @@ -56,15 +56,14 @@ <value> <string>"""Find and returns Person object for current logged in user.\n Returns None if no corresponding person, for example when not using ERP5Security.ERP5UserManager.\n """\n +portal = context.getPortalObject()\n if user_name is None:\n - user_name = context.portal_membership.getAuthenticatedMember()\n + user_name = portal.portal_membership.getAuthenticatedMember()\n \n -found_user_list = context.acl_users.erp5_users.getUserByLogin(str(user_name))\n -found_users = len(found_user_list)\n -if found_users != 1:\n - return None\n -\n -return found_user_list[0]\n +from Products.ERP5Security.ERP5UserManager import getUserByLogin\n +found_user_list = getUserByLogin(portal, str(user_name))\n +if len(found_user_list) == 1:\n + return found_user_list[0]\n </string> </value> </item> <item> @@ -110,13 +109,15 @@ return found_user_list[0]\n <value> <tuple> <string>user_name</string> - <string>None</string> <string>_getattr_</string> <string>context</string> + <string>portal</string> + <string>None</string> + <string>Products.ERP5Security.ERP5UserManager</string> + <string>getUserByLogin</string> <string>str</string> <string>found_user_list</string> <string>len</string> - <string>found_users</string> <string>_getitem_</string> </tuple> </value> diff --git a/bt5/erp5_base/bt/revision b/bt5/erp5_base/bt/revision index f0b2b124d1..c31da8b3c4 100644 --- a/bt5/erp5_base/bt/revision +++ b/bt5/erp5_base/bt/revision @@ -1 +1 @@ -803 \ No newline at end of file +804 \ No newline at end of file diff --git a/product/ERP5Security/ERP5UserManager.py b/product/ERP5Security/ERP5UserManager.py index 2799cbe2ee..6a0e58ef87 100644 --- a/product/ERP5Security/ERP5UserManager.py +++ b/product/ERP5Security/ERP5UserManager.py @@ -26,7 +26,7 @@ from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PluggableAuthService.utils import classImplements from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin -from Products.ERP5Type.Cache import CachingMethod +from Products.ERP5Type.Cache import CachingMethod, transactional_cached from ZODB.POSException import ConflictError import sys from DateTime import DateTime @@ -64,6 +64,36 @@ class _AuthenticationFailure(Exception): etc...) """ +@transactional_cached(lambda portal, *args: args) +def getUserByLogin(portal, login, exact_match=True): + if isinstance(login, basestring): + login = login, + if exact_match: + reference_key = 'ExactMatch' + else: + reference_key = 'Keyword' + result = portal.portal_catalog.unrestrictedSearchResults( + select_expression='reference', + portal_type="Person", + reference=dict(query=login, key=reference_key)) + # XXX: Here, we filter catalog result list ALTHOUGH we did pass + # parameters to unrestrictedSearchResults to restrict result set. + # This is done because the following values can match person with + # reference "foo": + # "foo " because of MySQL (feature, PADSPACE collation): + # mysql> SELECT reference as r FROM catalog + # -> WHERE reference="foo "; + # +-----+ + # | r | + # +-----+ + # | foo | + # +-----+ + # 1 row in set (0.01 sec) + # "bar OR foo" because of ZSQLCatalog tokenizing searched strings + # by default (feature). + return [x.getObject() for x in result if not exact_match + or x['reference'] in login] + class ERP5UserManager(BasePlugin): """ PAS plugin for managing users in ERP5 @@ -180,81 +210,30 @@ class ERP5UserManager(BasePlugin): return tuple(user_info) - def getUserByLogin(self, login, exact_match=True): # Search the Catalog for login and return a list of person objects # login can be a string or a list of strings # (no docstring to prevent publishing) if not login: return [] - - portal = self.getPortalObject() - - def _getUserByLogin(login, exact_match): - # because we aren't logged in, we have to create our own - # SecurityManager to be able to access the Catalog - if isinstance(login, list): - login = tuple(login) - elif not isinstance(login, tuple): - login = (str(login),) - sm = getSecurityManager() - if sm.getUser().getId() != SUPER_USER: - newSecurityManager(self, self.getUser(SUPER_USER)) - - try: - try: - if exact_match: - reference_key = 'ExactMatch' - else: - reference_key = 'Keyword' - - result = portal.portal_catalog.unrestrictedSearchResults( - select_expression='reference', - portal_type="Person", - reference=dict(query=login, - key=reference_key)) - except ConflictError: - raise - except: - LOG('ERP5Security', PROBLEM, 'getUserByLogin failed', error=sys.exc_info()) - # Here we must raise an exception to prevent callers from caching - # a result of a degraded situation. - # The kind of exception does not matter as long as it's catched by - # PAS and causes a lookup using another plugin or user folder. - # As PAS does not define explicitely such exception, we must use - # the _SWALLOWABLE_PLUGIN_EXCEPTIONS list. - raise _SWALLOWABLE_PLUGIN_EXCEPTIONS[0] - finally: - setSecurityManager(sm) - # XXX: Here, we filter catalog result list ALTHOUGH we did pass - # parameters to unrestrictedSearchResults to restrict result set. - # This is done because the following values can match person with - # reference "foo": - # "foo " because of MySQL (feature, PADSPACE collation): - # mysql> SELECT reference as r FROM catalog - # -> WHERE reference="foo "; - # +-----+ - # | r | - # +-----+ - # | foo | - # +-----+ - # 1 row in set (0.01 sec) - # "bar OR foo" because of ZSQLCatalog tokenizing searched strings - # by default (feature). - result = [x.path for x in result if (not exact_match) - or x['reference'] in login] - if not result: - raise _AuthenticationFailure() - return result - - _getUserByLogin = CachingMethod(_getUserByLogin, - id='ERP5UserManager_getUserByLogin', - cache_factory='erp5_content_short') + if isinstance(login, list): + login = tuple(login) + elif not isinstance(login, tuple): + login = str(login) try: - return [portal.unrestrictedTraverse(x) for x in - _getUserByLogin(login, exact_match)] - except _AuthenticationFailure: - return [] + return getUserByLogin(self.getPortalObject(), login, exact_match) + except ConflictError: + raise + except: + LOG('ERP5Security', PROBLEM, 'getUserByLogin failed', error=sys.exc_info()) + # Here we must raise an exception to prevent callers from caching + # a result of a degraded situation. + # The kind of exception does not matter as long as it's catched by + # PAS and causes a lookup using another plugin or user folder. + # As PAS does not define explicitely such exception, we must use + # the _SWALLOWABLE_PLUGIN_EXCEPTIONS list. + raise _SWALLOWABLE_PLUGIN_EXCEPTIONS[0] + classImplements( ERP5UserManager , IAuthenticationPlugin diff --git a/product/ERP5Security/__init__.py b/product/ERP5Security/__init__.py index 13a5069536..e441b489eb 100644 --- a/product/ERP5Security/__init__.py +++ b/product/ERP5Security/__init__.py @@ -108,3 +108,6 @@ def initialize(context): , icon='www/portal.gif' ) +from AccessControl.SecurityInfo import ModuleSecurityInfo +ModuleSecurityInfo('Products.ERP5Security.ERP5UserManager').declarePublic( + 'getUserByLogin') -- 2.30.9