ERP5GroupManager.py 7.58 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
#
##############################################################################
""" Classes: ERP5GroupManager
"""

18
from Products.ERP5Type.Globals import InitializeClass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
19 20 21 22 23 24
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IGroupsPlugin
from Products.ERP5Type.Cache import CachingMethod
25 26
from Products.ERP5Type.ERP5Type \
  import ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT
27
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
28
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
29
from ZODB.POSException import ConflictError
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30

31
import sys
Alexandre Boeglin's avatar
Alexandre Boeglin committed
32

33
from zLOG import LOG, WARNING
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34

35 36
from ERP5UserManager import SUPER_USER

37 38 39 40 41 42 43 44
# It can be useful to set NO_CACHE_MODE to 1 in order to debug
# complex security issues related to caching groups. For example,
# the use of scripts instead of external methods for
# assignment category lookup may make the system unstable and
# hard to debug. Setting NO_CACHE_MODE allows to debug such
# issues.
NO_CACHE_MODE = 0

45 46
class ConsistencyError(Exception): pass

Jean-Paul Smets's avatar
Jean-Paul Smets committed
47
manage_addERP5GroupManagerForm = PageTemplateFile(
Jérome Perrin's avatar
Jérome Perrin committed
48 49
    'www/ERP5Security_addERP5GroupManager', globals(),
    __name__='manage_addERP5GroupManagerForm' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
50 51

def addERP5GroupManager( dispatcher, id, title=None, REQUEST=None ):
Alexandre Boeglin's avatar
Alexandre Boeglin committed
52
  """ Add a ERP5GroupManager to a Pluggable Auth Service. """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
53

Alexandre Boeglin's avatar
Alexandre Boeglin committed
54 55
  egm = ERP5GroupManager(id, title)
  dispatcher._setObject(egm.getId(), egm)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56

Alexandre Boeglin's avatar
Alexandre Boeglin committed
57 58 59 60 61 62
  if REQUEST is not None:
    REQUEST['RESPONSE'].redirect(
                              '%s/manage_workspace'
                              '?manage_tabs_message='
                              'ERP5GroupManager+added.'
                          % dispatcher.absolute_url())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65

class ERP5GroupManager(BasePlugin):

Alexandre Boeglin's avatar
Alexandre Boeglin committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
  """ PAS plugin for dynamically adding Groups
  based on Assignments in ERP5
  """
  meta_type = 'ERP5 Group Manager'

  security = ClassSecurityInfo()

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

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

  #
  #   IGroupsPlugin implementation
  #
81
  security.declarePrivate('getGroupsForPrincipal')
Alexandre Boeglin's avatar
Alexandre Boeglin committed
82 83
  def getGroupsForPrincipal(self, principal, request=None):
    """ See IGroupsPlugin.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
84
    """
85 86 87 88
    # If this is the super user, skip the check.
    if principal.getId() == SUPER_USER:
      return ()

89
    @UnrestrictedMethod
Alexandre Boeglin's avatar
Alexandre Boeglin committed
90 91 92 93
    def _getGroupsForPrincipal(user_name, path):
      security_category_dict = {} # key is the base_category_list,
                                  # value is the list of fetched categories
      security_group_list = []
94
      security_definition_list = ()
Alexandre Boeglin's avatar
Alexandre Boeglin committed
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
      try:
        # To get the complete list of groups, we try to call the
        # ERP5Type_getSecurityCategoryMapping which should return a list
        # of lists of two elements (script, base_category_list) like :
        # (
        #   ('script_1', ['base_category_1', 'base_category_2', ...]),
        #   ('script_2', ['base_category_1', 'base_category_3', ...])
        # )
        #
        # else, if the script does not exist, falls back to a list containng
        # only one list :
        # (('ERP5Type_getSecurityCategoryFromAssignment',
        #   self.getPortalAssignmentBaseCategoryList() ),)

        mapping_method = getattr(self,
            'ERP5Type_getSecurityCategoryMapping', None)
        if mapping_method is None:
          security_definition_list = ((
              'ERP5Type_getSecurityCategoryFromAssignment',
              self.getPortalAssignmentBaseCategoryList()
          ),)
        else:
          security_definition_list = mapping_method()

120 121
        # get the person from its reference - no security check needed
        catalog_result = self.portal_catalog.unrestrictedSearchResults(
122
            portal_type="Person", query=SimpleQuery(reference=user_name))
123 124 125 126 127 128 129 130 131 132
        if len(catalog_result) != 1: # we won't proceed with groups
          if len(catalog_result) > 1: # configuration is screwed
            raise ConsistencyError, 'There is more than one Person whose \
                login is %s : %s' % (user_name,
                repr([r.getObject() for r in catalog_result]))
          else: # no person is linked to this user login
            return ()
        person_object = catalog_result[0].getObject()

        # Fetch category values from defined scripts
133
        for (method_name, base_category_list) in security_definition_list:
134 135 136 137 138
          base_category_list = tuple(base_category_list)
          method = getattr(self, method_name)
          security_category_list = security_category_dict.setdefault(
                                            base_category_list, [])
          try:
139 140 141 142
            # The called script may want to distinguish if it is called
            # from here or from _updateLocalRolesOnSecurityGroups.
            # Currently, passing portal_type='' (instead of 'Person')
            # is the only way to make the difference.
143 144 145 146 147 148 149 150 151 152 153
            security_category_list.extend(
              method(base_category_list, user_name, person_object, '')
            )
          except ConflictError:
            raise
          except:
            LOG('ERP5GroupManager', WARNING,
                'could not get security categories from %s' % (method_name,),
                error = sys.exc_info())

        # Get group names from category values
154 155 156
        # XXX try ERP5Type_asSecurityGroupIdList first for compatibility
        generator_name = 'ERP5Type_asSecurityGroupIdList'
        group_id_list_generator = getattr(self, generator_name, None)
157
        if group_id_list_generator is None:
158 159
          generator_name = ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT
          group_id_list_generator = getattr(self, generator_name)
160
        for base_category_list, category_value_list in \
161
            security_category_dict.iteritems():
162 163
          for category_dict in category_value_list:
            try:
164 165
              group_id_list = group_id_list_generator(
                                        category_order=base_category_list,
166
                                        **category_dict)
167 168
              if isinstance(group_id_list, str):
                group_id_list = [group_id_list]
169 170 171 172 173 174 175 176
              security_group_list.extend(group_id_list)
            except ConflictError:
              raise
            except:
              LOG('ERP5GroupManager', WARNING,
                  'could not get security groups from %s' %
                  generator_name,
                  error = sys.exc_info())
177
      finally:
178
        pass
Alexandre Boeglin's avatar
Alexandre Boeglin committed
179 180
      return tuple(security_group_list)

181 182
    if not NO_CACHE_MODE:
      _getGroupsForPrincipal = CachingMethod(_getGroupsForPrincipal,
Aurel's avatar
Aurel committed
183
                                             id='ERP5GroupManager_getGroupsForPrincipal',
Aurel's avatar
Aurel committed
184
                                             cache_factory='erp5_content_short')
185

Romain Courteaud's avatar
Romain Courteaud committed
186
    return _getGroupsForPrincipal(
Jérome Perrin's avatar
Jérome Perrin committed
187 188
                user_name=principal.getId(),
                path=self.getPhysicalPath())
Romain Courteaud's avatar
Romain Courteaud committed
189

Alexandre Boeglin's avatar
Alexandre Boeglin committed
190

Jean-Paul Smets's avatar
Jean-Paul Smets committed
191 192 193 194 195
classImplements( ERP5GroupManager
               , IGroupsPlugin
               )

InitializeClass(ERP5GroupManager)