follow yams api change: attributes permissions are now defined for stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 15 Feb 2010 15:10:25 +0100
branchstable
changeset 4570 ede247bbbf62
parent 4569 1acd90d0cb59
child 4571 794ac358dc36
follow yams api change: attributes permissions are now defined for an 'update' action, no more 'add' / 'delete' which makes no sense in such case. fix afs.relations_by_section permissions checking of object relation on the way.
hooks/security.py
hooks/syncschema.py
misc/migration/3.6.1_Any.py
misc/migration/bootstrapmigration_repository.py
schema.py
schemas/bootstrap.py
server/schemaserial.py
server/test/data/schema.py
web/test/unittest_views_editforms.py
web/uicfg.py
web/views/autoform.py
--- a/hooks/security.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/hooks/security.py	Mon Feb 15 15:10:25 2010 +0100
@@ -28,7 +28,7 @@
         rdef = eschema.rdef(attr)
         if rdef.final: # non final relation are checked by other hooks
             # add/delete should be equivalent (XXX: unify them into 'update' ?)
-            rdef.check_perm(session, 'add', eid=eid)
+            rdef.check_perm(session, 'update', eid=eid)
 
 
 class _CheckEntityPermissionOp(hook.LateOperation):
--- a/hooks/syncschema.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/hooks/syncschema.py	Mon Feb 15 15:10:25 2010 +0100
@@ -716,6 +716,9 @@
             return
         if isinstance(erschema, RelationSchema): # XXX 3.6 migration
             return
+        if isinstance(erschema, RelationDefinitionSchema) and \
+               self.action in ('delete', 'add'): # XXX 3.6.1 migration
+            return
         perms = list(erschema.action_permissions(self.action))
         if hasattr(self, 'group_eid'):
             perm = self.session.entity_from_eid(self.group_eid).name
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.6.1_Any.py	Mon Feb 15 15:10:25 2010 +0100
@@ -0,0 +1,1 @@
+sync_schema_props_perms(syncprops=False)
--- a/misc/migration/bootstrapmigration_repository.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Feb 15 15:10:25 2010 +0100
@@ -10,24 +10,35 @@
 
 applcubicwebversion, cubicwebversion = versions_map['cubicweb']
 
-if applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
-    from cubicweb.server import schemaserial as ss
+from cubicweb.server import schemaserial as ss
+def _add_relation_definition_no_perms(subjtype, rtype, objtype):
+    rschema = fsschema.rschema(rtype)
+    for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
+        rql(query, args, ask_confirm=False)
+    commit(ask_confirm=False)
+
+if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+    _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'CWGroup')
+    _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'RQLExpression')
+    session.set_pool()
+    session.unsafe_execute('SET X update_permission Y WHERE X is CWAttribute, X add_permission Y')
+    drop_relation_definition('CWAttribute', 'add_permission', 'CWGroup')
+    drop_relation_definition('CWAttribute', 'add_permission', 'RQLExpression')
+    drop_relation_definition('CWAttribute', 'delete_permission', 'CWGroup')
+    drop_relation_definition('CWAttribute', 'delete_permission', 'RQLExpression')
+
+elif applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
     session.set_pool()
     session.execute = session.unsafe_execute
     permsdict = ss.deserialize_ertype_permissions(session)
-    def _add_relation_definition_no_perms(subjtype, rtype, objtype):
-        rschema = fsschema.rschema(rtype)
-        for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
-            rql(query, args, ask_confirm=False)
-        commit(ask_confirm=False)
 
     config.disabled_hooks_categories.add('integrity')
     for rschema in repo.schema.relations():
         rpermsdict = permsdict.get(rschema.eid, {})
         for rdef in rschema.rdefs.values():
-            for action in ('read', 'add', 'delete'):
+            for action in rdef.ACTIONS:
                 actperms = []
-                for something in rpermsdict.get(action, ()):
+                for something in rpermsdict.get(action == 'update' and 'add' or action, ()):
                     if isinstance(something, tuple):
                         actperms.append(rdef.rql_expression(*something))
                     else: # group name
@@ -36,18 +47,32 @@
     for action in ('read', 'add', 'delete'):
         _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup')
         _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression')
+    for action in ('read', 'update'):
         _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup')
         _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression')
     for action in ('read', 'add', 'delete'):
-        rql('SET X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+        rql('SET X %s_permission Y WHERE X is CWRelation, '
             'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action))
         rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
-            'X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+            'X %s_permission Y WHERE X is CWRelation, '
             'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
             'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
+    rql('SET X read_permission Y WHERE X is CWAttribute, '
+        'RT read_permission Y, X relation_type RT, Y is CWGroup')
+    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+        'X read_permission Y WHERE X is CWAttribute, '
+        'X relation_type RT, RT read_permission Y2, Y2 exprtype YET, '
+        'Y2 mainvars YMV, Y2 expression YEX')
+    rql('SET X update_permission Y WHERE X is CWAttribute, '
+        'RT add_permission Y, X relation_type RT, Y is CWGroup')
+    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+        'X update_permission Y WHERE X is CWAttribute, '
+        'X relation_type RT, RT add_permission Y2, Y2 exprtype YET, '
+        'Y2 mainvars YMV, Y2 expression YEX')
+    for action in ('read', 'add', 'delete'):
         drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
         drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
