1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
1 # copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # |
3 # |
4 # This file is part of CubicWeb. |
4 # This file is part of CubicWeb. |
5 # |
5 # |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
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 import pkgutil |
|
23 import re |
22 import re |
24 from os.path import join, basename |
23 from os.path import join |
25 from hashlib import md5 |
24 from hashlib import md5 |
26 from logging import getLogger |
25 from logging import getLogger |
27 from warnings import warn |
26 from warnings import warn |
28 |
27 |
29 from six import PY2, text_type, string_types, add_metaclass |
28 from six import PY2, text_type, string_types, add_metaclass |
30 from six.moves import range |
29 from six.moves import range |
31 |
30 |
32 from logilab.common import tempattr |
|
33 from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty |
31 from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty |
34 from logilab.common.logging_ext import set_log_methods |
32 from logilab.common.logging_ext import set_log_methods |
35 from logilab.common.deprecation import deprecated |
33 from logilab.common.deprecation import deprecated |
36 from logilab.common.textutils import splitstrip |
34 from logilab.common.textutils import splitstrip |
37 from logilab.common.graph import get_cycles |
35 from logilab.common.graph import get_cycles |
42 RelationDefinitionSchema, PermissionMixIn, role_name |
40 RelationDefinitionSchema, PermissionMixIn, role_name |
43 from yams.constraints import (BaseConstraint, FormatConstraint, |
41 from yams.constraints import (BaseConstraint, FormatConstraint, |
44 cstr_json_dumps, cstr_json_loads) |
42 cstr_json_dumps, cstr_json_loads) |
45 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader, |
43 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader, |
46 cleanup_sys_modules, fill_schema_from_namespace) |
44 cleanup_sys_modules, fill_schema_from_namespace) |
47 |
45 from yams.buildobjs import _add_relation as yams_add_relation |
48 from rql import parse, nodes, RQLSyntaxError, TypeResolverException |
46 |
|
47 from rql import parse, nodes, stmts, RQLSyntaxError, TypeResolverException |
49 from rql.analyze import ETypeResolver |
48 from rql.analyze import ETypeResolver |
50 |
49 |
51 import cubicweb |
50 import cubicweb |
|
51 from cubicweb import server |
52 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _ |
52 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _ |
53 |
53 |
54 from cubicweb import server |
|
55 |
54 |
56 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',)) |
55 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',)) |
57 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',)) |
56 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',)) |
58 |
57 |
59 # set of meta-relations available for every entity types |
58 # set of meta-relations available for every entity types |
600 def ERSchema_display_name(self, req, form='', context=None): |
599 def ERSchema_display_name(self, req, form='', context=None): |
601 """return a internationalized string for the entity/relation type name in |
600 """return a internationalized string for the entity/relation type name in |
602 a given form |
601 a given form |
603 """ |
602 """ |
604 return display_name(req, self.type, form, context) |
603 return display_name(req, self.type, form, context) |
|
604 |
|
605 |
605 ERSchema.display_name = ERSchema_display_name |
606 ERSchema.display_name = ERSchema_display_name |
606 |
607 |
607 |
608 |
608 @cached |
609 @cached |
609 def get_groups(self, action): |
610 def get_groups(self, action): |
619 assert action in self.ACTIONS, action |
620 assert action in self.ACTIONS, action |
620 try: |
621 try: |
621 return frozenset(g for g in self.permissions[action] if isinstance(g, string_types)) |
622 return frozenset(g for g in self.permissions[action] if isinstance(g, string_types)) |
622 except KeyError: |
623 except KeyError: |
623 return () |
624 return () |
|
625 |
|
626 |
624 PermissionMixIn.get_groups = get_groups |
627 PermissionMixIn.get_groups = get_groups |
625 |
628 |
626 |
629 |
627 @cached |
630 @cached |
628 def get_rqlexprs(self, action): |
631 def get_rqlexprs(self, action): |
638 assert action in self.ACTIONS, action |
641 assert action in self.ACTIONS, action |
639 try: |
642 try: |
640 return tuple(g for g in self.permissions[action] if not isinstance(g, string_types)) |
643 return tuple(g for g in self.permissions[action] if not isinstance(g, string_types)) |
641 except KeyError: |
644 except KeyError: |
642 return () |
645 return () |
|
646 |
|
647 |
643 PermissionMixIn.get_rqlexprs = get_rqlexprs |
648 PermissionMixIn.get_rqlexprs = get_rqlexprs |
644 |
649 |
645 |
650 |
646 def set_action_permissions(self, action, permissions): |
651 def set_action_permissions(self, action, permissions): |
647 """set the groups and rql expressions allowing to perform <action> on |
652 """set the groups and rql expressions allowing to perform <action> on |
654 :param permissions: the groups and rql expressions allowing the given action |
659 :param permissions: the groups and rql expressions allowing the given action |
655 """ |
660 """ |
656 orig_set_action_permissions(self, action, tuple(permissions)) |
661 orig_set_action_permissions(self, action, tuple(permissions)) |
657 clear_cache(self, 'get_rqlexprs') |
662 clear_cache(self, 'get_rqlexprs') |
658 clear_cache(self, 'get_groups') |
663 clear_cache(self, 'get_groups') |
|
664 |
|
665 |
659 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
666 orig_set_action_permissions = PermissionMixIn.set_action_permissions |
660 PermissionMixIn.set_action_permissions = set_action_permissions |
667 PermissionMixIn.set_action_permissions = set_action_permissions |
661 |
668 |
662 |
669 |
663 def has_local_role(self, action): |
670 def has_local_role(self, action): |
671 if self.get_rqlexprs(action): |
678 if self.get_rqlexprs(action): |
672 return True |
679 return True |
673 if action in ('update', 'delete'): |
680 if action in ('update', 'delete'): |
674 return 'owners' in self.get_groups(action) |
681 return 'owners' in self.get_groups(action) |
675 return False |
682 return False |
|
683 |
|
684 |
676 PermissionMixIn.has_local_role = has_local_role |
685 PermissionMixIn.has_local_role = has_local_role |
677 |
686 |
678 |
687 |
679 def may_have_permission(self, action, req): |
688 def may_have_permission(self, action, req): |
680 if action != 'read' and not (self.has_local_role('read') or |
689 if action != 'read' and not (self.has_local_role('read') or |
681 self.has_perm(req, 'read')): |
690 self.has_perm(req, 'read')): |
682 return False |
691 return False |
683 return self.has_local_role(action) or self.has_perm(req, action) |
692 return self.has_local_role(action) or self.has_perm(req, action) |
|
693 |
|
694 |
684 PermissionMixIn.may_have_permission = may_have_permission |
695 PermissionMixIn.may_have_permission = may_have_permission |
685 |
696 |
686 |
697 |
687 def has_perm(self, _cw, action, **kwargs): |
698 def has_perm(self, _cw, action, **kwargs): |
688 """return true if the action is granted globally or locally""" |
699 """return true if the action is granted globally or locally""" |
689 try: |
700 try: |
690 self.check_perm(_cw, action, **kwargs) |
701 self.check_perm(_cw, action, **kwargs) |
691 return True |
702 return True |
692 except Unauthorized: |
703 except Unauthorized: |
693 return False |
704 return False |
|
705 |
|
706 |
694 PermissionMixIn.has_perm = has_perm |
707 PermissionMixIn.has_perm = has_perm |
695 |
708 |
696 |
709 |
697 def check_perm(self, _cw, action, **kwargs): |
710 def check_perm(self, _cw, action, **kwargs): |
698 # NB: _cw may be a server transaction or a request object. |
711 # NB: _cw may be a server transaction or a request object. |
732 for rqlexpr in self.get_rqlexprs(action)])) |
745 for rqlexpr in self.get_rqlexprs(action)])) |
733 if any(rqlexpr.check(_cw, **kwargs) |
746 if any(rqlexpr.check(_cw, **kwargs) |
734 for rqlexpr in self.get_rqlexprs(action)): |
747 for rqlexpr in self.get_rqlexprs(action)): |
735 return |
748 return |
736 raise Unauthorized(action, str(self)) |
749 raise Unauthorized(action, str(self)) |
|
750 |
|
751 |
737 PermissionMixIn.check_perm = check_perm |
752 PermissionMixIn.check_perm = check_perm |
738 |
753 |
739 |
754 |
740 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None |
755 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None |
741 # remember rproperties defined at this point. Others will have to be serialized in |
756 # remember rproperties defined at this point. Others will have to be serialized in |
816 |
831 |
817 def main_attribute(self): |
832 def main_attribute(self): |
818 """convenience method that returns the *main* (i.e. the first non meta) |
833 """convenience method that returns the *main* (i.e. the first non meta) |
819 attribute defined in the entity schema |
834 attribute defined in the entity schema |
820 """ |
835 """ |
821 for rschema, _ in self.attribute_definitions(): |
836 for rschema, __ in self.attribute_definitions(): |
822 if not (rschema in META_RTYPES |
837 if not (rschema in META_RTYPES |
823 or self.is_metadata(rschema)): |
838 or self.is_metadata(rschema)): |
824 return rschema |
839 return rschema |
825 |
840 |
826 def add_subject_relation(self, rschema): |
841 def add_subject_relation(self, rschema): |
1260 # dunno if the validation error `occurred` on eidfrom or eidto (from |
1275 # dunno if the validation error `occurred` on eidfrom or eidto (from |
1261 # user interface point of view) |
1276 # user interface point of view) |
1262 # |
1277 # |
1263 # possible enhancement: check entity being created, it's probably |
1278 # possible enhancement: check entity being created, it's probably |
1264 # the main eid unless this is a composite relation |
1279 # the main eid unless this is a composite relation |
1265 if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars: |
1280 if eidto is None or 'S' in self.mainvars or 'O' not in self.mainvars: |
1266 maineid = eidfrom |
1281 maineid = eidfrom |
1267 qname = role_name(rtype, 'subject') |
1282 qname = role_name(rtype, 'subject') |
1268 else: |
1283 else: |
1269 maineid = eidto |
1284 maineid = eidto |
1270 qname = role_name(rtype, 'object') |
1285 qname = role_name(rtype, 'object') |
1271 if self.msg: |
1286 if self.msg: |
1272 msg = session._(self.msg) |
1287 msg = session._(self.msg) |
1273 else: |
1288 else: |
1274 msg = '%(constraint)s %(expression)s failed' % { |
1289 msg = '%(constraint)s %(expression)s failed' % { |
1275 'constraint': session._(self.type()), |
1290 'constraint': session._(self.type()), |
1276 'expression': self.expression} |
1291 'expression': self.expression} |
1277 raise ValidationError(maineid, {qname: msg}) |
1292 raise ValidationError(maineid, {qname: msg}) |
1278 |
1293 |
1279 def exec_query(self, _cw, eidfrom, eidto): |
1294 def exec_query(self, _cw, eidfrom, eidto): |
1280 if eidto is None: |
1295 if eidto is None: |
1318 return len(self.exec_query(session, eidfrom, eidto)) <= 1 |
1333 return len(self.exec_query(session, eidfrom, eidto)) <= 1 |
1319 |
1334 |
1320 |
1335 |
1321 # workflow extensions ######################################################### |
1336 # workflow extensions ######################################################### |
1322 |
1337 |
1323 from yams.buildobjs import _add_relation as yams_add_relation |
|
1324 |
|
1325 |
|
1326 class workflowable_definition(ybo.metadefinition): |
1338 class workflowable_definition(ybo.metadefinition): |
1327 """extends default EntityType's metaclass to add workflow relations |
1339 """extends default EntityType's metaclass to add workflow relations |
1328 (i.e. in_state, wf_info_for and custom_workflow). This is the default |
1340 (i.e. in_state, wf_info_for and custom_workflow). This is the default |
1329 metaclass for WorkflowableEntityType. |
1341 metaclass for WorkflowableEntityType. |
1330 """ |
1342 """ |
1407 """return a Schema instance from the schema definition read |
1419 """return a Schema instance from the schema definition read |
1408 from <directory> |
1420 from <directory> |
1409 """ |
1421 """ |
1410 self.info('loading %s schemas', ', '.join(config.cubes())) |
1422 self.info('loading %s schemas', ', '.join(config.cubes())) |
1411 try: |
1423 try: |
1412 return super(CubicWebSchemaLoader, self).load(config, config.schema_modnames(), **kwargs) |
1424 return super(CubicWebSchemaLoader, self).load( |
|
1425 config, config.schema_modnames(), **kwargs) |
1413 finally: |
1426 finally: |
1414 # we've to cleanup modules imported from cubicweb.schemas as well |
1427 # we've to cleanup modules imported from cubicweb.schemas as well |
1415 cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')]) |
1428 cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')]) |
1416 |
1429 |
1417 # these are overridden by set_log_methods below |
1430 # these are overridden by set_log_methods below |
1446 hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT) |
1459 hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT) |
1447 if hasperm: |
1460 if hasperm: |
1448 return self.regular_formats + tuple(NEED_PERM_FORMATS) |
1461 return self.regular_formats + tuple(NEED_PERM_FORMATS) |
1449 return self.regular_formats |
1462 return self.regular_formats |
1450 |
1463 |
|
1464 |
1451 # XXX itou for some Statement methods |
1465 # XXX itou for some Statement methods |
1452 from rql import stmts |
|
1453 |
|
1454 |
1466 |
1455 def bw_get_etype(self, name): |
1467 def bw_get_etype(self, name): |
1456 return orig_get_etype(self, bw_normalize_etype(name)) |
1468 return orig_get_etype(self, bw_normalize_etype(name)) |
|
1469 |
|
1470 |
1457 orig_get_etype = stmts.ScopeNode.get_etype |
1471 orig_get_etype = stmts.ScopeNode.get_etype |
1458 stmts.ScopeNode.get_etype = bw_get_etype |
1472 stmts.ScopeNode.get_etype = bw_get_etype |
1459 |
1473 |
1460 |
1474 |
1461 def bw_add_main_variable_delete(self, etype, vref): |
1475 def bw_add_main_variable_delete(self, etype, vref): |
1462 return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref) |
1476 return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref) |
|
1477 |
|
1478 |
1463 orig_add_main_variable_delete = stmts.Delete.add_main_variable |
1479 orig_add_main_variable_delete = stmts.Delete.add_main_variable |
1464 stmts.Delete.add_main_variable = bw_add_main_variable_delete |
1480 stmts.Delete.add_main_variable = bw_add_main_variable_delete |
1465 |
1481 |
1466 |
1482 |
1467 def bw_add_main_variable_insert(self, etype, vref): |
1483 def bw_add_main_variable_insert(self, etype, vref): |
1468 return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref) |
1484 return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref) |
|
1485 |
|
1486 |
1469 orig_add_main_variable_insert = stmts.Insert.add_main_variable |
1487 orig_add_main_variable_insert = stmts.Insert.add_main_variable |
1470 stmts.Insert.add_main_variable = bw_add_main_variable_insert |
1488 stmts.Insert.add_main_variable = bw_add_main_variable_insert |
1471 |
1489 |
1472 |
1490 |
1473 def bw_set_statement_type(self, etype): |
1491 def bw_set_statement_type(self, etype): |
1474 return orig_set_statement_type(self, bw_normalize_etype(etype)) |
1492 return orig_set_statement_type(self, bw_normalize_etype(etype)) |
|
1493 |
|
1494 |
1475 orig_set_statement_type = stmts.Select.set_statement_type |
1495 orig_set_statement_type = stmts.Select.set_statement_type |
1476 stmts.Select.set_statement_type = bw_set_statement_type |
1496 stmts.Select.set_statement_type = bw_set_statement_type |