Fix (de)serialization of ComputedRelation read permissions
authorJulien Cristau <julien.cristau@logilab.fr>
Tue, 28 Jul 2015 09:25:26 +0200
changeset 10553 1d824df4f2bd
parent 10552 0e7fab504305
child 10554 c39749d14c53
Fix (de)serialization of ComputedRelation read permissions For normal relation types, permissions don't need to be stored since they're just default values for the relation definitions. However, computed relations are serialized (as CWComputedRType), while their relation definitions are added at schema finalization time, and are only in memory. So add the 'read_permission' relation to CWComputedRType, and the appropriate hooks to save and restore those permissions. To avoid having to touch yams, we drop the 'add' and 'delete' permissions from the default computed relation permissions; this should probably be backported there. The actual permissions (set on the relation definitions) are hardcoded in finalize_computed_relations anyway. In deserialize_schema, the CWComputedRType handling needs to be delayed a little bit, until after we've called deserialize_ertype_permissions. The rql2sql test is adjusted because CWComputedRType has a 'name' attribute and the 'read_permission' relation, which generates ambiguity vs CWEType. We add an explicit CubicWebRelationSchema.check_permission_definitions, since we need to check both that computed and non-computed rtypes are defined properly. Based on report and initial patch from Christophe de Vienne (thanks!). Closes #5706307
misc/migration/3.21.1_Any.py
misc/migration/bootstrapmigration_repository.py
schema.py
schemas/bootstrap.py
server/migractions.py
server/schemaserial.py
server/test/data-cwep002/schema.py
server/test/unittest_rql2sql.py
server/test/unittest_schemaserial.py
test/unittest_schema.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.21.1_Any.py	Tue Jul 28 09:25:26 2015 +0200
@@ -0,0 +1,4 @@
+# re-read ComputedRelation permissions from schema.py now that we're
+# able to serialize them
+for computedrtype in schema.iter_computed_relations():
+    sync_schema_props_perms(computedrtype.type)
--- a/misc/migration/bootstrapmigration_repository.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Tue Jul 28 09:25:26 2015 +0200
@@ -434,6 +434,12 @@
 if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0):
     add_cube('card', update_database=False)
 
+
+if applcubicwebversion < (3, 21, 1) and cubicwebversion >= (3, 21, 1):
+    add_relation_definition('CWComputedRType', 'read_permission', 'CWGroup')
+    add_relation_definition('CWComputedRType', 'read_permission', 'RQLExpression')
+
+
 def sync_constraint_types():
     """Make sure the repository knows about all constraint types defined in the code"""
     from cubicweb.schema import CONSTRAINTS
--- a/schema.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/schema.py	Tue Jul 28 09:25:26 2015 +0200
@@ -32,6 +32,7 @@
 from logilab.common.textutils import splitstrip
 from logilab.common.graph import get_cycles
 
+import yams
 from yams import BadSchemaDefinition, buildobjs as ybo
 from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
      RelationDefinitionSchema, PermissionMixIn, role_name
@@ -462,6 +463,13 @@
 ybo.DEFAULT_ATTRPERMS['update'] = ('managers', ERQLExpression('U has_update_permission X'))
 ybo.DEFAULT_ATTRPERMS['add'] = ('managers', ERQLExpression('U has_add_permission X'))
 
