[migration] write migration instructions for permissions handling on relation definition
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Mon, 07 Dec 2009 19:14:49 +0100
changeset 4011 394f853bb653
parent 4010 b2d0b14a365d
child 4012 f6c65e04704c
[migration] write migration instructions for permissions handling on relation definition
__pkginfo__.py
hooks/syncschema.py
misc/migration/bootstrapmigration_repository.py
server/hook.py
server/hookhelper.py
server/migractions.py
server/schemaserial.py
--- a/__pkginfo__.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/__pkginfo__.py	Mon Dec 07 19:14:49 2009 +0100
@@ -7,7 +7,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 5, 10)
+numversion = (3, 6, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
--- a/hooks/syncschema.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/hooks/syncschema.py	Mon Dec 07 19:14:49 2009 +0100
@@ -12,7 +12,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from yams.schema import BASE_TYPES
+from yams.schema import BASE_TYPES, RelationSchema
 from yams.buildobjs import EntityType, RelationType, RelationDefinition
 from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
 
@@ -483,7 +483,8 @@
         # so there is nothing to do here
         if session.added_in_transaction(rdef.eid):
             return
-        subjtype, rtype, objtype = session.vreg.schema.schema_by_eid(rdef.eid)
+        rdefschema = session.vreg.schema.schema_by_eid(rdef.eid)
+        subjtype, rtype, objtype = rdefschema.as_triple()
         cstrtype = self.entity.type
         oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
         newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
@@ -603,7 +604,7 @@
     def commit_event(self):
         # structure should be clean, not need to remove entity's relations
         # at this point
-        self.rschema.rdef[self.kobj].update(self.values)
+        self.rschema.rdefs[self.kobj].update(self.values)
 
 
 class MemSchemaRDefDel(MemSchemaOperation):
@@ -632,7 +633,8 @@
         if self.session.added_in_transaction(rdef.eid):
             self.cancelled = True
             return
-        subjtype, rtype, objtype = self.session.vreg.schema.schema_by_eid(rdef.eid)
+        rdef = self.session.vreg.schema.schema_by_eid(rdef.eid)
+        subjtype, rtype, objtype = rdef.as_triple()
         self.prepare_constraints(subjtype, rtype, objtype)
         cstrtype = self.entity.type
         self.cstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
@@ -668,13 +670,13 @@
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
-            erschema = self.session.vreg.schema[self.name]
+            erschema = self.session.vreg.schema.schema_by_eid(self.eid)
         except KeyError:
             # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
+            self.error('no schema for %s', self.eid)
             return
         perms = list(erschema.action_permissions(self.action))
-        if hasattr(self, group_eid):
+        if hasattr(self, 'group_eid'):
             perm = self.session.entity_from_eid(self.group_eid).name
         else:
             perm = erschema.rql_expression(self.expr)
@@ -695,18 +697,20 @@
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
-            erschema = self.session.vreg.schema[self.name]
+            erschema = self.session.vreg.schema.schema_by_eid(self.eid)
         except KeyError:
             # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
+            self.error('no schema for %s', self.eid)
+            return
+        if isinstance(erschema, RelationSchema): # XXX 3.6 migration
             return
         perms = list(erschema.action_permissions(self.action))
-        if hasattr(self, group_eid):
+        if hasattr(self, 'group_eid'):
             perm = self.session.entity_from_eid(self.group_eid).name
         else:
             perm = erschema.rql_expression(self.expr)
         try:
-            perms.remove(self.group)
+            perms.remove(perm)
             erschema.set_action_permissions(self.action, perms)
         except ValueError:
             self.error('can\'t remove permission %s for %s on %s',
@@ -916,7 +920,7 @@
     # don't use getattr(entity, attr), we would get the modified value if any
     for attr in ro_attrs:
         if attr in entity.edited_attributes:
-            origval, newval = entity_oldnewvalue(entity, attr)
+            origval, newval = hook.entity_oldnewvalue(entity, attr)
             if newval != origval:
                 errors[attr] = session._("can't change the %s attribute") % \
                                display_name(session, attr)
@@ -940,8 +944,8 @@
 
     def __call__(self):
         session = self._cw
-        subjschema, rschema, objschema = session.vreg.schema.schema_by_eid(self.eidfrom)
-        subjschema, rschema, objschema = session.schema.schema_by_eid(rdefeid)
+        rdef = session.vreg.schema.schema_by_eid(self.eidfrom)
+        subjschema, rschema, objschema = rdef.as_triple()
         pendings = session.transaction_data.get('pendingeids', ())
         pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set())
         # first delete existing relation if necessary
@@ -956,7 +960,7 @@
                                 % (rschema, subjschema, objschema))
         execute = session.unsafe_execute
         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
