diff --git a/product/ERP5Catalog/CatalogTool.py b/product/ERP5Catalog/CatalogTool.py index 40bcce34c4673fd632066bcc838c62a92f4a0658..be9042f0b8b5152429e7c28e4f7d29e4edaa0d32 100644 --- a/product/ERP5Catalog/CatalogTool.py +++ b/product/ERP5Catalog/CatalogTool.py @@ -28,6 +28,7 @@ from Products.CMFCore.CatalogTool import CatalogTool as CMFCoreCatalogTool from Products.ZSQLCatalog.ZSQLCatalog import ZCatalog +from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery from Products.CMFCore import CMFCorePermissions from AccessControl import ClassSecurityInfo, getSecurityManager from Products.CMFCore.CatalogTool import IndexableObjectWrapper as CMFCoreIndexableObjectWrapper @@ -70,6 +71,8 @@ try: except ImportError: pass +DEFAULT_RESULT_LIMIT = 1000 + def getSecurityProduct(acl_users): """returns the security used by the user folder passed. (NuxUserGroup, ERP5Security, or None if anything else). @@ -139,7 +142,7 @@ class IndexableObjectWrapper(CMFCoreIndexableObjectWrapper): # trying to reduce the number of security definitions # However, this could be a bad idea if we start to use Owner role # as a kind of Assignee and if we need it for worklists. - if role != 'Owner': + if role != 'Owner': if withnuxgroups: allowed[user + ':' + role] = 1 else: @@ -211,14 +214,14 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): product_path = package_home(globals()) zsql_dirs = [] - # Common methods + # Common methods - for backward compatibility + # SQL code distribution is supposed to be business template based nowadays if config_id.lower() == 'erp5_mysql': zsql_dirs.append(os.path.join(product_path, 'sql', 'common_mysql')) zsql_dirs.append(os.path.join(product_path, 'sql', 'erp5_mysql')) elif config_id.lower() == 'cps3_mysql': zsql_dirs.append(os.path.join(product_path, 'sql', 'common_mysql')) zsql_dirs.append(os.path.join(product_path, 'sql', 'cps3_mysql')) - # XXX TODO : add other cases # Iterate over the sql directory. Add all sql methods in that directory. for directory in zsql_dirs: @@ -237,7 +240,7 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): # Make this the default. self.default_sql_catalog_id = config_id - + security.declareProtected( 'Import/Export objects', 'exportSQLMethods' ) def exportSQLMethods(self, sql_catalog_id=None, config_id='erp5'): """ @@ -258,7 +261,7 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): 'z_create_record', 'z_related_security', 'z_delete_recorded_object_list', 'z_reserve_uid', 'z_getitem_by_path', 'z_show_columns', 'z_getitem_by_path', 'z_show_tables', 'z_getitem_by_uid', 'z_unique_values', 'z_produce_reserved_uid_list',) - + msg = '' for id in catalog.objectIds(spec=('Z SQL Method',)): if id in common_sql_list: @@ -275,7 +278,7 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): f.write(text) finally: f.close() - + properties = self.manage_catalogExportProperties(sql_catalog_id=sql_catalog_id) name = os.path.join(config_sql_dir, 'properties.xml') msg += 'Writing %s\n' % (name,) @@ -284,9 +287,9 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): f.write(properties) finally: f.close() - + return msg - + def _listAllowedRolesAndUsers(self, user): security_product = getSecurityProduct(self.acl_users) if security_product == SECURITY_USING_PAS: @@ -394,12 +397,15 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): This is supposed to be used with Z SQL Methods to check permissions when you list up documents. It is also able to take into account - a parameter named local_roles so that list documents only include + a parameter named local_roles so that listed documents only include those documents for which the user (or the group) was associated one of the given local roles. + + XXX allowedRolesAndUsers naming is wrong """ user = _getAuthenticatedUser(self) allowedRolesAndUsers = self._listAllowedRolesAndUsers(user) + role_column_dict = {} # Patch for ERP5 by JP Smets in order # to implement worklists and search of local roles @@ -415,52 +421,84 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): # Local roles now has precedence (since it comes from a WorkList) for user_or_group in allowedRolesAndUsers: for role in local_roles: - if role == "Owner": - # This is for now only a placeholder to handle the case of Owner - # which may not be supported (see above comment arround line 135 - new_allowedRolesAndUsers.append('%s:%s' % (user_or_group, role)) + # Performance optimisation + lower_role = role.lower() + if self.getSQLCatalog().getColumnMap().has_key(lower_role): + # If a given role exists as a column in the catalog, + # then it is considered as single valued and indexed + # through the catalog. + role_column_dict[lower_role] = user # XXX This should be a list + # which also includes all user groups else: + # Else, we use the standard approach new_allowedRolesAndUsers.append('%s:%s' % (user_or_group, role)) allowedRolesAndUsers = new_allowedRolesAndUsers - return allowedRolesAndUsers + return allowedRolesAndUsers, role_column_dict - security.declarePrivate('getSecurityUid') - def getSecurityUid(self, **kw): + security.declarePrivate('getSecurityQuery') + def getSecurityQuery(self, query=None, **kw): """ - Return list of security oid for given roles list + Build a query based on allowed roles (DEPRECATED) + or on a list of security_uid values. The query takes into + account the fact that some roles are catalogued with columns. """ + allowedRolesAndUsers, role_column_dict = self.getAllowedRolesAndUsers(**kw) catalog = self.getSQLCatalog() method = getattr(catalog, catalog.sql_search_security, '') + original_query = query if method in ('', None): # XXX old way, should not be used anylonger warnings.warn("The usage of allowedRolesAndUsers is deprecated.\n" "Please update your business template erp5_mysql_innodb.", DeprecationWarning) - kw['allowedRolesAndUsers'] = self.getAllowedRolesAndUsers(**kw) + if role_column_dict: + query_list = [] + for key, value in role_column_dict.items(): + new_query = Query(key=value) + query_list.append(new_query) + operator_kw = {'operator': 'AND'} + query = ComplexQuery(*query_list, **operator_kw) + if allowedRolesAndUsers: + query = ComplexQuery(Query(allowedRolesAndUsers=allowedRolesAndUsers), + query, operator='OR') + else: + query = Query(allowedRolesAndUsers=allowedRolesAndUsers) else: - allowedRolesAndUsers = ["'%s'" % (role, ) for role in self.getAllowedRolesAndUsers(**kw)] - security_uid_list = [x.uid for x in method(security_roles_list = allowedRolesAndUsers)] - kw['security_uid'] = security_uid_list - return kw + if allowedRolesAndUsers: + allowedRolesAndUsers = ["'%s'" % (role, ) for role in allowedRolesAndUsers] + security_uid_list = [x.uid for x in method(security_roles_list = allowedRolesAndUsers)] + if role_column_dict: + query_list = [] + for key, value in role_column_dict.items(): + new_query = Query(key=value) + query_list.append(new_query) + operator_kw = {'operator': 'AND'} + query = ComplexQuery(*query_list, **operator_kw) + if allowedRolesAndUsers: + query = ComplexQuery(Query(security_uid=security_uid_list), + query, operator='OR') + else: + query = Query(security_uid=security_uid_list) + if original_query is not None: + query = ComplexQuery(query, original_query, operator='AND') + return query # searchResults has inherited security assertions. - def searchResults(self, REQUEST=None, **kw): + def searchResults(self, query=None, **kw): """ - Calls ZCatalog.searchResults with extra arguments that - limit the results to what the user is allowed to see. + Calls ZCatalog.searchResults with extra arguments that + limit the results to what the user is allowed to see. """ - kw = self.getSecurityUid(**kw) - if not _checkPermission( CMFCorePermissions.AccessInactivePortalContent, self ): - base = aq_base( self ) now = DateTime() kw[ 'effective' ] = { 'query' : now, 'range' : 'max' } kw[ 'expires' ] = { 'query' : now, 'range' : 'min' } - kw.setdefault('limit', 1000) - return ZCatalog.searchResults(self, REQUEST, **kw) + query = self.getSecurityQuery(query=query, **kw) + kw.setdefault('limit', DEFAULT_RESULT_LIMIT) + return ZCatalog.searchResults(self, query=query, **kw) __call__ = searchResults @@ -468,16 +506,14 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): def unrestrictedSearchResults(self, REQUEST=None, **kw): """Calls ZSQLCatalog.searchResults directly without restrictions. """ - kw.setdefault('limit', 1000) + kw.setdefault('limit', DEFAULT_RESULT_LIMIT) return ZCatalog.searchResults(self, REQUEST, **kw) - def countResults(self, REQUEST=None, **kw): + def countResults(self, query=None, **kw): """ Calls ZCatalog.countResults with extra arguments that limit the results to what the user is allowed to see. """ - kw = self.getSecurityUid(**kw) - # XXX This needs to be set again #if not _checkPermission( # CMFCorePermissions.AccessInactivePortalContent, self ): @@ -486,8 +522,10 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): # #kw[ 'effective' ] = { 'query' : now, 'range' : 'max' } # #kw[ 'expires' ] = { 'query' : now, 'range' : 'min' } - return ZCatalog.countResults(self, REQUEST, **kw) - + query = self.getSecurityQuery(query=query, **kw) + kw.setdefault('limit', DEFAULT_RESULT_LIMIT) + return ZCatalog.countResults(self, query=query, **kw) + security.declarePrivate('unrestrictedCountResults') def unrestrictedCountResults(self, REQUEST=None, **kw): """Calls ZSQLCatalog.countResults directly without restrictions. @@ -679,7 +717,7 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): else: base_category_id = name[len(DYNAMIC_METHOD_NAME):] method = RelatedBaseCategory(base_category_id) - setattr(self.__class__, name, + setattr(self.__class__, name, method) klass = aq_base(self).__class__ if hasattr(klass, 'security'):