+# we don't want 'add' or 'delete' permissions on computed relation types
+# (they're hardcoded to '()' on computed relation definitions)
+if 'add' in yams.DEFAULT_COMPUTED_RELPERMS:
+    del yams.DEFAULT_COMPUTED_RELPERMS['add']
+if 'delete' in yams.DEFAULT_COMPUTED_RELPERMS:
+    del yams.DEFAULT_COMPUTED_RELPERMS['delete']
+
 
 PUB_SYSTEM_ENTITY_PERMS = {
     'read':   ('managers', 'users', 'guests',),
@@ -859,7 +867,9 @@
         return ERQLExpression(expression, mainvars, eid)
 
 
-class CubicWebRelationSchema(RelationSchema):
+class CubicWebRelationSchema(PermissionMixIn, RelationSchema):
+    permissions = {}
+    ACTIONS = ()
 
     def __init__(self, schema=None, rdef=None, eid=None, **kwargs):
         if rdef is not None:
@@ -870,6 +880,17 @@
             eid = getattr(rdef, 'eid', None)
         self.eid = eid
 
+    def init_computed_relation(self, rdef):
+        self.ACTIONS = ('read',)
+        super(CubicWebRelationSchema, self).init_computed_relation(rdef)
+
+    def advertise_new_add_permission(self):
+        pass
+
+    def check_permission_definitions(self):
+        RelationSchema.check_permission_definitions(self)
+        PermissionMixIn.check_permission_definitions(self)
+
     @property
     def meta(self):
         return self.type in META_RTYPES
--- a/schemas/bootstrap.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/schemas/bootstrap.py	Tue Jul 28 09:25:26 2015 +0200
@@ -239,7 +239,7 @@
     """groups allowed to read entities/relations of this type"""
     __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'read_permission'
-    subject = ('CWEType', 'CWAttribute', 'CWRelation')
+    subject = ('CWEType', 'CWAttribute', 'CWRelation', 'CWComputedRType')
     object = 'CWGroup'
     cardinality = '**'
 
@@ -271,7 +271,7 @@
     """rql expression allowing to read entities/relations of this type"""
     __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'read_permission'
-    subject = ('CWEType', 'CWAttribute', 'CWRelation')
+    subject = ('CWEType', 'CWAttribute', 'CWRelation', 'CWComputedRType')
     object = 'RQLExpression'
     cardinality = '*?'
     composite = 'subject'
--- a/server/migractions.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/server/migractions.py	Tue Jul 28 09:25:26 2015 +0200
@@ -1020,7 +1020,8 @@
             print 'warning: relation type %s is already known, skip addition' % (
                 rtype)
         elif rschema.rule:
-            ss.execschemarql(execute, rschema, ss.crschema2rql(rschema))
+            gmap = self.group_mapping()
+            ss.execschemarql(execute, rschema, ss.crschema2rql(rschema, gmap))
         else:
             # register the relation into CWRType and insert necessary relation
             # definitions
--- a/server/schemaserial.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/server/schemaserial.py	Tue Jul 28 09:25:26 2015 +0200
@@ -92,14 +92,6 @@
     with cnx.ensure_cnx_set:
         tables = set(t.lower() for t in dbhelper.list_tables(cnx.cnxset.cu))
         has_computed_relations = 'cw_cwcomputedrtype' in tables
-    if has_computed_relations:
-        rset = cnx.execute(
-            'Any X, N, R, D WHERE X is CWComputedRType, X name N, '
-            'X rule R, X description D')
-        for eid, rule_name, rule, description in rset.rows:
-            rtype = ybo.ComputedRelation(name=rule_name, rule=rule, eid=eid,
-                                         description=description)
-            schema.add_relation_type(rtype)
     # computed attribute
     try:
         cnx.system_sql("SELECT cw_formula FROM cw_CWAttribute")
@@ -177,6 +169,15 @@
         stype = ETYPE_NAME_MAP.get(stype, stype)
         schema.eschema(etype)._specialized_type = stype
         schema.eschema(stype)._specialized_by.append(etype)
+    if has_computed_relations:
+        rset = cnx.execute(
+            'Any X, N, R, D WHERE X is CWComputedRType, X name N, '
+            'X rule R, X description D')
+        for eid, rule_name, rule, description in rset.rows:
+            rtype = ybo.ComputedRelation(name=rule_name, rule=rule, eid=eid,
+                                         description=description)
+            rschema = schema.add_relation_type(rtype)
+            set_perms(rschema, permsidx)
     # load every relation types
     for eid, rtype, desc, sym, il, ftc in cnx.execute(
         'Any X,N,D,S,I,FTC WHERE X is CWRType, X name N, X description D, '
@@ -375,7 +376,7 @@
             pb.update()
             continue
         if rschema.rule:
-            execschemarql(execute, rschema, crschema2rql(rschema))
+            execschemarql(execute, rschema, crschema2rql(rschema, groupmap))
             pb.update()
             continue
         execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
@@ -525,9 +526,12 @@
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-def crschema2rql(crschema):
+def crschema2rql(crschema, groupmap):
     relations, values = crschema_relations_values(crschema)
     yield 'INSERT CWComputedRType X: %s' % ','.join(relations), values
+    if groupmap:
+        for rql, args in _erperms2rql(crschema, groupmap):
+            yield rql, args
 
 def crschema_relations_values(crschema):
     values = _ervalues(crschema)
--- a/server/test/data-cwep002/schema.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/server/test/data-cwep002/schema.py	Tue Jul 28 09:25:26 2015 +0200
@@ -32,4 +32,5 @@
 
 class has_employee(ComputedRelation):
     rule = 'O works_for S'
+    __permissions__ = {'read': ('managers',)}
 
--- a/server/test/unittest_rql2sql.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/server/test/unittest_rql2sql.py	Tue Jul 28 09:25:26 2015 +0200
@@ -396,13 +396,13 @@
 ORDER BY 1'''),
 
     # DISTINCT, can use relation under exists scope as principal
-    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", X is CWEType, Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
      '''SELECT DISTINCT _X.cw_eid, rel_read_permission0.eid_to
 FROM cw_CWEType AS _X, read_permission_relation AS rel_read_permission0
 WHERE _X.cw_name=CWGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=_X.cw_eid)'''),
 
     # no distinct, Y can't be invariant
-    ('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
+    ('Any X,Y WHERE X name "CWGroup", X is CWEType, Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
      '''SELECT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
@@ -412,7 +412,7 @@
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
 
     # DISTINCT but NEGED exists, can't be invariant
-    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)',
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", X is CWEType, Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)',
      '''SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
@@ -422,7 +422,7 @@
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))'''),
 
     # should generate the same query as above