-                       'R eid %%(x)s' % rdeftype, {'x': rteid})
+                       'R eid %%(x)s' % rdeftype, {'x': self.eidto})
         lastrel = rset[0][0] == 0
         # we have to update physical schema systematically for final and inlined
         # relations, but only if it's the last instance for this relation type
@@ -965,17 +969,17 @@
         if (rschema.final or rschema.inlined):
             rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
                            'R eid %%(x)s, X from_entity E, E name %%(name)s'
-                           % rdeftype, {'x': rteid, 'name': str(subjschema)})
+                           % rdeftype, {'x': self.eidto, 'name': str(subjschema)})
             if rset[0][0] == 0 and not subjschema.eid in pendings:
                 ptypes = session.transaction_data.setdefault('pendingrtypes', set())
                 ptypes.add(rschema.type)
                 DropColumn(session, table=SQL_PREFIX + subjschema.type,
-                             column=SQL_PREFIX + rschema.type)
+                           column=SQL_PREFIX + rschema.type)
         elif lastrel:
             DropRelationTable(session, rschema.type)
         # if this is the last instance, drop associated relation type
         if lastrel and not rteid in pendings:
-            execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
+            execute('DELETE CWRType X WHERE X eid %(x)s', {'x': self.eidto}, 'x')
         MemSchemaRDefDel(session, (subjschema, rschema, objschema))
 
 
@@ -1087,7 +1091,7 @@
                                    group_eid=self.eidto)
         else: # RQLExpression
             expr = self._cw.entity_from_eid(self.eidto).expression
-            MemSchemaPermissionAdd(session, action=action, eid=self.eidfrom,
+            MemSchemaPermissionAdd(self._cw, action=action, eid=self.eidfrom,
                                    expr=expr)
 
 
--- a/misc/migration/bootstrapmigration_repository.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Dec 07 19:14:49 2009 +0100
@@ -10,6 +10,45 @@
 
 applcubicwebversion, cubicwebversion = versions_map['cubicweb']
 
+if applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+    from cubicweb.server import schemaserial as ss
+    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)
+        checkpoint(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'):
+                actperms = []
+                for something in rpermsdict.get(action, ()):
+                    if isinstance(something, tuple):
+                        actperms.append(rdef.rql_expression(*something))
+                    else: # group name
+                        actperms.append(something)
+                rdef.set_action_permissions(action, actperms)
+    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')
+        _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), '
+            '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 relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
+            'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
+        drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
+        drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
+    config.disabled_hooks_categories.add('integrity')
+
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
 
     session.set_shared_data('do-not-insert-cwuri', True)
--- a/server/hook.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/server/hook.py	Mon Dec 07 19:14:49 2009 +0100
@@ -87,6 +87,19 @@
 VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
 
 
+def entity_oldnewvalue(entity, attr):
+    """returns the couple (old attr value, new attr value)
+    NOTE: will only work in a before_update_entity hook
+    """
+    # get new value and remove from local dict to force a db query to
+    # fetch old value
+    newvalue = entity.pop(attr, None)
+    oldvalue = getattr(entity, attr)
+    if newvalue is not None:
+        entity[attr] = newvalue
+    return oldvalue, newvalue
+
+
 # some hook specific selectors #################################################
 
 @objectify_selector
--- a/server/hookhelper.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/server/hookhelper.py	Mon Dec 07 19:14:49 2009 +0100
@@ -10,18 +10,12 @@
 from logilab.common.deprecation import deprecated, class_moved
 
 from cubicweb import RepositoryError
+from cubicweb.server import hook
 
+@deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook')
 def entity_oldnewvalue(entity, attr):
-    """returns the couple (old attr value, new attr value)
-    NOTE: will only work in a before_update_entity hook
-    """
-    # get new value and remove from local dict to force a db query to
-    # fetch old value
-    newvalue = entity.pop(attr, None)
-    oldvalue = getattr(entity, attr)
-    if newvalue is not None:
-        entity[attr] = newvalue
-    return oldvalue, newvalue
+    """return the "name" attribute of the entity with the given eid"""
+    return hook.entity_oldnewvalue(entity, attr)
 
 @deprecated('[3.6] entity_name is deprecated, use entity.name')
 def entity_name(session, eid):
