From 7c0bc43781d7366084827a7836e3ccf0e8303c73 Mon Sep 17 00:00:00 2001
From: Alexandre Boeglin <alex@nexedi.com>
Date: Mon, 5 Sep 2005 08:00:17 +0000
Subject: [PATCH] Modified the security management : it still depends on
 category values, but only the part that is site specific has been taken out.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@3735 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Type/ERP5Type.py              | 99 +++++++++++++++++++----
 product/ERP5Type/RoleInformation.py       | 60 +++++---------
 product/ERP5Type/RoleProviderBase.py      | 18 ++---
 product/ERP5Type/dtml/editToolsRoles.dtml | 20 ++---
 4 files changed, 119 insertions(+), 78 deletions(-)

diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py
index 1e3d353bea..e4bfc992e6 100755
--- a/product/ERP5Type/ERP5Type.py
+++ b/product/ERP5Type/ERP5Type.py
@@ -21,7 +21,7 @@
 ##############################################################################
 
 from Globals import InitializeClass, DTMLFile
-from AccessControl import ClassSecurityInfo
+from AccessControl import ClassSecurityInfo, getSecurityManager
 from Acquisition import aq_base, aq_inner, aq_parent
 
 import Products.CMFCore.TypesTool
@@ -39,7 +39,7 @@ from RoleInformation import ori
 
 from zLOG import LOG
 
-ERP5TYPE_ROLE_INIT_SCRIPT = 'ERP5Type_initLocalRoleMapping'
+ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT = 'ERP5TypeSecurity_asGroupId'
 
 class ERP5TypeInformation( FactoryTypeInformation, RoleProviderBase ):
     """
@@ -106,7 +106,7 @@ class ERP5TypeInformation( FactoryTypeInformation, RoleProviderBase ):
     hidden_content_type_list = ()
     filter_actions = 0
     allowed_action_list = []
-    
+
     #
     #   Acquisition editing interface
     #
@@ -119,7 +119,7 @@ class ERP5TypeInformation( FactoryTypeInformation, RoleProviderBase ):
         """
         self.setMethodAliases({})
         return 1
-        
+
     security.declarePublic('hideFromAddMenu')
     def hidenFromAddMenu(self):
       """
@@ -140,24 +140,18 @@ class ERP5TypeInformation( FactoryTypeInformation, RoleProviderBase ):
         """
         ob = FactoryTypeInformation.constructInstance(self, container, id, *args, **kw)
 
-        # Only try to find the local role init script
-        # if some roles are defined
+        # Only try to assign roles to secutiry groups if some roles are defined
         # This is an optimisation to prevent defining local roles on subobjects
         # which acquire their security definition from their parent
-        # The downside of this optimisation is that it is not possible to 
+        # The downside of this optimisation is that it is not possible to
         # set a local role definition if the local role list is empty
         if len(self._roles):
-          init_role_script = getattr(ob, ERP5TYPE_ROLE_INIT_SCRIPT, None)
-          if init_role_script is not None:
-            # Retrieve applicable roles
-            role_mapping = self.getFilteredRoleListFor(object = self) # kw provided in order to take any appropriate action
-            # Call the local role init script
-            init_role_script(role_mapping = role_mapping, **kw)
+            self.assignRoleToSecurityGroup(ob)
 
         if self.init_script:
-          # Acquire the init script in the context of this object
-          init_script = getattr(ob, self.init_script)
-          init_script(*args, **kw)
+            # Acquire the init script in the context of this object
+            init_script = getattr(ob, self.init_script)
+            init_script(*args, **kw)
 
         return ob
 
@@ -193,6 +187,79 @@ class ERP5TypeInformation( FactoryTypeInformation, RoleProviderBase ):
         result.sort()
         return result
 