-    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", X is CWEType, Y eid IN(1, 2, 3), NOT X read_permission Y',
      '''SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
@@ -432,7 +432,7 @@
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))'''),
 
     # neged relation, can't be inveriant
-    ('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
+    ('Any X,Y WHERE X name "CWGroup", X is CWEType, Y eid IN(1, 2, 3), NOT X read_permission Y',
      '''SELECT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
 WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
--- a/server/test/unittest_schemaserial.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/server/test/unittest_schemaserial.py	Tue Jul 28 09:25:26 2015 +0200
@@ -434,6 +434,8 @@
         self.repo.set_schema(self.repo.deserialize_schema(), resetvreg=False)
         schema = self.repo.schema
         self.assertEqual([('Company', 'Person')], list(schema['has_employee'].rdefs))
+        self.assertEqual(schema['has_employee'].rdef('Company', 'Person').permissions['read'],
+                         (u'managers',))
         self.assertEqual('O works_for S',
                          schema['has_employee'].rule)
         self.assertEqual([('Company', 'Int')], list(schema['total_salary'].rdefs))
--- a/test/unittest_schema.py	Fri Jul 17 16:48:43 2015 +0200
+++ b/test/unittest_schema.py	Tue Jul 28 09:25:26 2015 +0200
@@ -482,6 +482,7 @@
                        ('cw_schema', 'CWSourceSchemaConfig', 'CWRelation', 'object'),
                        ('delete_permission', 'CWRelation', 'RQLExpression', 'subject'),
                        ('read_permission', 'CWRelation', 'RQLExpression', 'subject')],
+        'CWComputedRType': [('read_permission', 'CWComputedRType', 'RQLExpression', 'subject')],
         'CWSource': [('cw_for_source', 'CWSourceSchemaConfig', 'CWSource', 'object'),
                      ('cw_host_config_of', 'CWSourceHostConfig', 'CWSource', 'object'),
                      ('cw_import_of', 'CWDataImport', 'CWSource', 'object'),