ERP5UserManager.py 9.82 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
3 4
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights
# Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5
#
6 7 8 9 10 11 12
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this
# distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
13 14 15 16 17 18 19
#
##############################################################################
""" Classes: ERP5UserManager
"""

from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
20 21
from AccessControl.SecurityManagement import getSecurityManager,\
    setSecurityManager, newSecurityManager
Jean-Paul Smets's avatar
Jean-Paul Smets committed
22
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
Jérome Perrin's avatar
Jérome Perrin committed
23 24
from Products.PluggableAuthService.PluggableAuthService import \
    _SWALLOWABLE_PLUGIN_EXCEPTIONS
Jean-Paul Smets's avatar
Jean-Paul Smets committed
25 26 27 28 29
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
30
from ZODB.POSException import ConflictError
Vincent Pelletier's avatar
Vincent Pelletier committed
31
import sys
32
from DateTime import DateTime
33
from zLOG import LOG, PROBLEM
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34

Jérome Perrin's avatar
Jérome Perrin committed
35 36 37 38 39
try :
  from AccessControl.AuthEncoding import pw_validate
except ImportError:
  pw_validate = lambda reference, attempt: reference == attempt

40 41 42
# This user is used to bypass all security checks.
SUPER_USER = '__erp5security-=__'

Jean-Paul Smets's avatar
Jean-Paul Smets committed
43
manage_addERP5UserManagerForm = PageTemplateFile(
Jérome Perrin's avatar
Jérome Perrin committed
44 45
    'www/ERP5Security_addERP5UserManager', globals(),
    __name__='manage_addERP5UserManagerForm' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47

def addERP5UserManager(dispatcher, id, title=None, REQUEST=None):
Jérome Perrin's avatar
Jérome Perrin committed
48
    """ Add a ERP5UserManager to a Pluggable Auth Service. """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49 50 51 52 53 54 55 56 57 58 59

    eum = ERP5UserManager(id, title)
    dispatcher._setObject(eum.getId(), eum)

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(
                                '%s/manage_workspace'
                                '?manage_tabs_message='
                                'ERP5UserManager+added.'
                            % dispatcher.absolute_url())

60 61 62 63 64 65 66 67
class _AuthenticationFailure(Exception):
  """Raised when authentication failed, to prevent caching the fact that a user
  does not exist (yet), which happens when someone try to login before the user
  account is ready (like when the indexing not finished, an assignment not open
  etc...)
  """


Jean-Paul Smets's avatar
Jean-Paul Smets committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
class ERP5UserManager(BasePlugin):
    """ PAS plugin for managing users in ERP5
    """

    meta_type = 'ERP5 User Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

    #
    #   IAuthenticationPlugin implementation
    #
    security.declarePrivate( 'authenticateCredentials' )
    def authenticateCredentials(self, credentials):
        """ See IAuthenticationPlugin.
87

Jean-Paul Smets's avatar
Jean-Paul Smets committed
88 89 90
        o We expect the credentials to be those returned by
            ILoginPasswordExtractionPlugin.
        """
91 92 93 94
        # Forbidden the usage of the super user.
        if credentials.get('login') == SUPER_USER:
          return None

Jean-Paul Smets's avatar
Jean-Paul Smets committed
95
        def _authenticateCredentials(login, password, path):
96
            if not login or not password:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
97
                return None
98

99
            user_list = self.getUserByLogin(login)
100

Jean-Paul Smets's avatar
Jean-Paul Smets committed
101
            if not user_list:
102
              raise _AuthenticationFailure()
103

Jean-Paul Smets's avatar
Jean-Paul Smets committed
104
            user = user_list[0]
105

106
            sm = getSecurityManager()
107
            if sm.getUser().getId() != SUPER_USER:
108 109
              newSecurityManager(self, self.getUser(SUPER_USER))
            try:
110 111
              # get assignment
              assignment_list = [x for x in user.contentValues(portal_type="Assignment") if x.getValidationState() == "open"]
112 113 114 115 116 117 118 119 120 121 122 123
              valid_assignment_list = []
              # check dates if exist
              login_date = DateTime()
              for assignment in assignment_list:
                if assignment.getStartDate() is not None and \
                       assignment.getStartDate() > login_date:
                  continue
                if assignment.getStopDate() is not None and \
                       assignment.getStopDate() < login_date:
                  continue
                valid_assignment_list.append(assignment)
                
124
              if pw_validate(user.getPassword(), password) and \
125
                     len(valid_assignment_list): #user.getCareerRole() == 'internal':
126 127 128
                return login, login # use same for user_id and login
            finally:
              setSecurityManager(sm)
129
            raise _AuthenticationFailure()
130

Jérome Perrin's avatar
Jérome Perrin committed
131
        _authenticateCredentials = CachingMethod(_authenticateCredentials,
Aurel's avatar
Aurel committed
132
                                                 id='ERP5UserManager_authenticateCredentials',
Aurel's avatar
Aurel committed
133
                                                 cache_factory='erp5_content_short')
134 135
        try:
          return _authenticateCredentials(
Jérome Perrin's avatar
Jérome Perrin committed
136 137 138
                      login=credentials.get('login'),
                      password=credentials.get('password'),
                      path=self.getPhysicalPath())
139 140
        except _AuthenticationFailure:
          return None
141

Jean-Paul Smets's avatar
Jean-Paul Smets committed
142 143 144 145
    #
    #   IUserEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateUsers' )
Jérome Perrin's avatar
Jérome Perrin committed
146 147
    def enumerateUsers(self, id=None, login=None, exact_match=False,
                       sort_by=None, max_results=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
148
        """ See IUserEnumerationPlugin.
149
        """
150 151
        if id is None:
          id = login
152 153
        if isinstance(id, str):
          id = (id,)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
154
        if isinstance(id, list):
155
          id = tuple(id)
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

        user_info = []
        plugin_id = self.getId()

        id_list = []
        for user_id in id:
          if SUPER_USER == user_id:
            info = { 'id' : SUPER_USER
                    , 'login' : SUPER_USER
                    , 'pluginid' : plugin_id
                    }
            user_info.append(info)
          else:
            id_list.append(user_id)

        if id_list:
          for user in self.getUserByLogin(tuple(id_list), exact_match=exact_match):
              info = { 'id' : user.getReference()
                     , 'login' : user.getReference()
                     , 'pluginid' : plugin_id
                     }

              user_info.append(info)

        return tuple(user_info)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
182

183
    def getUserByLogin(self, login, exact_match=True):
184 185 186
        # 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)
187 188
        if not login:
          return []
189

190 191
        portal = self.getPortalObject()

192
        def _getUserByLogin(login, exact_match):
193 194
          # because we aren't logged in, we have to create our own
          # SecurityManager to be able to access the Catalog
195 196 197 198
          if isinstance(login, list):
            login = tuple(login)
          elif not isinstance(login, tuple):
            login = (str(login),)
199
          sm = getSecurityManager()
200
          if sm.getUser().getId() != SUPER_USER:
201 202
            newSecurityManager(self, self.getUser(SUPER_USER))
  
203
          try:
204
            try:
205 206 207 208 209
              if exact_match:
                reference_key = 'ExactMatch'
              else:
                reference_key = 'Keyword'

210
              result = portal.portal_catalog.unrestrictedSearchResults(
211 212 213 214
                                        select_expression='reference',
                                        portal_type="Person",
                                        reference=dict(query=login,
                                                       key=reference_key))
215 216 217 218
            except ConflictError:
              raise
            except:
              LOG('ERP5Security', PROBLEM, 'getUserByLogin failed', error=sys.exc_info())
219
              # Here we must raise an exception to prevent callers from caching
220 221 222 223 224 225 226 227
              # 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)
228 229 230 231 232 233 234 235 236 237 238 239 240
          # 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)
241 242
          # "bar OR foo" because of ZSQLCatalog tokenizing searched strings
          #  by default (feature).
243 244 245 246 247 248
          result = [x.path for x in result if (not exact_match)
                          or x['reference'] in login]
          if not result:
            raise _AuthenticationFailure()
          return result

249 250 251
        _getUserByLogin = CachingMethod(_getUserByLogin,
                                        id='ERP5UserManager_getUserByLogin',
                                        cache_factory='erp5_content_short')
252 253 254 255 256
        try:
          return [portal.unrestrictedTraverse(x) for x in
                              _getUserByLogin(login, exact_match)]
        except _AuthenticationFailure:
          return []
257

Jean-Paul Smets's avatar
Jean-Paul Smets committed
258 259 260 261 262 263
classImplements( ERP5UserManager
               , IAuthenticationPlugin
               , IUserEnumerationPlugin
               )

InitializeClass(ERP5UserManager)