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
--- /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'),