17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
18 """classes to define schemas for CubicWeb""" |
18 """classes to define schemas for CubicWeb""" |
19 |
19 |
20 from __future__ import print_function |
20 from __future__ import print_function |
21 |
21 |
|
22 from functools import wraps |
22 import re |
23 import re |
23 from os.path import join |
24 from os.path import join |
24 from hashlib import md5 |
25 from hashlib import md5 |
25 from logging import getLogger |
26 from logging import getLogger |
26 from warnings import warn |
27 from warnings import warn |
592 return text_type(req.pgettext(context, key)) |
593 return text_type(req.pgettext(context, key)) |
593 else: |
594 else: |
594 return text_type(req._(key)) |
595 return text_type(req._(key)) |
595 |
596 |
596 |
597 |
|
598 def _override_method(cls, method_name=None, pass_original=False): |
|
599 """Override (or set) a method on `cls`.""" |
|
600 def decorator(func): |
|
601 name = method_name or func.__name__ |
|
602 orig = None |
|
603 if pass_original: |
|
604 orig = getattr(cls, name) |
|
605 |
|
606 @wraps(func) |
|
607 def wrapper(*args, **kwargs): |
|
608 if orig is not None: |
|
609 kwargs['_orig'] = orig |
|
610 return func(*args, **kwargs) |
|
611 |
|
612 setattr(cls, name, wrapper) |
|
613 |
|
614 return decorator |
|
615 |
|
616 |
597 # Schema objects definition ################################################### |
617 # Schema objects definition ################################################### |
598 |
618 |
|
619 @_override_method(ERSchema, 'display_name') |
599 def ERSchema_display_name(self, req, form='', context=None): |
620 def ERSchema_display_name(self, req, form='', context=None): |
600 """return a internationalized string for the entity/relation type name in |
621 """return a internationalized string for the entity/relation type name in |
601 a given form |
622 a given form |
602 """ |
623 """ |
603 return display_name(req, self.type, form, context) |
624 return display_name(req, self.type, form, context) |
604 |
625 |
605 |
626 |
606 ERSchema.display_name = ERSchema_display_name |
627 @_override_method(PermissionMixIn) |
607 |
|
608 |
|
609 @cached |
628 @cached |
610 def get_groups(self, action): |
629 def get_groups(self, action): |
611 """return the groups authorized to perform <action> on entities of |
630 """return the groups authorized to perform <action> on entities of |
612 this type |
631 this type |
613 |
632 |
622 return frozenset(g for g in self.permissions[action] if isinstance(g, string_types)) |
641 return frozenset(g for g in self.permissions[action] if isinstance(g, string_types)) |
623 except KeyError: |
642 except KeyError: |
624 return () |
643 return () |
625 |
644 |
626 |
645 |
627 PermissionMixIn.get_groups = get_groups |
646 @_override_method(PermissionMixIn) |
628 |
|
629 |
|
630 @cached |
647 @cached |
631 def get_rqlexprs(self, action): |
648 def get_rqlexprs(self, action): |
632 """return the rql expressions representing queries to check the user is allowed |
649 """return the rql expressions representing queries to check the user is allowed |
633 to perform <action> on entities of this type |
650 to perform <action> on entities of this type |
634 |
651 |
643 return tuple(g for g in self.permissions[action] if not isinstance(g, string_types)) |
660 return tuple(g for g in self.permissions[action] if not isinstance(g, string_types)) |
644 except KeyError: |
661 except KeyError: |
645 return () |
662 return () |
646 |
663 |
647 |
664 |
648 PermissionMixIn.get_rqlexprs = get_rqlexprs |
665 @_override_method(PermissionMixIn, pass_original=True) |
649 |
666 def set_action_permissions(self, action, permissions, _orig): |
650 |
|
651 def set_action_permissions(self, action, permissions): |
|
652 """set the groups and rql expressions allowing to perform <action> on |
667 """set the groups and rql expressions allowing to perform <action> on |
653 entities of this type |
668 entities of this type |
654 |
669 |
655 :type action: str |
670 :type action: str |
656 :param action: the name of a permission |
671 :param action: the name of a permission |
657 |
672 |
658 :type permissions: tuple |
673 :type permissions: tuple |
659 :param permissions: the groups and rql expressions allowing the given action |
674 :param permissions: the groups and rql expressions allowing the given action |
660 """ |
675 """ |
661 orig_set_action_permissions(self, action, tuple(permissions)) |
676 _orig(self, action, tuple(permissions)) |
662 clear_cache(self, 'get_rqlexprs') |
677 clear_cache(self, 'get_rqlexprs') |
663 clear_cache(self, 'get_groups') |
678 clear_cache(self, 'get_groups') |
664 |
679 |
665 |
680 |
666 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
681 @_override_method(PermissionMixIn) |
667 PermissionMixIn.set_action_permissions = set_action_permissions |
|
668 |
|
669 |
|
670 def has_local_role(self, action): |
682 def has_local_role(self, action): |
671 """return true if the action *may* be granted locally (i.e. either rql |
683 """return true if the action *may* be granted locally (i.e. either rql |
672 expressions or the owners group are used in security definition) |
684 expressions or the owners group are used in security definition) |
673 |
685 |
674 XXX this method is only there since we don't know well how to deal with |
686 XXX this method is only there since we don't know well how to deal with |
680 if action in ('update', 'delete'): |
692 if action in ('update', 'delete'): |
681 return 'owners' in self.get_groups(action) |
693 return 'owners' in self.get_groups(action) |
682 return False |
694 return False |
683 |
695 |
684 |
696 |
685 PermissionMixIn.has_local_role = has_local_role |
697 @_override_method(PermissionMixIn) |
686 |
|
687 |
|
688 def may_have_permission(self, action, req): |
698 def may_have_permission(self, action, req): |
689 if action != 'read' and not (self.has_local_role('read') or |
699 if action != 'read' and not (self.has_local_role('read') or |
690 self.has_perm(req, 'read')): |
700 self.has_perm(req, 'read')): |
691 return False |
701 return False |
692 return self.has_local_role(action) or self.has_perm(req, action) |
702 return self.has_local_role(action) or self.has_perm(req, action) |
693 |
703 |
694 |
704 |
695 PermissionMixIn.may_have_permission = may_have_permission |
705 @_override_method(PermissionMixIn) |
696 |
|
697 |
|
698 def has_perm(self, _cw, action, **kwargs): |
706 def has_perm(self, _cw, action, **kwargs): |
699 """return true if the action is granted globally or locally""" |
707 """return true if the action is granted globally or locally""" |
700 try: |
708 try: |
701 self.check_perm(_cw, action, **kwargs) |
709 self.check_perm(_cw, action, **kwargs) |
702 return True |
710 return True |
703 except Unauthorized: |
711 except Unauthorized: |
704 return False |
712 return False |
705 |
713 |
706 |
714 |
707 PermissionMixIn.has_perm = has_perm |
715 @_override_method(PermissionMixIn) |
708 |
|
709 |
|
710 def check_perm(self, _cw, action, **kwargs): |
716 def check_perm(self, _cw, action, **kwargs): |
711 # NB: _cw may be a server transaction or a request object. |
717 # NB: _cw may be a server transaction or a request object. |
712 # |
718 # |
713 # check user is in an allowed group, if so that's enough internal |
719 # check user is in an allowed group, if so that's enough internal |
714 # transactions should always stop there |
720 # transactions should always stop there |
745 for rqlexpr in self.get_rqlexprs(action)])) |
751 for rqlexpr in self.get_rqlexprs(action)])) |
746 if any(rqlexpr.check(_cw, **kwargs) |
752 if any(rqlexpr.check(_cw, **kwargs) |
747 for rqlexpr in self.get_rqlexprs(action)): |
753 for rqlexpr in self.get_rqlexprs(action)): |
748 return |
754 return |
749 raise Unauthorized(action, str(self)) |
755 raise Unauthorized(action, str(self)) |
750 |
|
751 |
|
752 PermissionMixIn.check_perm = check_perm |
|
753 |
756 |
754 |
757 |
755 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None |
758 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None |
756 # remember rproperties defined at this point. Others will have to be serialized in |
759 # remember rproperties defined at this point. Others will have to be serialized in |
757 # CWAttribute.extra_props |
760 # CWAttribute.extra_props |
1462 return self.regular_formats |
1465 return self.regular_formats |
1463 |
1466 |
1464 |
1467 |
1465 # XXX itou for some Statement methods |
1468 # XXX itou for some Statement methods |
1466 |
1469 |
1467 def bw_get_etype(self, name): |
1470 @_override_method(stmts.ScopeNode, pass_original=True) |
1468 return orig_get_etype(self, bw_normalize_etype(name)) |
1471 def get_etype(self, name, _orig): |
1469 |
1472 return _orig(self, bw_normalize_etype(name)) |
1470 |
1473 |
1471 orig_get_etype = stmts.ScopeNode.get_etype |
1474 |
1472 stmts.ScopeNode.get_etype = bw_get_etype |
1475 @_override_method(stmts.Delete, method_name='add_main_variable', |
1473 |
1476 pass_original=True) |
1474 |
1477 def _add_main_variable_delete(self, etype, vref, _orig): |
1475 def bw_add_main_variable_delete(self, etype, vref): |
1478 return _orig(self, bw_normalize_etype(etype), vref) |
1476 return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref) |
1479 |
1477 |
1480 |
1478 |
1481 @_override_method(stmts.Insert, method_name='add_main_variable', |
1479 orig_add_main_variable_delete = stmts.Delete.add_main_variable |
1482 pass_original=True) |
1480 stmts.Delete.add_main_variable = bw_add_main_variable_delete |
1483 def _add_main_variable_insert(self, etype, vref, _orig): |
1481 |
1484 return _orig(self, bw_normalize_etype(etype), vref) |
1482 |
1485 |
1483 def bw_add_main_variable_insert(self, etype, vref): |
1486 |
1484 return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref) |
1487 @_override_method(stmts.Select, pass_original=True) |
1485 |
1488 def set_statement_type(self, etype, _orig): |
1486 |
1489 return _orig(self, bw_normalize_etype(etype)) |
1487 orig_add_main_variable_insert = stmts.Insert.add_main_variable |
|
1488 stmts.Insert.add_main_variable = bw_add_main_variable_insert |
|
1489 |
|
1490 |
|
1491 def bw_set_statement_type(self, etype): |
|
1492 return orig_set_statement_type(self, bw_normalize_etype(etype)) |
|
1493 |
|
1494 |
|
1495 orig_set_statement_type = stmts.Select.set_statement_type |
|
1496 stmts.Select.set_statement_type = bw_set_statement_type |
|