-    config.disabled_hooks_categories.add('integrity')
+    config.disabled_hooks_categories.remove('integrity')
 
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
 
--- a/schema.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/schema.py	Mon Feb 15 15:10:25 2010 +0100
@@ -919,6 +919,11 @@
             kwargs['o'] = toeid
         return self._check(session, **kwargs)
 
+# in yams, default 'update' perm for attributes granted to managers and owners.
+# Within cw, we want to default to users who may edit the entity holding the
+# attribute.
+ybo._DEFAULT_ATTRPERMS['update'] = (
+    'managers', ERQLExpression('U has_update_permission X'))
 
 # workflow extensions #########################################################
 
--- a/schemas/bootstrap.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/schemas/bootstrap.py	Mon Feb 15 15:10:25 2010 +0100
@@ -9,7 +9,7 @@
 _ = unicode
 
 from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
-                            ObjectRelation, RichString, String, Boolean, Int)
+                            RichString, String, Boolean, Int)
 from cubicweb.schema import RQLConstraint
 from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
 
@@ -131,15 +131,6 @@
                                       'relation\'subject, object and to '
                                       'the request user. '))
 
-    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                      description=_('rql expression allowing to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                     description=_('rql expression allowing to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                        description=_('rql expression allowing to delete entities/relations of this type'))
-    update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject',
-                                        description=_('rql expression allowing to update entities of this type'))
-
 
 class CWConstraint(EntityType):
     """define a schema constraint"""
@@ -162,16 +153,6 @@
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
-    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='**',
-                                      description=_('groups allowed to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
-                                     description=_('groups allowed to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
-                                        description=_('groups allowed to delete entities/relations of this type'))
-    update_permission = ObjectRelation('CWEType',
-                                        description=_('groups allowed to update entities of this type'))
-
-
 
 class CWProperty(EntityType):
     """used for cubicweb configuration. Once a property has been created you
@@ -215,27 +196,44 @@
     inlined = True
 
 class read_permission(RelationType):
-    """core relation giving to a group the permission to read an entity or
-    relation type
+    """grant permission to read entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWAttribute', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class add_permission(RelationType):
-    """core relation giving to a group the permission to add an entity or
-    relation type
+    """grant permission to add entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class delete_permission(RelationType):
-    """core relation giving to a group the permission to delete an entity or
-    relation type
+    """grant permission to delete entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class update_permission(RelationType):
-    """core relation giving to a group the permission to update an entity type
+    """grant permission to update entity or attribute through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWAttribute')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 
 class is_(RelationType):
--- a/server/schemaserial.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/server/schemaserial.py	Mon Feb 15 15:10:25 2010 +0100
@@ -189,13 +189,17 @@
     definition dictionary as built by deserialize_ertype_permissions for a
     given erschema's eid
     """
+    # reset erschema permissions here to avoid getting yams default anyway
+    erschema.permissions = dict((action, ()) for action in erschema.ACTIONS)
     try:
         thispermsdict = permsdict[erschema.eid]
     except KeyError:
         return
-    permissions = erschema.permissions
     for action, somethings in thispermsdict.iteritems():
-        permissions[action] = tuple(
+        # XXX cw < 3.6.1 bw compat
+        if isinstance(erschema, schemamod.RelationDefinitionSchema) and erschema.final and action == 'add':
+            action = 'update'
+        erschema.permissions[action] = tuple(
             isinstance(p, tuple) and erschema.rql_expression(*p) or p
             for p in somethings)
 
--- a/server/test/data/schema.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/server/test/data/schema.py	Mon Feb 15 15:10:25 2010 +0100
@@ -137,14 +137,13 @@
 class para(RelationType):
     __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
-        'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
+        'update':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
         }
 
 class test(RelationType):
     __permissions__ = {'read': ('managers', 'users', 'guests'),
-                   'delete': ('managers',),
-                   'add': ('managers',)}
+                       'update': ('managers',),
+                       }
 
 class multisource_rel(RelationDefinition):
     subject = ('Card', 'Note')
--- a/web/test/unittest_views_editforms.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/web/test/unittest_views_editforms.py	Mon Feb 15 15:10:25 2010 +0100
@@ -16,7 +16,11 @@
 AFS = uicfg.autoform_section
 
 def rbc(entity, formtype, section):
-    return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section)]
+    if section in ('attributes', 'metadata', 'hidden'):
+        permission = 'update'
+    else:
+        permission = 'add'
+    return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section, permission)]
 
 class AutomaticEntityFormTC(CubicWebTC):
 
@@ -69,19 +73,16 @@
                               [('use_email', 'subject'),
                                ])
         # owned_by is defined both as subject and object relations on CWUser