+    security.declareProtected(ERP5Permissions.ModifyPortalContent, 'assignRoleToSecurityGroup')
+    def assignRoleToSecurityGroup(self, object):
+        """
+        Assign Local Roles to Groups on object, based on Portal Type Role Definitions
+        """
+        user_name = getSecurityManager().getUser().getUserName()
+        # First of all, check that NuxUserGroups is here. Otherwise, it's not possible to give Roles to Groups
+        try:
+            import Products.NuxUserGroups
+        except ImportError:
+            raise RuntimeError, 'Product "NuxUserGroups" was not found on your setup. '\
+                'Please install it to benefit from group-based security'
+
+
+        # Retrieve applicable roles
+        role_mapping = self.getFilteredRoleListFor(object = self) # kw provided in order to take any appropriate action
+        role_category_list = {}
+        for role, definition_list in role_mapping.items():
+            if not role_category_list.has_key(role):
+                role_category_list[role] = []
+            # For each role definition, we look for the base_category_script
+            # and try to use it to retrieve the values for the base_category list
+            for definition in definition_list:
+                base_category_script = getattr(object, definition['base_category_script'], None)
+                if base_category_script is not None:
+                    # call the script, which should return either a dict or a list of dicts
+                    category_result = base_category_script(definition['base_category'], user_name, object, object.getPortalType())
+                    # we also need to store the user specified order of categories, as dict are not ordered
+                    category_order_list = []
+                    category_order_list.extend(definition['base_category'])
+                    for c in definition['category']:
+                        bc = c.split('/')[0]
+                        if bc not in category_order_list:
+                            category_order_list.append(bc)
+                    # add the result to role_category_list
+                    if type(category_result) is type({}):
+                        category_result = [category_result]
+                    for category_dict in category_result:
+                        category_value_dict = {'category_order':category_order_list}
+                        category_value_dict.update(category_dict)
+                        for c in definition['category']:
+                            bc, value = c.split('/', 1)
+                            category_value_dict[bc] = value
+                        role_category_list[role].append(category_value_dict)
+
+        # Generate security group ids from category_value_dicts
+        role_group_id_dict = {}
+        group_id_generator = getattr(object, ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT, None)
+        if group_id_generator is None:
+            raise RuntimeError, '%s script was not found' % ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT
+        for role, value_list in role_category_list.items():
+            if not role_group_id_dict.has_key(role):
+                role_group_id_dict[role] = []
+            role_group_dict = {}
+            for category_dict in value_list:
+                group_id = group_id_generator(**category_dict)
+                role_group_dict[group_id] = 1
+            role_group_id_dict[role].extend(role_group_dict.keys())
+
+        #Switch index from role to group id
+        group_id_role_dict = {}
+        for role, group_list in role_group_id_dict.items():
+            for group_id in group_list:
+                if not group_id_role_dict.has_key(group_id):
+                    group_id_role_dict[group_id] = []
+                group_id_role_dict[group_id].append(role)
+        #Clean old group roles
+        old_group_list = object.get_local_group_roles()
+        object.manage_delLocalGroupRoles([x[0] for x in old_group_list])
+        #Assign new roles
+        for group, role_list in group_id_role_dict.items():
+            object.manage_addLocalGroupRoles(group, role_list)
+
     security.declarePublic('getFilteredRoleListFor')
     def getFilteredRoleListFor(self, object=None, **kw):
         """
diff --git a/product/ERP5Type/RoleInformation.py b/product/ERP5Type/RoleInformation.py
index 78cad77b55..806dc9a3f6 100755
--- a/product/ERP5Type/RoleInformation.py
+++ b/product/ERP5Type/RoleInformation.py
@@ -31,7 +31,7 @@ from types import StringType
 class RoleInformation( SimpleItem ):
 
     """ Represent a single selectable role.
-    
+
     Roles generate links to views of content, or to specific methods
     of the site.  They can be filtered via their conditions.
     """
@@ -48,24 +48,21 @@ class RoleInformation( SimpleItem ):
                 , condition=''
                 , priority=10
                 , base_category=()
-                , user=''
+                , base_category_script=''
                 ):
         """ Set up an instance.
         """
         if condition and type( condition ) == type( '' ):
             condition = Expression( condition )
 
-        if user and type( user ) == type( '' ):
-            user = Expression( user )
-
         self.id = id
         self.title = title
         self.description = description
-        self.category = category 
+        self.category = category
         self.condition = condition
-        self.priority = priority 
+        self.priority = priority
         self.base_category = base_category
-        self.user = user
+        self.base_category_script = base_category_script
 
     security.declareProtected( View, 'Title' )
     def Title(self):
@@ -100,36 +97,10 @@ class RoleInformation( SimpleItem ):
         info = {}
         info['id'] = self.id
         info['name'] = self.Title()
-        expr = self.getUserExpression()
-        __traceback_info__ = (info['id'], info['name'], expr)
-        if self.user:
-            info['user'] = self.user( ec ) or None
-        else:
-            info['user'] = getSecurityManager().getUser() # XXX The user should be a handle to the Person object
         info['category'] = self.getCategory()
         info['base_category'] = self.getBaseCategory()
-        return info 
-
-    security.declarePublic( 'getUserExpression' )
-    def getUserExpression( self ):
-
-        """ Return the text of the TALES expression for our URL.
-        """
-        user = getattr(self, 'user', '')
-        expr = user and user.text or ''
-        if expr and type( expr ) is StringType:
-            if not expr.startswith('python:') and not expr.startswith('string:'):
-                expr = 'string:${object_url}/%s' % expr
-                self.user = Expression( expr )
-        return expr
-
-    security.declarePrivate( 'setRoleExpression' )
-    def setUserExpression(self, user):
-        if user and type( user ) is StringType:
-            if not user.startswith('python:')  and not user.startswith('string:'):
-                user = 'string:${object_url}/%s' % user
-                user = Expression( user )
-        self.user = user
+        info['base_category_script'] = self.getBaseCategoryScript()
+        return info
 
     security.declarePublic( 'getCondition' )
     def getCondition(self):
@@ -141,9 +112,9 @@ class RoleInformation( SimpleItem ):
     security.declarePublic( 'getCategory' )
     def getCategory( self ):
 
-        """ Return the category 
+        """ Return the category
             as a tuple (to prevent script from modifying it)
-            
+
             Strip any return or ending space
         """
         return tuple(map(lambda x: x.strip(), filter(lambda x: x, self.category))) or ()
@@ -156,6 +127,13 @@ class RoleInformation( SimpleItem ):
         """
         return tuple(getattr(self, 'base_category', ()))
 
+    security.declarePublic( 'getBaseCategoryScript' )
+    def getBaseCategoryScript( self ):
+
+        """ Return the base_category_script id
+        """
+        return getattr(self, 'base_category_script', '')
+
     security.declarePrivate( 'base_category' )
     def clone( self ):
 
@@ -168,7 +146,7 @@ class RoleInformation( SimpleItem ):
                              , condition=self.getCondition()
                              , priority =self.priority
                              , base_category=self.base_category
-                             , user=self.getUserExpression()
+                             , base_category_script=self.base_category_script
                              )
 
 InitializeClass( RoleInformation )
@@ -182,8 +160,8 @@ class ori:
     def __init__( self, tool, folder, object=None ):
         self.portal = portal = aq_parent(aq_inner(tool))
         membership = getToolByName(tool, 'portal_membership')
-        self.isAnonymous = membership.isAnonymousUser()
-        self.user_id = membership.getAuthenticatedMember().getId()
+        #self.isAnonymous = membership.isAnonymousUser()
+        #self.user_id = membership.getAuthenticatedMember().getId()
         self.portal_url = portal.absolute_url()
         if folder is not None:
             self.folder_url = folder.absolute_url()
diff --git a/product/ERP5Type/RoleProviderBase.py b/product/ERP5Type/RoleProviderBase.py
index 2327e1ea87..5f04da0107 100755
--- a/product/ERP5Type/RoleProviderBase.py
+++ b/product/ERP5Type/RoleProviderBase.py
@@ -41,7 +41,7 @@ class RoleProviderBase:
     manage_options = ( { 'label' : 'Roles'
                        , 'action' : 'manage_editRolesForm'
                        }
-                     , 
+                     ,
                      )
 
     #
@@ -70,7 +70,7 @@ class RoleProviderBase:
             a1['name'] = a.Title()  # The name of this role definition (ex. Assignor at company X)
             a1['category'] = a.getCategory() or [] # Category definition
             a1['base_category'] = a.getBaseCategory() # Base Category Definition
-            a1['user'] = a.getUserExpression()
+            a1['base_category_script'] = a.getBaseCategoryScript() # Base Category Script Id
             a1['condition'] = a.getCondition()
             roles.append(a1)
 
@@ -85,9 +85,9 @@ class RoleProviderBase:
     def addRole( self
                  , id
                  , name
-                 , user
                  , condition
                  , category
+                 , base_category_script
                  , base_category=()
                  , REQUEST=None
                  ):
@@ -96,17 +96,16 @@ class RoleProviderBase:
         if not name:
             raise ValueError('A name is required.')
 
-        a_expr = user and Expression(text=str(user)) or ''
         c_expr = condition and Expression(text=str(condition)) or ''
 
         new_roles = self._cloneRoles()
 
         new_role = RoleInformation(     id=str(id)
                                       , title=str(name)
-                                      , user=a_expr
                                       , condition=c_expr
                                       , category=category.split('\n')
                                       , base_category=base_category.split()
+                                      , base_category_script=base_category_script
                                       )
 
         new_roles.append( new_role )
@@ -220,7 +219,7 @@ class RoleProviderBase:
         """ Return a list of roles, cloned from our current list.
         """
         return map( lambda x: x.clone(), list( self._roles ) )
- 
+
     security.declarePrivate( '_extractRole' )
     def _extractRole( self, properties, index ):
 
@@ -228,26 +227,23 @@ class RoleProviderBase:
         """
         id             = str( properties.get( 'id_%d'          % index, '' ) )
         name           = str( properties.get( 'name_%d'        % index, '' ) )
