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