-        self.assertListEquals(rbc(e, 'main', 'hidden'),
-                              [('in_state', 'subject'),
-                               ('is', 'subject'),
-                               ('is_instance_of', 'subject'),
-                               ('has_text', 'subject'),
-                               ('identity', 'subject'),
-                               ('tags', 'object'),
-                               ('for_user', 'object'),
-                               ('created_by', 'object'),
-                               ('wf_info_for', 'object'),
-                               ('owned_by', 'object'),
-                               ('identity', 'object'),
-                               ])
+        self.assertListEquals(sorted(rbc(e, 'main', 'hidden')),
+                              sorted([('has_text', 'subject'),
+                                      ('identity', 'subject'),
+                                      ('tags', 'object'),
+                                      ('for_user', 'object'),
+                                      ('created_by', 'object'),
+                                      ('wf_info_for', 'object'),
+                                      ('owned_by', 'object'),
+                                      ('identity', 'object'),
+                                      ]))
 
     def test_inlined_view(self):
         self.failUnless('main_inlined' in AFS.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'))
@@ -122,10 +123,8 @@
                                ('connait', 'object')
                                ])
         self.assertListEquals(rbc(e, 'main', 'hidden'),
-                              [('is', 'subject'),
-                               ('has_text', 'subject'),
+                              [('has_text', 'subject'),
                                ('identity', 'subject'),
-                               ('is_instance_of', 'subject'),
                                ('identity', 'object'),
                                ])
 
--- a/web/uicfg.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/web/uicfg.py	Mon Feb 15 15:10:25 2010 +0100
@@ -347,8 +347,8 @@
         # overriden to avoid recomputing done in parent classes
         return self._tagdefs.get(key, ())
 
-    def relations_by_section(self, entity, formtype, section,
-                             permission=None, strict=False):
+    def relations_by_section(self, entity, formtype, section, permission,
+                             strict=False):
         """return a list of (relation schema, target schemas, role) for the
         given entity matching categories and permission.
 
@@ -363,52 +363,60 @@
         else:
             eid = None
             strict = False
+        if permission == 'update':
+            assert section in ('attributes', 'metadata', 'hidden')
+            relpermission = 'add'
+        else:
+            assert section not in ('attributes', 'metadata', 'hidden')
+            relpermission = permission
         cw = entity._cw
         for rschema, targetschemas, role in eschema.relation_definitions(True):
-            # check category first, potentially lower cost than checking
-            # permission which may imply rql queries
             _targetschemas = []
             for tschema in targetschemas:
+                # check section's tag first, potentially lower cost than
+                # checking permission which may imply rql queries
                 if not tag in self.etype_get(eschema, rschema, role, tschema):
                     continue
                 rdef = rschema.role_rdef(eschema, tschema, role)
-                if permission is not None and \
-                       not ((not strict and rdef.has_local_role(permission)) or
-                            rdef.has_perm(cw, permission, fromeid=eid)):
-                    continue
+                if rschema.final:
+                    if not rdef.has_perm(cw, permission, eid=eid):
+                        continue
+                elif strict or not rdef.has_local_role(relpermission):
+                    if role == 'subject':
+                        if not rdef.has_perm(cw, relpermission, fromeid=eid):
+                            continue
+                    elif role == 'object':
+                        if not rdef.has_perm(cw, relpermission, toeid=eid):
+                            continue
                 _targetschemas.append(tschema)
             if not _targetschemas:
                 continue
             targetschemas = _targetschemas
-            if permission is not None:
-                rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
-                # tag allowing to hijack the permission machinery when
-                # permission is not verifiable until the entity is actually
-                # created...
-                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
-                    yield (rschema, targetschemas, role)
+            rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
+            # XXX tag allowing to hijack the permission machinery when
+            # permission is not verifiable until the entity is actually
+            # created...
+            if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+                yield (rschema, targetschemas, role)
+                continue
+            if not rschema.final and role == 'subject':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', fromeid=eid,
+                                          toeid=entity.related(rschema.type, role)[0][0])):
                     continue
-                if rschema.final:
-                    if not rdef.has_perm(cw, permission, fromeid=eid):
-                        continue
-                elif role == 'subject':
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rdef.role_cardinality(role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rdef.has_perm(cw, 'delete', fromeid=eid,
-                                              toeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-                elif role == 'object':
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rdef.role_cardinality(role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rdef.has_perm(cw, 'delete', toeid=eid,
-                                              fromeid=entity.related(rschema.type, role)[0][0])):
-                        continue
+            elif role == 'object':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', toeid=eid,
+                                          fromeid=entity.related(rschema.type, role)[0][0])):
+                    continue
             yield (rschema, targetschemas, role)
 
 autoform_section = AutoformSectionRelationTags('autoform_section')
--- a/web/views/autoform.py	Mon Feb 15 15:05:15 2010 +0100
+++ b/web/views/autoform.py	Mon Feb 15 15:10:25 2010 +0100
@@ -686,7 +686,7 @@
             return self.display_fields
         # XXX we should simply put eid in the generated section, no?
         return [(rtype, role) for rtype, _, role in self._relations_by_section(
-            'attributes', strict=strict) if rtype != 'eid']
+            'attributes', 'update', strict) if rtype != 'eid']
 
     def editable_relations(self):
         """return a sorted list of (relation's label, relation'schema, role) for