ERP5UserManager.py 9.85 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

    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())

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.
79

Jean-Paul Smets's avatar
Jean-Paul Smets committed
80 81 82
        o We expect the credentials to be those returned by
            ILoginPasswordExtractionPlugin.
        """
83 84 85 86
        # Forbidden the usage of the super user.
        if credentials.get('login') == SUPER_USER:
          return None

Jean-Paul Smets's avatar
Jean-Paul Smets committed
87
        def _authenticateCredentials(login, password, path):
88
            if not login or not password:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
89
                return None
90

Jean-Paul Smets's avatar
Jean-Paul Smets committed
91
            user_list = self.getUserByLogin(login)
92

Jean-Paul Smets's avatar
Jean-Paul Smets committed
93 94
            if not user_list:
                return None
95

Jean-Paul Smets's avatar
Jean-Paul Smets committed
96
            user = user_list[0]
97

98
            sm = getSecurityManager()
99
            if sm.getUser().getId() != SUPER_USER:
100 101
              newSecurityManager(self, self.getUser(SUPER_USER))
            try:
102 103
              # get assignment
              assignment_list = [x for x in user.contentValues(portal_type="Assignment") if x.getValidationState() == "open"]
104 105 106 107 108 109 110 111 112 113 114 115
              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)
                
116
              if pw_validate(user.getPassword(), password) and \
117
                     len(valid_assignment_list): #user.getCareerRole() == 'internal':
118 119 120
                return login, login # use same for user_id and login
            finally:
              setSecurityManager(sm)
121

Jean-Paul Smets's avatar
Jean-Paul Smets committed
122
            return None
123

Jérome Perrin's avatar
Jérome Perrin committed
124
        _authenticateCredentials = CachingMethod(_authenticateCredentials,
Aurel's avatar
Aurel committed
125
                                                 id='ERP5UserManager_authenticateCredentials',
Aurel's avatar
Aurel committed
126
                                                 cache_factory='erp5_content_short')
Jérome Perrin's avatar
Jérome Perrin committed
127 128 129 130
        return _authenticateCredentials(
                      login=credentials.get('login'),
                      password=credentials.get('password'),
                      path=self.getPhysicalPath())
131

Jean-Paul Smets's avatar
Jean-Paul Smets committed
132 133 134 135
    #
    #   IUserEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateUsers' )
Jérome Perrin's avatar
Jérome Perrin committed
136 137
    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
138
        """ See IUserEnumerationPlugin.
139 140
        """
        def _enumerateUsers(id_tuple, exact_match, path):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
141 142
            user_info = []
            plugin_id = self.getId()
143

144 145 146 147 148 149 150 151 152 153 154 155 156
            id_list = []
            for id in id_tuple:
              if SUPER_USER == id:
                info = { 'id' : SUPER_USER
                        , 'login' : SUPER_USER
                        , 'pluginid' : plugin_id
                        }
                user_info.append(info)
              else:
                if exact_match:
                  id_list.append(id)
                else:
                  id_list.append('%%%s%%' % id)
157

158
            if id_list:
159
              for user in self.getUserByLogin(tuple(id_list)):
160
                  info = { 'id' : user.getReference()
161 162 163
                         , 'login' : user.getReference()
                         , 'pluginid' : plugin_id
                         }
164

165
                  user_info.append(info)
166

Jean-Paul Smets's avatar
Jean-Paul Smets committed
167
            return tuple(user_info)
168

Jérome Perrin's avatar
Jérome Perrin committed
169
        _enumerateUsers = CachingMethod(_enumerateUsers,
Aurel's avatar
Aurel committed
170
                                        id='ERP5UserManager_enumerateUsers',
Aurel's avatar
Aurel committed
171
                                        cache_factory='erp5_content_short')
172

173 174
        if id is None:
          id = login
175
        if isinstance(id, str):
176
          id = (id,)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
177
        if isinstance(id, list):
178
          id = tuple(id)
Jérome Perrin's avatar
Jérome Perrin committed
179 180 181
        return _enumerateUsers(id_tuple=id,
                               exact_match=exact_match,
                               path=self.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
182 183

    def getUserByLogin(self, login):
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 193 194 195
        def _getUserByLogin(login):
          # because we aren't logged in, we have to create our own
          # SecurityManager to be able to access the Catalog
          sm = getSecurityManager()
196
          if sm.getUser().getId() != SUPER_USER:
197 198
            newSecurityManager(self, self.getUser(SUPER_USER))
  
199
          try:
200
            try:
201
              result = portal.portal_catalog.unrestrictedSearchResults(
202
                                      select_expression='reference',
203 204 205 206 207
                                      portal_type="Person", reference=login)
            except ConflictError:
              raise
            except:
              LOG('ERP5Security', PROBLEM, 'getUserByLogin failed', error=sys.exc_info())
208
              # Here we must raise an exception to prevent callers from caching
209 210 211 212 213 214 215 216
              # 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)
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
          # 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)
          # " foo", "foo " and other padding variations because of
          # ZSQLCatalog (feature ?):
          #  (Pdb) print portal.portal_catalog.unrestrictedSearchResults(\
          #              portal_type="Person", reference='  foo  ', src__=1)
          #  SELECT DISTINCT
          #     catalog.path,   catalog.uid
          #  FROM
          #     catalog AS catalog
          #  WHERE
          #    1 = 1
          #    AND (((((catalog.portal_type = 'Person'))))) AND (((((catalog.reference = 'foo')))))
          #  LIMIT 1000
          # "bar OR foo" because of ZSQLCatalog tokenizing searched sgtrings
          # by default (feature).
          return [x.path for x in result if x['reference'] == login]
245 246 247 248
        _getUserByLogin = CachingMethod(_getUserByLogin,
                                        id='ERP5UserManager_getUserByLogin',
                                        cache_factory='erp5_content_short')
        result = _getUserByLogin(login)
249
        return [portal.unrestrictedTraverse(x) for x in result]
250

Jean-Paul Smets's avatar
Jean-Paul Smets committed
251 252 253 254 255 256
classImplements( ERP5UserManager
               , IAuthenticationPlugin
               , IUserEnumerationPlugin
               )

InitializeClass(ERP5UserManager)