cubicweb/schema.py
changeset 12258 46a8146f9703
parent 12210 3fa6c9ef2f51
child 12355 c703dc95c82e
equal deleted inserted replaced
12257:39cd3c7eb2e8 12258:46a8146f9703
    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