@@ -32,5 +26,4 @@
 def rproperty(session, rtype, eidfrom, eidto, rprop):
     return session.rproperty(rtype, eidfrom, eidto, rprop)
 
-from cubicweb.server.hook import SendMailOp
-SendMailOp = class_moved(SendMailOp)
+SendMailOp = class_moved(hook.SendMailOp)
--- a/server/migractions.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/server/migractions.py	Mon Dec 07 19:14:49 2009 +0100
@@ -521,9 +521,9 @@
 
     # base actions ############################################################
 
-    def checkpoint(self):
+    def checkpoint(self, ask_confirm=True):
         """checkpoint action"""
-        if self.confirm('commit now ?', shell=False):
+        if not ask_confirm or self.confirm('commit now ?', shell=False):
             self.commit()
 
     def cmd_add_cube(self, cube, update_database=True):
@@ -686,12 +686,9 @@
             eschema = self.fs_schema.eschema(etype)
         confirm = self.verbosity >= 2
         # register the entity into CWEType
-        self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
+        self.rqlexecall(ss.eschema2rql(eschema, self.group_mapping()), ask_confirm=confirm)
         # add specializes relation if needed
         self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
-        # register groups / permissions for the entity
-        self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
-                        ask_confirm=confirm)
         # register entity's attributes
         for rschema, attrschema in eschema.attribute_definitions():
             # ignore those meta relations, they will be automatically added
@@ -828,12 +825,9 @@
         # definitions
         self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
                         ask_confirm=self.verbosity>=2)
-        # register groups / permissions for the relation
-        self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
-                        ask_confirm=self.verbosity>=2)
         if addrdef:
             self.commit()
-            self.rqlexecall(ss.rdef2rql(rschema),
+            self.rqlexecall(ss.rdef2rql(rschema, groupmap=self.group_mapping()),
                             ask_confirm=self.verbosity>=2)
             if rtype in META_RTYPES:
                 # if the relation is in META_RTYPES, ensure we're adding it for
@@ -880,7 +874,7 @@
         rschema = self.fs_schema.rschema(rtype)
         if not rtype in self.repo.schema:
             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
-        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
+        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype, groupmap=self.group_mapping()),
                         ask_confirm=self.verbosity>=2)
         if commit:
             self.commit()
--- a/server/schemaserial.py	Mon Dec 07 17:57:19 2009 +0100
+++ b/server/schemaserial.py	Mon Dec 07 19:14:49 2009 +0100
@@ -308,14 +308,10 @@
             if pb is not None:
                 pb.update()
             continue
-        for rql, kwargs in erschema2rql(schema[ertype]):
+        for rql, kwargs in erschema2rql(schema[ertype], groupmap):
             if verbose:
                 print rql % kwargs
             cursor.execute(rql, kwargs)
-        for rql, kwargs in erperms2rql(schema[ertype], groupmap):
-            if verbose:
-                print rql
-            cursor.execute(rql, kwargs)
         if pb is not None:
             pb.update()
     for rql, kwargs in specialize2rql(schema):
@@ -399,7 +395,7 @@
     return relations, values
 
 
-def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None):
+def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None, groupmap=None):
     if subjtype is None:
         assert objtype is None
         assert props is None
@@ -407,9 +403,14 @@
     else:
         assert not objtype is None
         targets = [(subjtype, objtype)]
+    # relation schema
+    if rschema.final:
+        etype = 'CWAttribute'
+    else:
+        etype = 'CWRelation'
     for subjtype, objtype in targets:
         if props is None:
-            _props = rschema.rproperties(subjtype, objtype)
+            _props = rschema.rdef(subjtype, objtype)
         else:
             _props = props
         # don't serialize infered relations
@@ -418,6 +419,15 @@
         gen = genmap[rschema.final]
         for rql, values in gen(rschema, subjtype, objtype, _props):
             yield rql, values
+        # no groupmap means "no security insertion"
+        if groupmap:
+            for rql, args in _erperms2rql(_props, groupmap):
+                args['st'] = str(subjtype)
+                args['rt'] = str(rschema)
+                args['ot'] = str(objtype)
+                yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
+                      'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
+                      'OT name %%(ot)s' % etype, args
 
 
 def schema2rql(schema, skip=None, allow=None):
