Added properties to security manager to correctly handle denied permissions
authorThierry Florac <thierry.florac@onf.fr>
Tue, 30 Jun 2015 15:02:07 +0200
changeset 44 b999bd4dd461
parent 43 d2ee97ff6d3e
child 45 4d5db00d12b9
Added properties to security manager to correctly handle denied permissions
src/pyams_security/interfaces/__init__.py
src/pyams_security/security.py
src/pyams_security/zmi/security.py
src/pyams_security/zmi/utility.py
--- a/src/pyams_security/interfaces/__init__.py	Wed Jun 17 09:59:26 2015 +0200
+++ b/src/pyams_security/interfaces/__init__.py	Tue Jun 30 15:02:07 2015 +0200
@@ -714,14 +714,26 @@
                                    required=True,
                                    default=True)
 
-    everyone_permissions = PermissionsSetField(title=_("Public permissions"),
-                                               description=_("These permissions will be granted to all users"),
+    everyone_denied = PermissionsSetField(title=_("Public denied permissions"),
+                                          description=_("These permissions will be denied to all users. "
+                                                        "Denied permissions take precedence over granted "
+                                                        "ones."),
+                                          required=False)
+
+    everyone_granted = PermissionsSetField(title=_("Public granted permissions"),
+                                           description=_("These permissions will be granted to all users"),
+                                           required=False)
+
+    authenticated_denied = PermissionsSetField(title=_("Authenticated denied permissions"),
+                                               description=_("These permissions will be denied to "
+                                                             "authenticated users. Denied permissions "
+                                                             "take precedence over granted ones."),
                                                required=False)
 
-    authenticated_permissions = PermissionsSetField(title=_("Authenticated permissions"),
-                                                    description=_("These permissions will be granted to authenticated "
-                                                                  "users"),
-                                                    required=False)
+    authenticated_granted = PermissionsSetField(title=_("Authenticated granted permissions"),
+                                                description=_("These permissions will be granted to authenticated "
+                                                              "users"),
+                                                required=False)
 
     inherit_parent_roles = Bool(title=_("Inherit parent roles?"),
                                 description=_("Get roles granted on parent levels"),
@@ -749,6 +761,21 @@
     def get_permissions(self, principal_id):
         """Get ID of permissions granted to given principal"""
 
+    def get_everyone_denied(self):
+        """Get denied permissions for everyone, including inherited ones"""
+
+    def get_everyone_granted(self):
+        """Get granted permissions for everyone, including inherited ones"""
+
+    def get_authenticated_denied(self):
+        """Get denied permissions for authenticated, including inherited ones"""
+
+    def get_authenticated_granted(self):
+        """Get granted permissions for authenticated, including inherited ones"""
+
+    def get_granted_roles(self):
+        """Get all roles, including inherited ones"""
+
 
 class IRoleProtectedObject(IProtectedObject):
     """Roles protected object interface"""
--- a/src/pyams_security/security.py	Wed Jun 17 09:59:26 2015 +0200
+++ b/src/pyams_security/security.py	Tue Jun 30 15:02:07 2015 +0200
@@ -28,7 +28,7 @@
 from pyams_utils.property import request_property
 from pyams_utils.registry import query_utility
 from pyramid.location import lineage
-from pyramid.security import DENY_ALL, Everyone, Allow, ALL_PERMISSIONS, Authenticated
+from pyramid.security import DENY_ALL, Everyone, Allow, Deny, ALL_PERMISSIONS, Authenticated
 from pyramid.threadlocal import get_current_registry
 from zope.container.contained import Contained
 from zope.interface import implementer
@@ -42,49 +42,59 @@
     """Base class for object protected by roles"""
 
     inherit_parent_security = FieldProperty(IRoleProtectedObject['inherit_parent_security'])
-    _everyone_permissions = FieldProperty(IRoleProtectedObject['everyone_permissions'])
-    _authenticated_permissions = FieldProperty(IRoleProtectedObject['authenticated_permissions'])
+    everyone_denied = FieldProperty(IRoleProtectedObject['everyone_denied'])
+    everyone_granted = FieldProperty(IRoleProtectedObject['everyone_granted'])
+    authenticated_denied = FieldProperty(IRoleProtectedObject['authenticated_denied'])
+    authenticated_granted = FieldProperty(IRoleProtectedObject['authenticated_granted'])
     inherit_parent_roles = FieldProperty(IRoleProtectedObject['inherit_parent_roles'])
 
     def __init__(self):
         self._principals_by_role = PersistentDict()
         self._roles_by_principal = PersistentDict()
 
-    @property
-    def everyone_permissions(self):
-        permissions = self._everyone_permissions
-        if (not permissions) and self.inherit_parent_security:
+    def get_everyone_denied(self):
+        permissions = self.everyone_denied or set()
+        if self.inherit_parent_security:
+            for parent in lineage(self):
+                if parent in (self, self.__parent__):
+                    continue
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    permissions = permissions | (protection.everyone_denied or set())
+        return permissions
+
+    def get_everyone_granted(self):
+        permissions = self.everyone_granted or set()
+        if self.inherit_parent_security:
             for parent in lineage(self):
                 if parent in (self, self.__parent__):
                     continue
                 protection = IProtectedObject(parent, None)
                 if protection is not None:
-                    permissions = protection.everyone_permissions
-                if permissions:
-                    break
+                    permissions = permissions | (protection.everyone_granted or set())
         return permissions
 
-    @everyone_permissions.setter
-    def everyone_permissions(self, value):
-        self._everyone_permissions = value
-
-    @property
-    def authenticated_permissions(self):
-        permissions = self._authenticated_permissions
-        if (not permissions) and self.inherit_parent_security:
+    def get_authenticated_denied(self):
+        permissions = self.authenticated_denied or set()
+        if self.inherit_parent_security:
             for parent in lineage(self):
                 if parent in (self, self.__parent__):
                     continue
                 protection = IProtectedObject(parent, None)
                 if protection is not None:
-                    permissions = protection.authenticated_permissions
-                if permissions:
-                    break
+                    permissions = permissions | (protection.authenticated_denied or set())
         return permissions
 
-    @authenticated_permissions.setter
-    def authenticated_permissions(self, value):
-        self._authenticated_permissions = value
+    def get_authenticated_granted(self):
+        permissions = self.authenticated_granted or set()
+        if self.inherit_parent_security:
+            for parent in lineage(self):
+                if parent in (self, self.__parent__):
+                    continue
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    permissions = permissions | (protection.authenticated_granted or set())
+        return permissions
 
     def grant_role(self, role_id, principal_ids):
         registry = get_current_registry()
@@ -146,24 +156,43 @@
             result |= role.permissions or set()
         return result
 
+    def get_granted_roles(self):
+        roles = set(self._principals_by_role.keys())
+        if self.inherit_parent_roles:
+            for parent in lineage(self):
+                if parent in (self, self.__parent__):
+                    continue
+                protection = IProtectedObject(parent, None)
+                if protection is not None:
+                    roles = roles | protection.get_granted_roles()
+        return roles
+
     @request_property(key=None)
     def __acl__(self):
         # always grant all permissions to system manager
         result = [(Allow, 'system:admin', ALL_PERMISSIONS)]
-        # grant permission to everyone and authenticated
-        if self.everyone_permissions:
-            result.append((Allow, Everyone, self.everyone_permissions))
-        if self.authenticated_permissions:
-            result.append((Allow, Authenticated, self.authenticated_permissions))
         # grant access to all roles permissions
-        for role_id in self._principals_by_role.keys():
+        for role_id in self.get_granted_roles():
             role = query_utility(IRole, role_id)
             if role is not None:
                 result.append((Allow, 'role:{0}'.format(role_id), role.permissions))
+        # grant permission to everyone and authenticated
+        permissions = self.get_everyone_denied()
+        if permissions:
+            result.append((Deny, Everyone, permissions))
+        permissions = self.get_authenticated_denied()
+        if permissions:
+            result.append((Deny, Authenticated, permissions))
+        permissions = self.get_authenticated_granted()
+        if permissions:
+            result.append((Allow, Authenticated, permissions))
+        permissions = self.get_everyone_granted()
+        if permissions:
+            result.append((Allow, Everyone, permissions))
         # stop inheritance if required
         if not self.inherit_parent_security:
             result.append(DENY_ALL)
-        logger.debug('ACL = {0}'.format(str(result)))
+        logger.debug('ACL({0!r}) = {1}'.format(self.__parent__, str(result)))
         return result
 
 
--- a/src/pyams_security/zmi/security.py	Wed Jun 17 09:59:26 2015 +0200
+++ b/src/pyams_security/zmi/security.py	Tue Jun 30 15:02:07 2015 +0200
@@ -83,12 +83,6 @@
     edit_permission = 'security.manage'
     weight = 1
 
-    def updateWidgets(self, prefix=None):
-        super(ProtectedObjectSecuritySubform, self).updateWidgets()
-        translate = self.request.localizer.translate
-        self.widgets['everyone_permissions'].noValueMessage = translate(_("(inherit from parent)"))
-        self.widgets['authenticated_permissions'].noValueMessage = translate(_("(inherit from parent)"))
-
 
 @adapter_config(name='roles.subform',
                 context=(IDefaultProtectionPolicy, IPyAMSLayer, ProtectedObjectRolesEditForm),
--- a/src/pyams_security/zmi/utility.py	Wed Jun 17 09:59:26 2015 +0200
+++ b/src/pyams_security/zmi/utility.py	Tue Jun 30 15:02:07 2015 +0200
@@ -151,9 +151,6 @@
 
     table_class = SecurityManagerPluginsTable
 
-    def __init__(self, context, request):
-        super(SecurityManagerView, self).__init__(context, request)
-
 
 @adapter_config(context=(ISite, IAdminLayer, SecurityManagerView), provides=IPageHeader)
 class SecurityManagerHeaderAdapter(DefaultPageHeaderAdapter):
@@ -202,7 +199,7 @@
 
     def update(self):
         super(SecurityManagerEditForm, self).update()
-        self.add_group(NamedWidgetsGroup('social_group', self.widgets,
+        self.add_group(NamedWidgetsGroup(self, 'social_group', self.widgets,
                                          ('enable_social_login', 'social_users_folder',
                                           'authomatic_secret', 'social_login_use_popup'),
                                          legend=_("Enable social login?"),
@@ -210,14 +207,14 @@
                                          switch=True,
                                          checkbox_switch=True,
                                          checkbox_field=ISecurityManager['enable_social_login']))
-        self.add_group(NamedWidgetsGroup('registry_group', self.widgets,
+        self.add_group(NamedWidgetsGroup(self, 'registry_group', self.widgets,
                                          ('open_registration', 'users_folder'),
                                          legend=_("Enable free registration?"),
                                          css_class='inner',
                                          switch=True,
                                          checkbox_switch=True,
                                          checkbox_field=ISecurityManager['open_registration']))
-        plugins_group = NamedWidgetsGroup('plugins_group', self.widgets,
+        plugins_group = NamedWidgetsGroup(self, 'plugins_group', self.widgets,
                                           ('credentials_plugins_names', 'authentication_plugins_names',
                                            'directory_plugins_names'),
                                           legend=_("Plug-ins"),