diff -r 058bb3dc685f -r 0b59724cb3f2 hooks/security.py --- a/hooks/security.py Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,209 +0,0 @@ -# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Security hooks: check permissions to add/delete/update entities according to -the connected user -""" - -__docformat__ = "restructuredtext en" -from warnings import warn - -from logilab.common.registry import objectify_predicate - -from yams import buildobjs - -from cubicweb import Unauthorized -from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook - - - -def check_entity_attributes(cnx, entity, action, editedattrs=None): - eid = entity.eid - eschema = entity.e_schema - if action == 'delete': - eschema.check_perm(session, action, eid=eid) - return - # ._cw_skip_security_attributes is there to bypass security for attributes - # set by hooks by modifying the entity's dictionary - if editedattrs is None: - editedattrs = entity.cw_edited - dontcheck = editedattrs.skip_security - etypechecked = False - for attr in editedattrs: - if attr in dontcheck: - continue - rdef = eschema.rdef(attr, takefirst=True) - if rdef.final: # non final relation are checked by standard hooks - perms = rdef.permissions.get(action) - # comparison below works because the default update perm is: - # - # ('managers', ERQLExpression(Any X WHERE U has_update_permission X, - # X eid %(x)s, U eid %(u)s)) - # - # is deserialized in this order (groups first), and ERQLExpression - # implements comparison by rql expression. - if perms == buildobjs.DEFAULT_ATTRPERMS[action]: - # The default rule is to delegate to the entity - # rule. This needs to be checked only once. - if not etypechecked: - entity.cw_check_perm(action) - etypechecked = True - continue - if perms == (): - # That means an immutable attribute; as an optimization, avoid - # going through check_perm. - raise Unauthorized(action, str(rdef)) - rdef.check_perm(cnx, action, eid=eid) - - if action == 'add' and not etypechecked: - # think about cnx.create_entity('Foo') - # the standard metadata were inserted by a hook - # with a bypass ... we conceptually need to check - # the eid attribute at *creation* time - entity.cw_check_perm(action) - - -class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation): - def precommit_event(self): - cnx = self.cnx - for eid, action, edited in self.get_data(): - entity = cnx.entity_from_eid(eid) - check_entity_attributes(cnx, entity, action, edited) - - -class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation): - def precommit_event(self): - cnx = self.cnx - for action, rschema, eidfrom, eidto in self.get_data(): - rdef = rschema.rdef(cnx.entity_metas(eidfrom)['type'], - cnx.entity_metas(eidto)['type']) - rdef.check_perm(cnx, action, fromeid=eidfrom, toeid=eidto) - - -@objectify_predicate -def write_security_enabled(cls, req, **kwargs): - if req is None or not req.write_security: - return 0 - return 1 - -class SecurityHook(hook.Hook): - __abstract__ = True - category = 'security' - __select__ = hook.Hook.__select__ & write_security_enabled() - - -class AfterAddEntitySecurityHook(SecurityHook): - __regid__ = 'securityafteraddentity' - events = ('after_add_entity',) - - def __call__(self): - CheckEntityPermissionOp.get_instance(self._cw).add_data( - (self.entity.eid, 'add', self.entity.cw_edited) ) - - -class AfterUpdateEntitySecurityHook(SecurityHook): - __regid__ = 'securityafterupdateentity' - events = ('after_update_entity',) - - def __call__(self): - # save back editedattrs in case the entity is reedited later in the - # same transaction, which will lead to cw_edited being - # overwritten - action = 'add' if self._cw.added_in_transaction(self.entity.eid) else 'update' - CheckEntityPermissionOp.get_instance(self._cw).add_data( - (self.entity.eid, action, self.entity.cw_edited) ) - - -class BeforeDelEntitySecurityHook(SecurityHook): - __regid__ = 'securitybeforedelentity' - events = ('before_delete_entity',) - - def __call__(self): - self.entity.cw_check_perm('delete') - - -def skip_inlined_relation_security(cnx, rschema, eid): - """return True if security for the given inlined relation should be skipped, - in case where the relation has been set through modification of - `entity.cw_edited` in a hook - """ - assert rschema.inlined - try: - entity = cnx.entity_cache(eid) - except KeyError: - return False - edited = getattr(entity, 'cw_edited', None) - if edited is None: - return False - return rschema.type in edited.skip_security - - -class BeforeAddRelationSecurityHook(SecurityHook): - __regid__ = 'securitybeforeaddrelation' - events = ('before_add_relation',) - - def __call__(self): - if self.rtype in BEFORE_ADD_RELATIONS: - nocheck = self._cw.transaction_data.get('skip-security', ()) - if (self.eidfrom, self.rtype, self.eidto) in nocheck: - return - rschema = self._cw.repo.schema[self.rtype] - if rschema.inlined and skip_inlined_relation_security( - self._cw, rschema, self.eidfrom): - return - rdef = rschema.rdef(self._cw.entity_metas(self.eidfrom)['type'], - self._cw.entity_metas(self.eidto)['type']) - rdef.check_perm(self._cw, 'add', fromeid=self.eidfrom, toeid=self.eidto) - - -class AfterAddRelationSecurityHook(SecurityHook): - __regid__ = 'securityafteraddrelation' - events = ('after_add_relation',) - - def __call__(self): - if self.rtype not in BEFORE_ADD_RELATIONS: - nocheck = self._cw.transaction_data.get('skip-security', ()) - if (self.eidfrom, self.rtype, self.eidto) in nocheck: - return - rschema = self._cw.repo.schema[self.rtype] - if rschema.inlined and skip_inlined_relation_security( - self._cw, rschema, self.eidfrom): - return - if self.rtype in ON_COMMIT_ADD_RELATIONS: - CheckRelationPermissionOp.get_instance(self._cw).add_data( - ('add', rschema, self.eidfrom, self.eidto) ) - else: - rdef = rschema.rdef(self._cw.entity_metas(self.eidfrom)['type'], - self._cw.entity_metas(self.eidto)['type']) - rdef.check_perm(self._cw, 'add', fromeid=self.eidfrom, toeid=self.eidto) - - -class BeforeDeleteRelationSecurityHook(SecurityHook): - __regid__ = 'securitybeforedelrelation' - events = ('before_delete_relation',) - - def __call__(self): - nocheck = self._cw.transaction_data.get('skip-security', ()) - if (self.eidfrom, self.rtype, self.eidto) in nocheck: - return - rschema = self._cw.repo.schema[self.rtype] - if rschema.inlined and skip_inlined_relation_security( - self._cw, rschema, self.eidfrom): - return - rdef = rschema.rdef(self._cw.entity_metas(self.eidfrom)['type'], - self._cw.entity_metas(self.eidto)['type']) - rdef.check_perm(self._cw, 'delete', fromeid=self.eidfrom, toeid=self.eidto)