src/pyams_security/security.py
changeset 2 94e76f8e9828
child 11 7f3ca73c67e6
equal deleted inserted replaced
1:5595823c66f1 2:94e76f8e9828
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 # import standard library
       
    16 import logging
       
    17 
       
    18 # import interfaces
       
    19 from pyams_security.interfaces import IProtectedObject, IRole, IPrincipalInfo, GrantedRoleEvent, RevokedRoleEvent, \
       
    20     IDefaultProtectionPolicy, IRoleProtectedObject
       
    21 from zope.annotation.interfaces import IAnnotations
       
    22 
       
    23 # import packages
       
    24 from persistent import Persistent
       
    25 from persistent.dict import PersistentDict
       
    26 from pyams_utils.adapter import adapter_config
       
    27 from pyams_utils.property import request_property
       
    28 from pyams_utils.registry import query_utility
       
    29 from pyams_utils.request import check_request
       
    30 from pyramid.location import lineage
       
    31 from pyramid.security import DENY_ALL, Everyone, Allow, ALL_PERMISSIONS, Authenticated
       
    32 from zope.interface import implementer
       
    33 from zope.lifecycleevent import ObjectCreatedEvent
       
    34 from zope.location.location import locate
       
    35 from zope.schema.fieldproperty import FieldProperty
       
    36 
       
    37 
       
    38 @implementer(IRoleProtectedObject)
       
    39 class RoleProtectedObject(Persistent):
       
    40     """Base class for object protected by roles"""
       
    41 
       
    42     inherit_parent_security = FieldProperty(IRoleProtectedObject['inherit_parent_security'])
       
    43     _everyone_permission = FieldProperty(IRoleProtectedObject['everyone_permission'])
       
    44     _authenticated_permission = FieldProperty(IRoleProtectedObject['authenticated_permission'])
       
    45     inherit_parent_roles = FieldProperty(IRoleProtectedObject['inherit_parent_roles'])
       
    46 
       
    47     def __init__(self):
       
    48         self._principals_by_role = PersistentDict()
       
    49         self._roles_by_principal = PersistentDict()
       
    50 
       
    51     @property
       
    52     def everyone_permission(self):
       
    53         permission = self._everyone_permission
       
    54         if permission is None and self.inherit_parent_security:
       
    55             for parent in lineage(self):
       
    56                 if parent is self:
       
    57                     continue
       
    58                 protection = IProtectedObject(parent, None)
       
    59                 if protection is not None:
       
    60                     permission = protection.everyone_permission
       
    61                 if permission is not None:
       
    62                     break
       
    63         return permission
       
    64 
       
    65     @everyone_permission.setter
       
    66     def everyone_permission(self, value):
       
    67         self._everyone_permission = value
       
    68 
       
    69     @property
       
    70     def authenticated_permission(self):
       
    71         permission = self._authenticated_permission
       
    72         if permission is None and self.inherit_parent_security:
       
    73             for parent in lineage(self):
       
    74                 if parent is self:
       
    75                     continue
       
    76                 protection = IProtectedObject(parent, None)
       
    77                 if protection is not None:
       
    78                     permission = protection.authenticated_permission
       
    79                 if permission is not None:
       
    80                     break
       
    81         return permission
       
    82 
       
    83     @authenticated_permission.setter
       
    84     def authenticated_permission(self, value):
       
    85         self._authenticated_permission = value
       
    86 
       
    87     def grant_role(self, role_id, principal_ids):
       
    88         registry = check_request().registry
       
    89         if IRole.providedBy(role_id):
       
    90             role_id = role_id.id
       
    91         if isinstance(principal_ids, str):
       
    92             principal_ids = {principal_ids}
       
    93         role_principals = self._principals_by_role.get(role_id) or set()
       
    94         for principal_id in principal_ids:
       
    95             if IPrincipalInfo.providedBy(principal_id):
       
    96                 principal_id = principal_id.id
       
    97             if principal_id not in role_principals:
       
    98                 principal_roles = self._roles_by_principal.get(principal_id) or set()
       
    99                 role_principals.add(principal_id)
       
   100                 principal_roles.add(role_id)
       
   101                 self._roles_by_principal[principal_id] = principal_roles
       
   102                 self._principals_by_role[role_id] = role_principals
       
   103                 registry.notify(GrantedRoleEvent(self, role_id, principal_id))
       
   104 
       
   105     def revoke_role(self, role_id, principal_ids):
       
   106         registry = check_request().registry
       
   107         if IRole.providedBy(role_id):
       
   108             role_id = role_id.id
       
   109         if isinstance(principal_ids, str):
       
   110             principal_ids = {principal_ids}
       
   111         role_principals = self._principals_by_role.get(role_id) or set()
       
   112         for principal_id in principal_ids:
       
   113             if IPrincipalInfo.providedBy(principal_id):
       
   114                 principal_id = principal_id.id
       
   115             if principal_id in role_principals:
       
   116                 principal_roles = self._roles_by_principal.get(principal_id) or set()
       
   117                 role_principals.remove(principal_id)
       
   118                 principal_roles.remove(role_id)
       
   119                 self._roles_by_principal[principal_id] = principal_roles
       
   120                 self._principals_by_role[role_id] = role_principals
       
   121                 registry.notify(RevokedRoleEvent(self, role_id, principal_id))
       
   122 
       
   123     def get_principals(self, role_id):
       
   124         if IRole.providedBy(role_id):
       
   125             role_id = role_id.id
       
   126         return self._principals_by_role.get(role_id) or set()
       
   127 
       
   128     def get_roles(self, principal_id):
       
   129         if IPrincipalInfo.providedBy(principal_id):
       
   130             principal_id = principal_id.id
       
   131         return self._roles_by_principal.get(principal_id) or set()
       
   132 
       
   133     def get_permissions(self, principal_id):
       
   134         registry = check_request().registry
       
   135         result = set()
       
   136         for role_id in self.get_roles(principal_id):
       
   137             role = registry.queryUtility(IRole, role_id)
       
   138             result |= role.permissions or set()
       
   139         return result
       
   140 
       
   141     @request_property(key=None)
       
   142     def __acl__(self):
       
   143         # always grant all permissions to system manager
       
   144         result = [(Allow, 'system:admin', ALL_PERMISSIONS)]
       
   145         # grant permission to everyone and authenticated
       
   146         if self.everyone_permission:
       
   147             result.append((Allow, Everyone, self.everyone_permission))
       
   148         if self.authenticated_permission:
       
   149             result.append((Allow, Authenticated, self.authenticated_permission))
       
   150         # grant access to all roles permissions
       
   151         for role_id in self._principals_by_role.keys():
       
   152             role = query_utility(IRole, role_id)
       
   153             if role is not None:
       
   154                 result.append((Allow, 'role:{0}'.format(role_id), role.permissions))
       
   155         # stop inheritance if required
       
   156         if not self.inherit_parent_security:
       
   157             result.append(DENY_ALL)
       
   158         logging.getLogger('PyAMS').debug(result)
       
   159         print(result)
       
   160         return result
       
   161 
       
   162 
       
   163 ROLES_ANNOTATIONS_KEY = 'pyams_security.roles'
       
   164 
       
   165 
       
   166 @adapter_config(context=IDefaultProtectionPolicy, provides=IRoleProtectedObject)
       
   167 def ProtectedObjectFactory(context):
       
   168     """Default protected object factory"""
       
   169     annotations = IAnnotations(context)
       
   170     protection = annotations.get(ROLES_ANNOTATIONS_KEY)
       
   171     if protection is None:
       
   172         protection = annotations[ROLES_ANNOTATIONS_KEY] = RoleProtectedObject()
       
   173         check_request().registry.notify(ObjectCreatedEvent(protection))
       
   174         locate(protection, context)
       
   175     return protection
       
   176 
       
   177 
       
   178 class ProtectedObject(object):
       
   179     """Base protected object class"""
       
   180 
       
   181     def __acl__(self):
       
   182         protected = IProtectedObject(self, None)
       
   183         if protected is not None:
       
   184             acl = protected.__acl__()
       
   185             if callable(acl):
       
   186                 acl = acl(protected)
       
   187             return acl
       
   188         return []