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) |
45 rdef = eschema.rdef(attr) |
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. |
63 # _exception_ to the `check attr update perms at entity create & |
64 raise Unauthorized(action, str(rdef)) |
64 # update time` rule for this case. |
65 rdef.check_perm(session, action, eid=eid) |
65 continue |
|
66 rdef.check_perm(session, 'update', eid=eid) |
|
67 |
66 |
68 |
67 |
69 class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation): |
68 class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation): |
70 def precommit_event(self): |
69 def precommit_event(self): |
71 session = self.session |
70 session = self.session |
72 for eid, action, edited in self.get_data(): |
71 for eid, action, edited in self.get_data(): |
73 entity = session.entity_from_eid(eid) |
72 entity = session.entity_from_eid(eid) |
74 entity.cw_check_perm(action) |
73 entity.cw_check_perm(action) |
75 check_entity_attributes(session, entity, edited, |
74 check_entity_attributes(session, entity, action, edited) |
76 creation=(action == 'add')) |
|
77 |
75 |
78 |
76 |
79 class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation): |
77 class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation): |
80 def precommit_event(self): |
78 def precommit_event(self): |
81 session = self.session |
79 session = self.session |