138 :return: names of the groups with the given permission |
139 :return: names of the groups with the given permission |
139 """ |
140 """ |
140 assert action in self.ACTIONS, action |
141 assert action in self.ACTIONS, action |
141 #assert action in self._groups, '%s %s' % (self, action) |
142 #assert action in self._groups, '%s %s' % (self, action) |
142 try: |
143 try: |
143 return frozenset(g for g in self._groups[action] if isinstance(g, basestring)) |
144 return frozenset(g for g in self.permissions[action] if isinstance(g, basestring)) |
144 except KeyError: |
145 except KeyError: |
145 return () |
146 return () |
146 ERSchema.get_groups = ERSchema_get_groups |
147 PermissionMixIn.get_groups = get_groups |
147 |
|
148 def ERSchema_set_groups(self, action, groups): |
|
149 """set the groups allowed to perform <action> on entities of this type. Don't |
|
150 change rql expressions for the same action. |
|
151 |
|
152 :type action: str |
|
153 :param action: the name of a permission |
|
154 |
|
155 :type groups: list or tuple |
|
156 :param groups: names of the groups granted to do the given action |
|
157 """ |
|
158 assert action in self.ACTIONS, action |
|
159 clear_cache(self, 'ERSchema_get_groups') |
|
160 self._groups[action] = tuple(groups) + self.get_rqlexprs(action) |
|
161 ERSchema.set_groups = ERSchema_set_groups |
|
162 |
148 |
163 @cached |
149 @cached |
164 def ERSchema_get_rqlexprs(self, action): |
150 def get_rqlexprs(self, action): |
165 """return the rql expressions representing queries to check the user is allowed |
151 """return the rql expressions representing queries to check the user is allowed |
166 to perform <action> on entities of this type |
152 to perform <action> on entities of this type |
167 |
153 |
168 :type action: str |
154 :type action: str |
169 :param action: the name of a permission |
155 :param action: the name of a permission |
172 :return: the rql expressions with the given permission |
158 :return: the rql expressions with the given permission |
173 """ |
159 """ |
174 assert action in self.ACTIONS, action |
160 assert action in self.ACTIONS, action |
175 #assert action in self._rqlexprs, '%s %s' % (self, action) |
161 #assert action in self._rqlexprs, '%s %s' % (self, action) |
176 try: |
162 try: |
177 return tuple(g for g in self._groups[action] if not isinstance(g, basestring)) |
163 return tuple(g for g in self.permissions[action] if not isinstance(g, basestring)) |
178 except KeyError: |
164 except KeyError: |
179 return () |
165 return () |
180 ERSchema.get_rqlexprs = ERSchema_get_rqlexprs |
166 PermissionMixIn.get_rqlexprs = get_rqlexprs |
181 |
167 |
182 def ERSchema_set_rqlexprs(self, action, rqlexprs): |
168 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
183 """set the rql expression allowing to perform <action> on entities of this type. Don't |
169 def set_action_permissions(self, action, permissions): |
184 change groups for the same action. |
170 """set the groups and rql expressions allowing to perform <action> on |
|
171 entities of this type |
185 |
172 |
186 :type action: str |
173 :type action: str |
187 :param action: the name of a permission |
174 :param action: the name of a permission |
188 |
175 |
189 :type rqlexprs: list or tuple |
|
190 :param rqlexprs: the rql expressions allowing the given action |
|
191 """ |
|
192 assert action in self.ACTIONS, action |
|
193 clear_cache(self, 'ERSchema_get_rqlexprs') |
|
194 self._groups[action] = tuple(self.get_groups(action)) + tuple(rqlexprs) |
|
195 ERSchema.set_rqlexprs = ERSchema_set_rqlexprs |
|
196 |
|
197 def ERSchema_set_permissions(self, action, permissions): |
|
198 """set the groups and rql expressions allowing to perform <action> on |
|
199 entities of this type |
|
200 |
|
201 :type action: str |
|
202 :param action: the name of a permission |
|
203 |
|
204 :type permissions: tuple |
176 :type permissions: tuple |
205 :param permissions: the groups and rql expressions allowing the given action |
177 :param permissions: the groups and rql expressions allowing the given action |
206 """ |
178 """ |
207 assert action in self.ACTIONS, action |
179 orig_set_action_permissions(self, action, tuple(permissions)) |
208 clear_cache(self, 'ERSchema_get_rqlexprs') |
180 clear_cache(self, 'get_rqlexprs') |
209 clear_cache(self, 'ERSchema_get_groups') |
181 clear_cache(self, 'get_groups') |
210 self._groups[action] = tuple(permissions) |
182 PermissionMixIn.set_action_permissions = set_action_permissions |
211 ERSchema.set_permissions = ERSchema_set_permissions |
183 |
212 |
184 def has_local_role(self, action): |
213 def ERSchema_has_perm(self, session, action, *args, **kwargs): |
|
214 """return true if the action is granted globaly or localy""" |
|
215 try: |
|
216 self.check_perm(session, action, *args, **kwargs) |
|
217 return True |
|
218 except Unauthorized: |
|
219 return False |
|
220 ERSchema.has_perm = ERSchema_has_perm |
|
221 |
|
222 def ERSchema_has_local_role(self, action): |
|
223 """return true if the action *may* be granted localy (eg either rql |
185 """return true if the action *may* be granted localy (eg either rql |
224 expressions or the owners group are used in security definition) |
186 expressions or the owners group are used in security definition) |
225 |
187 |
226 XXX this method is only there since we don't know well how to deal with |
188 XXX this method is only there since we don't know well how to deal with |
227 'add' action checking. Also find a better name would be nice. |
189 'add' action checking. Also find a better name would be nice. |
228 """ |
190 """ |
229 assert action in self.ACTIONS, action |
191 assert action in self.ACTIONS, action |
230 if self.get_rqlexprs(action): |
192 if self.get_rqlexprs(action): |
231 return True |
193 return True |
232 if action in ('update', 'delete'): |
194 if action in ('update', 'delete'): |
233 return self.has_group(action, 'owners') |
195 return 'owners' in self.get_groups(action) |
234 return False |
196 return False |
235 ERSchema.has_local_role = ERSchema_has_local_role |
197 PermissionMixIn.has_local_role = has_local_role |
|
198 |
|
199 def may_have_permission(self, action, req): |
|
200 if action != 'read' and not (self.has_local_role('read') or |
|
201 self.has_perm(req, 'read')): |
|
202 return False |
|
203 return self.has_local_role(action) or self.has_perm(req, action) |
|
204 PermissionMixIn.may_have_permission = may_have_permission |
|
205 |
|
206 def has_perm(self, session, action, **kwargs): |
|
207 """return true if the action is granted globaly or localy""" |
|
208 try: |
|
209 self.check_perm(session, action, **kwargs) |
|
210 return True |
|
211 except Unauthorized: |
|
212 return False |
|
213 PermissionMixIn.has_perm = has_perm |
|
214 |
|
215 def check_perm(self, session, action, **kwargs): |
|
216 # NB: session may be a server session or a request object check user is |
|
217 # in an allowed group, if so that's enough internal sessions should |
|
218 # always stop there |
|
219 groups = self.get_groups(action) |
|
220 if session.user.matching_groups(groups): |
|
221 return |
|
222 # if 'owners' in allowed groups, check if the user actually owns this |
|
223 # object, if so that's enough |
|
224 if 'owners' in groups and 'eid' in kwargs and session.user.owns(kwargs['eid']): |
|
225 return |
|
226 # else if there is some rql expressions, check them |
|
227 if any(rqlexpr.check(session, **kwargs) |
|
228 for rqlexpr in self.get_rqlexprs(action)): |
|
229 return |
|
230 raise Unauthorized(action, str(self)) |
|
231 PermissionMixIn.check_perm = check_perm |
|
232 |
|
233 |
|
234 RelationDefinitionSchema._RPROPERTIES['eid'] = None |
|
235 |
|
236 def rql_expression(self, expression, mainvars=None, eid=None): |
|
237 """rql expression factory""" |
|
238 if self.rtype.final: |
|
239 return ERQLExpression(expression, mainvars, eid) |
|
240 return RRQLExpression(expression, mainvars, eid) |
|
241 RelationDefinitionSchema.rql_expression = rql_expression |
|
242 |
|
243 orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions |
|
244 def check_permission_definitions(self): |
|
245 orig_check_permission_definitions(self) |
|
246 schema = self.subject.schema |
|
247 for action, groups in self.permissions.iteritems(): |
|
248 for group_or_rqlexpr in groups: |
|
249 if action == 'read' and \ |
|
250 isinstance(group_or_rqlexpr, RQLExpression): |
|
251 msg = "can't use rql expression for read permission of %s" |
|
252 raise BadSchemaDefinition(msg % self) |
|
253 elif self.final and isinstance(group_or_rqlexpr, RRQLExpression): |
|
254 if schema.reading_from_database: |
|
255 # we didn't have final relation earlier, so turn |
|
256 # RRQLExpression into ERQLExpression now |
|
257 rqlexpr = group_or_rqlexpr |
|
258 newrqlexprs = [x for x in self.get_rqlexprs(action) |
|
259 if not x is rqlexpr] |
|
260 newrqlexprs.append(ERQLExpression(rqlexpr.expression, |
|
261 rqlexpr.mainvars, |
|
262 rqlexpr.eid)) |
|
263 self.set_rqlexprs(action, newrqlexprs) |
|
264 else: |
|
265 msg = "can't use RRQLExpression on %s, use an ERQLExpression" |
|
266 raise BadSchemaDefinition(msg % self) |
|
267 elif not self.final and \ |
|
268 isinstance(group_or_rqlexpr, ERQLExpression): |
|
269 msg = "can't use ERQLExpression on %s, use a RRQLExpression" |
|
270 raise BadSchemaDefinition(msg % self) |
|
271 RelationDefinitionSchema.check_permission_definitions = check_permission_definitions |
236 |
272 |
237 |
273 |
238 def system_etypes(schema): |
274 def system_etypes(schema): |
239 """return system entity types only: skip final, schema and application entities |
275 """return system entity types only: skip final, schema and application entities |
240 """ |
276 """ |
327 |
363 |
328 def schema_entity(self): |
364 def schema_entity(self): |
329 """return True if this entity type is used to build the schema""" |
365 """return True if this entity type is used to build the schema""" |
330 return self.type in SCHEMA_TYPES |
366 return self.type in SCHEMA_TYPES |
331 |
367 |
332 def check_perm(self, session, action, eid=None): |
|
333 # NB: session may be a server session or a request object |
|
334 user = session.user |
|
335 # check user is in an allowed group, if so that's enough |
|
336 # internal sessions should always stop there |
|
337 if user.matching_groups(self.get_groups(action)): |
|
338 return |
|
339 # if 'owners' in allowed groups, check if the user actually owns this |
|
340 # object, if so that's enough |
|
341 if eid is not None and 'owners' in self.get_groups(action) and \ |
|
342 user.owns(eid): |
|
343 return |
|
344 # else if there is some rql expressions, check them |
|
345 if any(rqlexpr.check(session, eid) |
|
346 for rqlexpr in self.get_rqlexprs(action)): |
|
347 return |
|
348 raise Unauthorized(action, str(self)) |
|
349 |
|
350 def rql_expression(self, expression, mainvars=None, eid=None): |
368 def rql_expression(self, expression, mainvars=None, eid=None): |
351 """rql expression factory""" |
369 """rql expression factory""" |
352 return ERQLExpression(expression, mainvars, eid) |
370 return ERQLExpression(expression, mainvars, eid) |
353 |
371 |
354 |
372 |
355 class CubicWebRelationSchema(RelationSchema): |
373 class CubicWebRelationSchema(RelationSchema): |
356 RelationSchema._RPROPERTIES['eid'] = None |
|
357 _perms_checked = False |
|
358 |
374 |
359 def __init__(self, schema=None, rdef=None, eid=None, **kwargs): |
375 def __init__(self, schema=None, rdef=None, eid=None, **kwargs): |
360 if rdef is not None: |
376 if rdef is not None: |
361 # if this relation is inlined |
377 # if this relation is inlined |
362 self.inlined = rdef.inlined |
378 self.inlined = rdef.inlined |
367 |
383 |
368 @property |
384 @property |
369 def meta(self): |
385 def meta(self): |
370 return self.type in META_RTYPES |
386 return self.type in META_RTYPES |
371 |
387 |
372 def update(self, subjschema, objschema, rdef): |
|
373 super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef) |
|
374 if not self._perms_checked and self._groups: |
|
375 for action, groups in self._groups.iteritems(): |
|
376 for group_or_rqlexpr in groups: |
|
377 if action == 'read' and \ |
|
378 isinstance(group_or_rqlexpr, RQLExpression): |
|
379 msg = "can't use rql expression for read permission of "\ |
|
380 "a relation type (%s)" |
|
381 raise BadSchemaDefinition(msg % self.type) |
|
382 elif self.final and isinstance(group_or_rqlexpr, RRQLExpression): |
|
383 if self.schema.reading_from_database: |
|
384 # we didn't have final relation earlier, so turn |
|
385 # RRQLExpression into ERQLExpression now |
|
386 rqlexpr = group_or_rqlexpr |
|
387 newrqlexprs = [x for x in self.get_rqlexprs(action) if not x is rqlexpr] |
|
388 newrqlexprs.append(ERQLExpression(rqlexpr.expression, |
|
389 rqlexpr.mainvars, |
|
390 rqlexpr.eid)) |
|
391 self.set_rqlexprs(action, newrqlexprs) |
|
392 else: |
|
393 msg = "can't use RRQLExpression on a final relation "\ |
|
394 "type (eg attribute relation), use an ERQLExpression (%s)" |
|
395 raise BadSchemaDefinition(msg % self.type) |
|
396 elif not self.final and \ |
|
397 isinstance(group_or_rqlexpr, ERQLExpression): |
|
398 msg = "can't use ERQLExpression on a relation type, use "\ |
|
399 "a RRQLExpression (%s)" |
|
400 raise BadSchemaDefinition(msg % self.type) |
|
401 self._perms_checked = True |
|
402 |
|
403 def cardinality(self, subjtype, objtype, target): |
|
404 card = self.rproperty(subjtype, objtype, 'cardinality') |
|
405 return (target == 'subject' and card[0]) or \ |
|
406 (target == 'object' and card[1]) |
|
407 |
|
408 def schema_relation(self): |
388 def schema_relation(self): |
409 """return True if this relation type is used to build the schema""" |
389 """return True if this relation type is used to build the schema""" |
410 return self.type in SCHEMA_TYPES |
390 return self.type in SCHEMA_TYPES |
411 |
391 |
412 def physical_mode(self): |
392 def may_have_permission(self, action, req, eschema=None, role=None): |
413 """return an appropriate mode for physical storage of this relation type: |
393 if eschema is not None: |
414 * 'subjectinline' if every possible subject cardinalities are 1 or ? |
394 for tschema in rschema.targets(eschema, role): |
415 * 'objectinline' if 'subjectinline' mode is not possible but every |
395 rdef = rschema.role_rdef(eschema, tschema, role) |
416 possible object cardinalities are 1 or ? |
396 if rdef.may_have_permission(action, req): |
417 * None if neither 'subjectinline' and 'objectinline' |
397 return True |
418 """ |
398 else: |
419 assert not self.final |
399 for rdef in self.rdefs.itervalues(): |
420 return self.inlined and 'subjectinline' or None |
400 if rdef.may_have_permission(action, req): |
421 |
401 return True |
422 def check_perm(self, session, action, *args, **kwargs): |
402 return False |
423 # NB: session may be a server session or a request object check user is |
403 |
424 # in an allowed group, if so that's enough internal sessions should |
404 def has_perm(self, session, action, **kwargs): |
425 # always stop there |
405 """return true if the action is granted globaly or localy""" |
426 if session.user.matching_groups(self.get_groups(action)): |
406 if 'fromeid' in kwargs: |
427 return |
407 subjtype = session.describe(kwargs['fromeid']) |
428 # else if there is some rql expressions, check them |
408 else: |
429 if any(rqlexpr.check(session, *args, **kwargs) |
409 subjtype = None |
430 for rqlexpr in self.get_rqlexprs(action)): |
410 if 'toeid' in kwargs: |
431 return |
411 objtype = session.describe(kwargs['toeid']) |
432 raise Unauthorized(action, str(self)) |
412 else: |
433 |
413 objtype = Nono |
434 def rql_expression(self, expression, mainvars=None, eid=None): |
414 if objtype and subjtype: |
435 """rql expression factory""" |
415 return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs) |
436 if self.final: |
416 elif subjtype: |
437 return ERQLExpression(expression, mainvars, eid) |
417 for tschema in rschema.targets(subjtype, 'subject'): |
438 return RRQLExpression(expression, mainvars, eid) |
418 rdef = rschema.rdef(subjtype, tschema) |
|
419 if not rdef.has_perm(action, req, **kwargs): |
|
420 return False |
|
421 elif objtype: |
|
422 for tschema in rschema.targets(objtype, 'object'): |
|
423 rdef = rschema.rdef(tschema, objtype) |
|
424 if not rdef.has_perm(action, req, **kwargs): |
|
425 return False |
|
426 else: |
|
427 for rdef in self.rdefs.itervalues(): |
|
428 if not rdef.has_perm(action, req, **kwargs): |
|
429 return False |
|
430 |
|
431 @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)') |
|
432 def cardinality(self, subjtype, objtype, target): |
|
433 return self.rdef(subjtype, objtype).role_cardinality(target) |
439 |
434 |
440 |
435 |
441 class CubicWebSchema(Schema): |
436 class CubicWebSchema(Schema): |
442 """set of entities and relations schema defining the possible data sets |
437 """set of entities and relations schema defining the possible data sets |
443 used in an application |
438 used in an application |
506 :param: the newly created or just completed relation schema |
498 :param: the newly created or just completed relation schema |
507 """ |
499 """ |
508 rdef.name = rdef.name.lower() |
500 rdef.name = rdef.name.lower() |
509 rdef.subject = bw_normalize_etype(rdef.subject) |
501 rdef.subject = bw_normalize_etype(rdef.subject) |
510 rdef.object = bw_normalize_etype(rdef.object) |
502 rdef.object = bw_normalize_etype(rdef.object) |
511 if super(CubicWebSchema, self).add_relation_def(rdef): |
503 rdefs = super(CubicWebSchema, self).add_relation_def(rdef) |
|
504 if rdefs: |
512 try: |
505 try: |
513 self._eid_index[rdef.eid] = (self.eschema(rdef.subject), |
506 self._eid_index[rdef.eid] = rdefs |
514 self.rschema(rdef.name), |
|
515 self.eschema(rdef.object)) |
|
516 except AttributeError: |
507 except AttributeError: |
517 pass # not a serialized schema |
508 pass # not a serialized schema |
518 |
509 |
519 def del_relation_type(self, rtype): |
510 def del_relation_type(self, rtype): |
520 rschema = self.rschema(rtype) |
511 rschema = self.rschema(rtype) |
521 self._eid_index.pop(rschema.eid, None) |
512 self._eid_index.pop(rschema.eid, None) |
522 super(CubicWebSchema, self).del_relation_type(rtype) |
513 super(CubicWebSchema, self).del_relation_type(rtype) |
523 |
514 |
524 def del_relation_def(self, subjtype, rtype, objtype): |
515 def del_relation_def(self, subjtype, rtype, objtype): |
525 for k, v in self._eid_index.items(): |
516 for k, v in self._eid_index.items(): |
526 if v == (subjtype, rtype, objtype): |
517 if not isinstance(v, RelationDefinitionSchema): |
|
518 continue |
|
519 if v.subject == subjtype and v.rtype == rtype and v.object == objtype: |
527 del self._eid_index[k] |
520 del self._eid_index[k] |
528 break |
521 break |
529 super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype) |
522 super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype) |
530 |
523 |
531 def del_entity_type(self, etype): |
524 def del_entity_type(self, etype): |