-        user           = str( properties.get( 'user_%d'      % index, '' ) )
         condition      = str( properties.get( 'condition_%d'   % index, '' ) )
         category       = properties.get( 'category_%d'    % index, '' ).split('\n')
         base_category  = properties.get( 'base_category_%d'     % index, ''  ).split()
+        base_category_script = str( properties.get( 'base_category_script_%d' % index, '' ) )
 
         if not name:
             raise ValueError('A name is required.')
 
-        if user is not '':
-            user = Expression( text=user )
-
         if condition is not '':
             condition = Expression( text=condition )
 
         return RoleInformation( id=id
                                 , title=name
-                                , user=user
                                 , condition=condition
                                 , category=category
                                 , base_category=base_category
+                                , base_category_script=base_category_script
                                 )
 
 InitializeClass(RoleProviderBase)
diff --git a/product/ERP5Type/dtml/editToolsRoles.dtml b/product/ERP5Type/dtml/editToolsRoles.dtml
index 83d8e687ef..103ce4d9ce 100755
--- a/product/ERP5Type/dtml/editToolsRoles.dtml
+++ b/product/ERP5Type/dtml/editToolsRoles.dtml
@@ -56,7 +56,7 @@
 <td></td>
 <td>
   <div class="form-label">
-  Condition 
+  Condition
   </div>
 </td>
 <td>
@@ -70,12 +70,12 @@
 <td></td>
 <td>
   <div class="form-label">
-  User
+  Base Category
   </div>
 </td>
 <td>
   <div class="form-element">
-  <input type="text" name="user_&dtml-index;" value="&dtml-user;" size="80" />
+  <input type="text" size="40" name="base_category_&dtml-index;" value="<dtml-var "' '.join(base_category)">" />
   </div>
 </td>
 </tr>
@@ -84,12 +84,12 @@
 <td></td>
 <td>
   <div class="form-label">
-  Base Category
+  Base Category Script
   </div>
 </td>
 <td>
   <div class="form-element">
-  <input type="text" size="40" name="base_category_&dtml-index;" value="<dtml-var "' '.join(base_category)">" />
+  <input type="text" name="base_category_script_&dtml-index;" value="&dtml-base_category_script;" size="80" />
   </div>
 </td>
 </tr>
@@ -169,7 +169,7 @@ Add a role
 <td></td>
 <td>
   <div class="form-label">
-  Condition 
+  Condition
   </div>
 </td>
 <td>
@@ -183,12 +183,12 @@ Add a role
 <td></td>
 <td>
   <div class="form-label">
-  User
+  Base Category
   </div>
 </td>
 <td>
   <div class="form-element">
-  <input type="text" name="user" size="80" />
+  <input type="text" size="40" name="base_category" />
   </div>
 </td>
 </tr>
@@ -197,12 +197,12 @@ Add a role
 <td></td>
 <td>
   <div class="form-label">
-  Base Category
+  Base Category Script
   </div>
 </td>
 <td>
   <div class="form-element">
-  <input type="text" size="40" name="base_category" />
+  <input type="text" name="base_category_script" size="80" />
   </div>
 </td>
 </tr>
-- 
2.30.9