@@ -433,12 +443,12 @@
         return chain(*[erschema2rql(schema[t]) for t in all if t in allow])
     return chain(*[erschema2rql(schema[t]) for t in all])
 
-def erschema2rql(erschema):
+def erschema2rql(erschema, groupmap):
     if isinstance(erschema, schemamod.EntitySchema):
-        return eschema2rql(erschema)
+        return eschema2rql(erschema, groupmap)
     return rschema2rql(erschema)
 
-def eschema2rql(eschema):
+def eschema2rql(eschema, groupmap):
     """return a list of rql insert statements to enter an entity schema
     in the database as an CWEType entity
     """
@@ -446,6 +456,10 @@
     # NOTE: 'specializes' relation can't be inserted here since there's no
     # way to make sure the parent type is inserted before the child type
     yield 'INSERT CWEType X: %s' % ','.join(relations) , values
+        # entity schema
+    for rql, args in _erperms2rql(eschema, groupmap):
+        args['name'] = str(eschema)
+        yield rql + 'X is CWEType, X name %(name)s', args
 
 def specialize2rql(schema):
     for eschema in schema.entities():
@@ -458,7 +472,7 @@
         values = {'x': eschema.type, 'et': specialized_type.type}
         yield 'SET X specializes ET WHERE X name %(x)s, ET name %(et)s', values
 
-def rschema2rql(rschema, addrdef=True):
+def rschema2rql(rschema, addrdef=True, groupmap=None):
     """return a list of rql insert statements to enter a relation schema
     in the database as an CWRType entity
     """
@@ -467,12 +481,12 @@
     relations, values = rschema_relations_values(rschema)
     yield 'INSERT CWRType X: %s' % ','.join(relations), values
     if addrdef:
-        for rql, values in rdef2rql(rschema):
+        for rql, values in rdef2rql(rschema, groupmap=groupmap):
             yield rql, values
 
-def rdef2rql(rschema, subjtype=None, objtype=None, props=None):
+def rdef2rql(rschema, subjtype=None, objtype=None, props=None, groupmap=None):
     genmap = {True: frdef2rql, False: nfrdef2rql}
-    return __rdef2rql(genmap, rschema, subjtype, objtype, props)
+    return __rdef2rql(genmap, rschema, subjtype, objtype, props, groupmap)
 
 
 _LOCATE_RDEF_RQL0 = 'X relation_type ER,X from_entity SE,X to_entity OE'
@@ -508,43 +522,8 @@
 CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, \
 ER name %(rt)s, SE name %(se)s, OE name %(oe)s', values
 
-def perms2rql(schema, groupmapping):
-    """return rql insert statements to enter the schema's permissions in
-    the database as [read|add|delete|update]_permission relations between
-    CWEType/CWRType and CWGroup entities
 
-    groupmapping is a dictionnary mapping standard group names to
-    eids
-    """
-    for etype in sorted(schema.entities()):
-        yield erperms2rql(schema[etype], groupmapping)
-    for rtype in sorted(schema.relations()):
-        yield erperms2rql(schema[rtype], groupmapping)
-
-def erperms2rql(erschema, groupmapping):
-    if hasattr(erschema, 'iter_rdefs'):
-        # relation schema
-        if erschema.final:
-            etype = 'CWAttribute'
-        else:
-            etype = 'CWRelation'
-        for subject, object in erschema.iter_rdefs():
-            permissions = erschema.rproperty(subject, object, 'permissions')
-            for rql, args in _erperms2rql(erschema.rproperties(subject, object),
-                                          groupmapping):
-                args['st'] = str(subject)
-                args['rt'] = str(erschema)
-                args['ot'] = str(object)
-                yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
-                      'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
-                      'OT name %%(ot)s' % etype, args
-    else:
-        # entity schema
-        for rql, args in _erperms2rql(erschema, groupmapping):
-            args['name'] = str(erschema)
-            yield rql + 'X is CWEType, X name %(name)s', args
-
-def _erperms2rql(erschema, groupmapping):
+def _erperms2rql(erschema, groupmap):
     """return rql insert statements to enter the entity or relation
     schema's permissions in the database as
     [read|add|delete|update]_permission relations between CWEType/CWRType
@@ -556,7 +535,7 @@
                 # group
                 try:
                     yield ('SET X %s_permission Y WHERE Y eid %%(g)s, ' % action,
-                           {'g': groupmapping[group_or_rqlexpr]})
+                           {'g': groupmap[group_or_rqlexpr]})
                 except KeyError:
                     continue
             else: