hooks/security.py
branchstable
changeset 9542 79b9bf88be28
parent 9536 1e39c5e91b68
child 9543 39f981482e34
child 9981 7099bbd685aa
equal deleted inserted replaced
9540:43b4895a150f 9542:79b9bf88be28
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    18 """Security hooks: check permissions to add/delete/update entities according to
    18 """Security hooks: check permissions to add/delete/update entities according to
    19 the user connected to a session
    19 the user connected to a session
    20 """
    20 """
    21 
    21 
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
       
    23 from warnings import warn
    23 
    24 
    24 from logilab.common.registry import objectify_predicate
    25 from logilab.common.registry import objectify_predicate
    25 
    26 
    26 from yams import buildobjs
    27 from yams import buildobjs
    27 
    28 
    28 from cubicweb import Unauthorized
    29 from cubicweb import Unauthorized
    29 from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
    30 from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
    30 
    31 
    31 
    32 
    32 _DEFAULT_UPDATE_ATTRPERM = buildobjs.DEFAULT_ATTRPERMS['update']
    33 
    33 def check_entity_attributes(session, entity, editedattrs=None, creation=False):
    34 def check_entity_attributes(session, entity, action, editedattrs=None):
    34     eid = entity.eid
    35     eid = entity.eid
    35     eschema = entity.e_schema
    36     eschema = entity.e_schema
    36     # ._cw_skip_security_attributes is there to bypass security for attributes
    37     # ._cw_skip_security_attributes is there to bypass security for attributes
    37     # set by hooks by modifying the entity's dictionary
    38     # set by hooks by modifying the entity's dictionary
    38     if editedattrs is None:
    39     if editedattrs is None:
    41     for attr in editedattrs:
    42     for attr in editedattrs:
    42         if attr in dontcheck:
    43         if attr in dontcheck:
    43             continue
    44             continue
    44         rdef = eschema.rdef(attr, takefirst=True)
    45         rdef = eschema.rdef(attr, takefirst=True)
    45         if rdef.final: # non final relation are checked by standard hooks
    46         if rdef.final: # non final relation are checked by standard hooks
    46             # attributes only have a specific 'update' permission
    47             perms = rdef.permissions.get(action)
    47             updateperm = rdef.permissions.get('update')
       
    48             # comparison below works because the default update perm is:
    48             # comparison below works because the default update perm is:
    49             #
    49             #
    50             #  ('managers', ERQLExpression(Any X WHERE U has_update_permission X, X eid %(x)s, U eid %(u)s))
    50             #  ('managers', ERQLExpression(Any X WHERE U has_update_permission X,
       
    51             #                              X eid %(x)s, U eid %(u)s))
    51             #
    52             #
    52             # is deserialized in this order (groups first), and ERQLExpression
    53             # is deserialized in this order (groups first), and ERQLExpression
    53             # implements comparison by expression.
    54             # implements comparison by rql expression.
    54             if updateperm == _DEFAULT_UPDATE_ATTRPERM:
    55             if perms == buildobjs.DEFAULT_ATTRPERMS[action]:
    55                 # The default update permission is to delegate to the entity
    56                 # The default rule is to delegate to the entity
    56                 # update permission. This is an historical artefact but it is
    57                 # rule. This is an historical artefact. Hence we take
    57                 # costly (in general). Hence we take this permission object as a
    58                 # this object as a marker saying "no specific"
    58                 # marker saying "no specific" update permissions for this
    59                 # permission rule for this attribute. Thus we just do
    59                 # attribute. Thus we just do nothing.
    60                 # nothing.
    60                 continue
    61                 continue
    61             if creation and updateperm == ():
    62             if perms == ():
    62                 # That actually means an immutable attribute.  We make an
    63                 # That means an immutable attribute; as an optimization, avoid
    63                 # _exception_ to the `check attr update perms at entity create &
    64                 # going through check_perm.
    64                 # update time` rule for this case.
    65                 raise Unauthorized(action, str(rdef))
    65                 continue
    66             rdef.check_perm(session, action, eid=eid)
    66             rdef.check_perm(session, 'update', eid=eid)
       
    67 
    67 
    68 
    68 
    69 class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
    69 class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
    70     def precommit_event(self):
    70     def precommit_event(self):
    71         session = self.session
    71         session = self.session
    72         for eid, action, edited in self.get_data():
    72         for eid, action, edited in self.get_data():
    73             entity = session.entity_from_eid(eid)
    73             entity = session.entity_from_eid(eid)
    74             entity.cw_check_perm(action)
    74             entity.cw_check_perm(action)
    75             check_entity_attributes(session, entity, edited,
    75             check_entity_attributes(session, entity, action, edited)
    76                                     creation=(action == 'add'))
       
    77 
    76 
    78 
    77 
    79 class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
    78 class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
    80     def precommit_event(self):
    79     def precommit_event(self):
    81         session = self.session
    80         session = self.session
   109 class AfterUpdateEntitySecurityHook(SecurityHook):
   108 class AfterUpdateEntitySecurityHook(SecurityHook):
   110     __regid__ = 'securityafterupdateentity'
   109     __regid__ = 'securityafterupdateentity'
   111     events = ('after_update_entity',)
   110     events = ('after_update_entity',)
   112 
   111 
   113     def __call__(self):
   112     def __call__(self):
   114         try:
   113         # save back editedattrs in case the entity is reedited later in the
   115             # check user has permission right now, if not retry at commit time
   114         # same transaction, which will lead to cw_edited being
   116             self.entity.cw_check_perm('update')
   115         # overwritten
   117             check_entity_attributes(self._cw, self.entity)
   116         CheckEntityPermissionOp.get_instance(self._cw).add_data(
   118         except Unauthorized:
   117             (self.entity.eid, 'update', self.entity.cw_edited) )
   119             self.entity._cw_clear_local_perm_cache('update')
       
   120             # save back editedattrs in case the entity is reedited later in the
       
   121             # same transaction, which will lead to cw_edited being
       
   122             # overwritten
       
   123             CheckEntityPermissionOp.get_instance(self._cw).add_data(
       
   124                 (self.entity.eid, 'update', self.entity.cw_edited) )
       
   125 
   118 
   126 
   119 
   127 class BeforeDelEntitySecurityHook(SecurityHook):
   120 class BeforeDelEntitySecurityHook(SecurityHook):
   128     __regid__ = 'securitybeforedelentity'
   121     __regid__ = 'securitybeforedelentity'
   129     events = ('before_delete_entity',)
   122     events = ('before_delete_entity',)