reldefsecurity branch : reldefsecurity
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 19 Nov 2009 12:55:47 +0100
branchreldefsecurity
changeset 3877 7ca53fc72a0a
parent 3876 1169d3154be6
child 3889 8902b8745918
reldefsecurity branch : * follow yams default branch api changes * now consider permissions on relation definitions, not relation types. This is still experimental.
devtools/_apptest.py
entity.py
rqlrewrite.py
rset.py
schema.py
schemas/Bookmark.py
schemas/base.py
schemas/bootstrap.py
schemas/workflow.py
schemaviewer.py
selectors.py
server/hookhelper.py
server/hooks.py
server/migractions.py
server/querier.py
server/repository.py
server/schemahooks.py
server/schemaserial.py
server/securityhooks.py
server/test/data/migratedapp/schema.py
server/test/data/schema.py
server/test/unittest_security.py
sobjects/notification.py
test/data/erqlexpr_on_ertype.py
test/data/rewrite/schema.py
test/data/rqlexpr_on_ertype_read.py
test/data/rrqlexpr_on_attr.py
test/data/rrqlexpr_on_eetype.py
test/unittest_entity.py
test/unittest_rqlrewrite.py
test/unittest_schema.py
web/uicfg.py
web/views/actions.py
web/views/autoform.py
web/views/owl.py
web/views/schema.py
web/views/startup.py
--- a/devtools/_apptest.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/devtools/_apptest.py	Thu Nov 19 12:55:47 2009 +0100
@@ -92,7 +92,7 @@
         schema = self.vreg.schema
         # else we may run into problems since email address are ususally share in app tests
         # XXX should not be necessary anymore
-        schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False)
+        schema.rschema('primary_email').rdef('CWUser', 'EmailAddress').composite = False
         self.deletable_entities = unprotected_entities(schema)
 
     def restore_database(self):
--- a/entity.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/entity.py	Thu Nov 19 12:55:47 2009 +0100
@@ -34,7 +34,7 @@
 def greater_card(rschema, subjtypes, objtypes, index):
     for subjtype in subjtypes:
         for objtype in objtypes:
-            card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+            card = rschema.rdef(subjtype, objtype).cardinality[index]
             if card in '+*':
                 return card
     return '1'
@@ -243,7 +243,8 @@
                 cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
                             attr, cls.id)
                 continue
-            if not user.matching_groups(rschema.get_groups('read')):
+            rdef = eschema.rdef(attr)
+            if not user.matching_groups(rdef.get_groups('read')):
                 continue
             var = varmaker.next()
             selection.append(var)
@@ -252,7 +253,7 @@
             if not rschema.final:
                 # XXX this does not handle several destination types
                 desttype = rschema.objects(eschema.type)[0]
-                card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+                card = rdef.cardinality[0]
                 if card not in '?1':
                     cls.warning('bad relation %s specified in fetch attrs for %s',
                                  attr, cls)
@@ -362,10 +363,10 @@
             self.req.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
 
     def check_perm(self, action):
-        self.e_schema.check_perm(self.req, action, self.eid)
+        self.e_schema.check_perm(self.req, action, eid=self.eid)
 
     def has_perm(self, action):
-        return self.e_schema.has_perm(self.req, action, self.eid)
+        return self.e_schema.has_perm(self.req, action, eid=self.eid)
 
     def view(self, vid, __registry='views', **kwargs):
         """shortcut to apply a view on this entity"""
@@ -443,11 +444,11 @@
             return u''
         if attrtype is None:
             attrtype = self.e_schema.destination(attr)
-        props = self.e_schema.rproperties(attr)
+        props = self.e_schema.rdef(attr)
         if attrtype == 'String':
             # internalinalized *and* formatted string such as schema
             # description...
-            if props.get('internationalizable'):
+            if props.internationalizable:
                 value = self.req._(value)
             attrformat = self.attr_metadata(attr, 'format')
             if attrformat:
@@ -495,11 +496,12 @@
             if rschema.type in self.skip_copy_for:
                 continue
             # skip composite relation
-            if self.e_schema.subjrproperty(rschema, 'composite'):
+            rdef = self.e_schema.rdef(rschema)
+            if rdef.composite:
                 continue
             # skip relation with card in ?1 else we either change the copied
             # object (inlined relation) or inserting some inconsistency
-            if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
+            if rdef.cardinality[1] in '?1':
                 continue
             rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
                 rschema.type, rschema.type)
@@ -509,14 +511,15 @@
             if rschema.meta:
                 continue
             # skip already defined relations
-            if getattr(self, 'reverse_%s' % rschema.type):
+            if self.related(rschema.type, 'object'):
                 continue
+            rdef = self.e_schema.rdef(rschema, 'object')
             # skip composite relation
-            if self.e_schema.objrproperty(rschema, 'composite'):
+            if rdef.composite:
                 continue
             # skip relation with card in ?1 else we either change the copied
             # object (inlined relation) or inserting some inconsistency
-            if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
+            if rdef.cardinality[0] in '?1':
                 continue
             rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
                 rschema.type, rschema.type)
@@ -537,15 +540,16 @@
         for rschema in self.e_schema.subject_relations():
             if rschema.final:
                 continue
-            if len(rschema.objects(self.e_schema)) > 1:
+            targets = rschema.objects(self.e_schema)
+            if len(targets) > 1:
                 # ambigous relations, the querier doesn't handle
                 # outer join correctly in this case
                 continue
             if rschema.inlined:
+                rdef = rschema.rdef(self.e_schema, targets[0])
                 matching_groups = self.req.user.matching_groups
-                if matching_groups(rschema.get_groups('read')) and \
-                   all(matching_groups(es.get_groups('read'))
-                       for es in rschema.objects(self.e_schema)):
+                if matching_groups(rdef.get_groups('read')) and \
+                   all(matching_groups(e.get_groups('read')) for e in targets):
                     yield rschema, 'subject'
 
     def to_complete_attributes(self, skip_bytes=True):
@@ -557,7 +561,8 @@
             if attr == 'eid':
                 continue
             # password retreival is blocked at the repository server level
-            if not self.req.user.matching_groups(rschema.get_groups('read')) \
+            rdef = rschema.rdef(self.e_schema, attrschema)
+            if not self.req.user.matching_groups(rdef.get_groups('read')) \
                    or attrschema.type == 'Password':
                 self[attr] = None
                 continue
@@ -593,24 +598,21 @@
                 if self.relation_cached(rtype, role):
                     continue
                 var = varmaker.next()
+                targettype = rschema.targets(self.e_schema, role)[0]
+                rdef = rschema.role_rdef(self.e_schema, targettype, role)
+                card = rdef.role_cardinality(role)
+                assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
+                                                      role, card)
                 if role == 'subject':
-                    targettype = rschema.objects(self.e_schema)[0]
-                    card = rschema.rproperty(self.e_schema, targettype,
-                                             'cardinality')[0]
                     if card == '1':
                         rql.append('%s %s %s' % (V, rtype, var))
-                    else: # '?"
+                    else:
                         rql.append('%s %s %s?' % (V, rtype, var))
                 else:
-                    targettype = rschema.subjects(self.e_schema)[1]
-                    card = rschema.rproperty(self.e_schema, targettype,
-                                             'cardinality')[1]
                     if card == '1':
                         rql.append('%s %s %s' % (var, rtype, V))
-                    else: # '?"
+                    else:
                         rql.append('%s? %s %s' % (var, rtype, V))
-                assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
-                                                      role, card)
                 selected.append(((rtype, role), var))
         if selected:
             # select V, we need it as the left most selected variable
@@ -756,16 +758,16 @@
             restriction = []
             args = {}
             securitycheck_args = {}
-        insertsecurity = (rtype.has_local_role('add') and not
-                          rtype.has_perm(self.req, 'add', **securitycheck_args))
-        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+        rdef = rtype.role_rdef(self.e_schema, targettype, role)
+        insertsecurity = (rdef.has_local_role('add') and not
+                          rdef.has_perm(self.req, 'add', **securitycheck_args))
         if vocabconstraints:
             # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
             # will be included as well
-            restriction += [cstr.restriction for cstr in constraints
+            restriction += [cstr.restriction for cstr in rdef.constraints
                             if isinstance(cstr, RQLVocabularyConstraint)]
         else:
-            restriction += [cstr.restriction for cstr in constraints
+            restriction += [cstr.restriction for cstr in rdef.constraints
                             if isinstance(cstr, RQLConstraint)]
         etypecls = self.vreg['etypes'].etype_class(targettype)
         rql = etypecls.fetch_rql(self.req.user, restriction,
@@ -775,7 +777,7 @@
             before, after = rql.split(' WHERE ', 1)
             rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
         if insertsecurity:
-            rqlexprs = rtype.get_rqlexprs('add')
+            rqlexprs = rdef.get_rqlexprs('add')
             rewriter = RQLRewriter(self.req)
             rqlst = self.req.vreg.parse(self.req, rql, args)
             if not self.has_eid():
@@ -827,12 +829,10 @@
             related = tuple(rset.entities(col))
             rschema = self.schema.rschema(rtype)
             if role == 'subject':
-                rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
-                                          'cardinality')[1]
+                rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
                 target = 'object'
             else:
-                rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
-                                          'cardinality')[0]
+                rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0]
                 target = 'subject'
             if rcard in '?1':
                 for rentity in related:
--- a/rqlrewrite.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/rqlrewrite.py	Thu Nov 19 12:55:47 2009 +0100
@@ -402,12 +402,12 @@
                 orel = self.varinfo['lhs_rels'][sniprel.r_type]
                 cardindex = 0
                 ttypes_func = rschema.objects
-                rprop = rschema.rproperty
+                rdef = rschema.rdef
             else: # target == 'subject':
                 orel = self.varinfo['rhs_rels'][sniprel.r_type]
                 cardindex = 1
                 ttypes_func = rschema.subjects
-                rprop = lambda x, y, z: rschema.rproperty(y, x, z)
+                rdef = lambda x, y: rschema.rdef(y, x)
         except KeyError, ex:
             # may be raised by self.varinfo['xhs_rels'][sniprel.r_type]
             return None
@@ -419,7 +419,7 @@
         # variable from the original query
         for etype in self.varinfo['stinfo']['possibletypes']:
             for ttype in ttypes_func(etype):
-                if rprop(etype, ttype, 'cardinality')[cardindex] in '+*':
+                if rdef(etype, ttype).cardinality[cardindex] in '+*':
                     return None
         return orel
 
--- a/rset.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/rset.py	Thu Nov 19 12:55:47 2009 +0100
@@ -403,25 +403,22 @@
                 select = rqlst
             # take care, due to outer join support, we may find None
             # values for non final relation
-            for i, attr, x in attr_desc_iterator(select, col):
+            for i, attr, role in attr_desc_iterator(select, col):
                 outerselidx = rqlst.subquery_selection_index(select, i)
                 if outerselidx is None:
                     continue
-                if x == 'subject':
+                if role == 'subject':
                     rschema = eschema.subjrels[attr]
                     if rschema.final:
                         entity[attr] = rowvalues[outerselidx]
                         continue
-                    tetype = rschema.objects(etype)[0]
-                    card = rschema.rproperty(etype, tetype, 'cardinality')[0]
                 else:
                     rschema = eschema.objrels[attr]
-                    tetype = rschema.subjects(etype)[0]
-                    card = rschema.rproperty(tetype, etype, 'cardinality')[1]
+                rdef = eschema.rdef(attr, role)
                 # only keep value if it can't be multivalued
-                if card in '1?':
+                if rdef.role_cardinality(role) in '1?':
                     if rowvalues[outerselidx] is None:
-                        if x == 'subject':
+                        if role == 'subject':
                             rql = 'Any Y WHERE X %s Y, X eid %s'
                         else:
                             rql = 'Any Y WHERE Y %s X, X eid %s'
@@ -429,7 +426,7 @@
                         req.decorate_rset(rrset)
                     else:
                         rrset = self._build_entity(row, outerselidx).as_rset()
-                    entity.set_related_cache(attr, x, rrset)
+                    entity.set_related_cache(attr, role, rrset)
         return entity
 
     @cached
--- a/schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -20,7 +20,8 @@
 from logilab.common.compat import any
 
 from yams import BadSchemaDefinition, buildobjs as ybo
-from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema
+from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
+     RelationDefinitionSchema, PermissionMixIn
 from yams.constraints import (BaseConstraint, StaticVocabularyConstraint,
                               FormatConstraint)
 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
@@ -127,7 +128,7 @@
 ERSchema.display_name = ERSchema_display_name
 
 @cached
-def ERSchema_get_groups(self, action):
+def get_groups(self, action):
     """return the groups authorized to perform <action> on entities of
     this type
 
@@ -140,28 +141,13 @@
     assert action in self.ACTIONS, action
     #assert action in self._groups, '%s %s' % (self, action)
     try:
-        return frozenset(g for g in self._groups[action] if isinstance(g, basestring))
+        return frozenset(g for g in self.permissions[action] if isinstance(g, basestring))
     except KeyError:
         return ()
-ERSchema.get_groups = ERSchema_get_groups
-
-def ERSchema_set_groups(self, action, groups):
-    """set the groups allowed to perform <action> on entities of this type. Don't
-    change rql expressions for the same action.
-
-    :type action: str
-    :param action: the name of a permission
-
-    :type groups: list or tuple
-    :param groups: names of the groups granted to do the given action
-    """
-    assert action in self.ACTIONS, action
-    clear_cache(self, 'ERSchema_get_groups')
-    self._groups[action] = tuple(groups) + self.get_rqlexprs(action)
-ERSchema.set_groups = ERSchema_set_groups
+PermissionMixIn.get_groups = get_groups
 
 @cached
-def ERSchema_get_rqlexprs(self, action):
+def get_rqlexprs(self, action):
     """return the rql expressions representing queries to check the user is allowed
     to perform <action> on entities of this type
 
@@ -174,27 +160,13 @@
     assert action in self.ACTIONS, action
     #assert action in self._rqlexprs, '%s %s' % (self, action)
     try:
-        return tuple(g for g in self._groups[action] if not isinstance(g, basestring))
+        return tuple(g for g in self.permissions[action] if not isinstance(g, basestring))
     except KeyError:
         return ()
-ERSchema.get_rqlexprs = ERSchema_get_rqlexprs
-
-def ERSchema_set_rqlexprs(self, action, rqlexprs):
-    """set the rql expression allowing to perform <action> on entities of this type. Don't
-    change groups for the same action.
-
-    :type action: str
-    :param action: the name of a permission
+PermissionMixIn.get_rqlexprs = get_rqlexprs
 
-    :type rqlexprs: list or tuple
-    :param rqlexprs: the rql expressions allowing the given action
-    """
-    assert action in self.ACTIONS, action
-    clear_cache(self, 'ERSchema_get_rqlexprs')
-    self._groups[action] = tuple(self.get_groups(action)) + tuple(rqlexprs)
-ERSchema.set_rqlexprs = ERSchema_set_rqlexprs
-
-def ERSchema_set_permissions(self, action, permissions):
+orig_set_action_permissions = PermissionMixIn.set_action_permissions
+def set_action_permissions(self, action, permissions):
     """set the groups and rql expressions allowing to perform <action> on
     entities of this type
 
@@ -204,22 +176,12 @@
     :type permissions: tuple
     :param permissions: the groups and rql expressions allowing the given action
     """
-    assert action in self.ACTIONS, action
-    clear_cache(self, 'ERSchema_get_rqlexprs')
-    clear_cache(self, 'ERSchema_get_groups')
-    self._groups[action] = tuple(permissions)
-ERSchema.set_permissions = ERSchema_set_permissions
+    orig_set_action_permissions(self, action, tuple(permissions))
+    clear_cache(self, 'get_rqlexprs')
+    clear_cache(self, 'get_groups')
+PermissionMixIn.set_action_permissions = set_action_permissions
 
-def ERSchema_has_perm(self, session, action, *args, **kwargs):
-    """return true if the action is granted globaly or localy"""
-    try:
-        self.check_perm(session, action, *args, **kwargs)
-        return True
-    except Unauthorized:
-        return False
-ERSchema.has_perm = ERSchema_has_perm
-
-def ERSchema_has_local_role(self, action):
+def has_local_role(self, action):
     """return true if the action *may* be granted localy (eg either rql
     expressions or the owners group are used in security definition)
 
@@ -230,9 +192,83 @@
     if self.get_rqlexprs(action):
         return True
     if action in ('update', 'delete'):
-        return self.has_group(action, 'owners')
+        return 'owners' in self.get_groups(action)
     return False
-ERSchema.has_local_role = ERSchema_has_local_role
+PermissionMixIn.has_local_role = has_local_role
+
+def may_have_permission(self, action, req):
+    if action != 'read' and not (self.has_local_role('read') or
+                                 self.has_perm(req, 'read')):
+        return False
+    return self.has_local_role(action) or self.has_perm(req, action)
+PermissionMixIn.may_have_permission = may_have_permission
+
+def has_perm(self, session, action, **kwargs):
+    """return true if the action is granted globaly or localy"""
+    try:
+        self.check_perm(session, action, **kwargs)
+        return True
+    except Unauthorized:
+        return False
+PermissionMixIn.has_perm = has_perm
+
+def check_perm(self, session, action, **kwargs):
+    # NB: session may be a server session or a request object check user is
+    # in an allowed group, if so that's enough internal sessions should
+    # always stop there
+    groups = self.get_groups(action)
+    if session.user.matching_groups(groups):
+        return
+    # if 'owners' in allowed groups, check if the user actually owns this
+    # object, if so that's enough
+    if 'owners' in groups and 'eid' in kwargs and session.user.owns(kwargs['eid']):
+        return
+    # else if there is some rql expressions, check them
+    if any(rqlexpr.check(session, **kwargs)
+           for rqlexpr in self.get_rqlexprs(action)):
+        return
+    raise Unauthorized(action, str(self))
+PermissionMixIn.check_perm = check_perm
+
+
+RelationDefinitionSchema._RPROPERTIES['eid'] = None
+
+def rql_expression(self, expression, mainvars=None, eid=None):
+    """rql expression factory"""
+    if self.rtype.final:
+        return ERQLExpression(expression, mainvars, eid)
+    return RRQLExpression(expression, mainvars, eid)
+RelationDefinitionSchema.rql_expression = rql_expression
+
+orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions
+def check_permission_definitions(self):
+    orig_check_permission_definitions(self)
+    schema = self.subject.schema
+    for action, groups in self.permissions.iteritems():
+        for group_or_rqlexpr in groups:
+            if action == 'read' and \
+                   isinstance(group_or_rqlexpr, RQLExpression):
+                msg = "can't use rql expression for read permission of %s"
+                raise BadSchemaDefinition(msg % self)
+            elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
+                if schema.reading_from_database:
+                    # we didn't have final relation earlier, so turn
+                    # RRQLExpression into ERQLExpression now
+                    rqlexpr = group_or_rqlexpr
+                    newrqlexprs = [x for x in self.get_rqlexprs(action)
+                                   if not x is rqlexpr]
+                    newrqlexprs.append(ERQLExpression(rqlexpr.expression,
+                                                      rqlexpr.mainvars,
+                                                      rqlexpr.eid))
+                    self.set_rqlexprs(action, newrqlexprs)
+                else:
+                    msg = "can't use RRQLExpression on %s, use an ERQLExpression"
+                    raise BadSchemaDefinition(msg % self)
+            elif not self.final and \
+                     isinstance(group_or_rqlexpr, ERQLExpression):
+                msg = "can't use ERQLExpression on %s, use a RRQLExpression"
+                raise BadSchemaDefinition(msg % self)
+RelationDefinitionSchema.check_permission_definitions = check_permission_definitions
 
 
 def system_etypes(schema):
@@ -256,8 +292,8 @@
             eid = getattr(edef, 'eid', None)
         self.eid = eid
         # take care: no _groups attribute when deep-copying
-        if getattr(self, '_groups', None):
-            for groups in self._groups.itervalues():
+        if getattr(self, 'permissions', None):
+            for groups in self.permissions.itervalues():
                 for group_or_rqlexpr in groups:
                     if isinstance(group_or_rqlexpr, RRQLExpression):
                         msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)"
@@ -304,7 +340,7 @@
             if rschema.final:
                 if rschema == 'has_text':
                     has_has_text = True
-                elif self.rproperty(rschema, 'fulltextindexed'):
+                elif self.rdef(rschema).get('fulltextindexed'):
                     may_need_has_text = True
             elif rschema.fulltext_container:
                 if rschema.fulltext_container == 'subject':
@@ -329,32 +365,12 @@
         """return True if this entity type is used to build the schema"""
         return self.type in SCHEMA_TYPES
 
-    def check_perm(self, session, action, eid=None):
-        # NB: session may be a server session or a request object
-        user = session.user
-        # check user is in an allowed group, if so that's enough
-        # internal sessions should always stop there
-        if user.matching_groups(self.get_groups(action)):
-            return
-        # if 'owners' in allowed groups, check if the user actually owns this
-        # object, if so that's enough
-        if eid is not None and 'owners' in self.get_groups(action) and \
-               user.owns(eid):
-            return
-        # else if there is some rql expressions, check them
-        if any(rqlexpr.check(session, eid)
-               for rqlexpr in self.get_rqlexprs(action)):
-            return
-        raise Unauthorized(action, str(self))
-
     def rql_expression(self, expression, mainvars=None, eid=None):
         """rql expression factory"""
         return ERQLExpression(expression, mainvars, eid)
 
 
 class CubicWebRelationSchema(RelationSchema):
-    RelationSchema._RPROPERTIES['eid'] = None
-    _perms_checked = False
 
     def __init__(self, schema=None, rdef=None, eid=None, **kwargs):
         if rdef is not None:
@@ -369,73 +385,52 @@
     def meta(self):
         return self.type in META_RTYPES
 
-    def update(self, subjschema, objschema, rdef):
-        super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef)
-        if not self._perms_checked and self._groups:
-            for action, groups in self._groups.iteritems():
-                for group_or_rqlexpr in groups:
-                    if action == 'read' and \
-                           isinstance(group_or_rqlexpr, RQLExpression):
-                        msg = "can't use rql expression for read permission of "\
-                              "a relation type (%s)"
-                        raise BadSchemaDefinition(msg % self.type)
-                    elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
-                        if self.schema.reading_from_database:
-                            # we didn't have final relation earlier, so turn
-                            # RRQLExpression into ERQLExpression now
-                            rqlexpr = group_or_rqlexpr
-                            newrqlexprs = [x for x in self.get_rqlexprs(action) if not x is rqlexpr]
-                            newrqlexprs.append(ERQLExpression(rqlexpr.expression,
-                                                              rqlexpr.mainvars,
-                                                              rqlexpr.eid))
-                            self.set_rqlexprs(action, newrqlexprs)
-                        else:
-                            msg = "can't use RRQLExpression on a final relation "\
-                                  "type (eg attribute relation), use an ERQLExpression (%s)"
-                            raise BadSchemaDefinition(msg % self.type)
-                    elif not self.final and \
-                             isinstance(group_or_rqlexpr, ERQLExpression):
-                        msg = "can't use ERQLExpression on a relation type, use "\
-                              "a RRQLExpression (%s)"
-                        raise BadSchemaDefinition(msg % self.type)
-            self._perms_checked = True
-
-    def cardinality(self, subjtype, objtype, target):
-        card = self.rproperty(subjtype, objtype, 'cardinality')
-        return (target == 'subject' and card[0]) or \
-               (target == 'object' and card[1])
-
     def schema_relation(self):
         """return True if this relation type is used to build the schema"""
         return self.type in SCHEMA_TYPES
 
-    def physical_mode(self):
-        """return an appropriate mode for physical storage of this relation type:
-        * 'subjectinline' if every possible subject cardinalities are 1 or ?
-        * 'objectinline' if 'subjectinline' mode is not possible but every
-          possible object cardinalities are 1 or ?
-        * None if neither 'subjectinline' and 'objectinline'
-        """
-        assert not self.final
-        return self.inlined and 'subjectinline' or None
+    def may_have_permission(self, action, req, eschema=None, role=None):
+        if eschema is not None:
+            for tschema in rschema.targets(eschema, role):
+                rdef = rschema.role_rdef(eschema, tschema, role)
+                if rdef.may_have_permission(action, req):
+                    return True
+        else:
+            for rdef in self.rdefs.itervalues():
+                if rdef.may_have_permission(action, req):
+                    return True
+        return False
 
-    def check_perm(self, session, action, *args, **kwargs):
-        # NB: session may be a server session or a request object check user is
-        # in an allowed group, if so that's enough internal sessions should
-        # always stop there
-        if session.user.matching_groups(self.get_groups(action)):
-            return
-        # else if there is some rql expressions, check them
-        if any(rqlexpr.check(session, *args, **kwargs)
-               for rqlexpr in self.get_rqlexprs(action)):
-            return
-        raise Unauthorized(action, str(self))
+    def has_perm(self, session, action, **kwargs):
+        """return true if the action is granted globaly or localy"""
+        if 'fromeid' in kwargs:
+            subjtype = session.describe(kwargs['fromeid'])
+        else:
+            subjtype = None
+        if 'toeid' in kwargs:
+            objtype = session.describe(kwargs['toeid'])
+        else:
+            objtype = Nono
+        if objtype and subjtype:
+            return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+        elif subjtype:
+            for tschema in rschema.targets(subjtype, 'subject'):
+                rdef = rschema.rdef(subjtype, tschema)
+                if not rdef.has_perm(action, req, **kwargs):
+                    return False
+        elif objtype:
+            for tschema in rschema.targets(objtype, 'object'):
+                rdef = rschema.rdef(tschema, objtype)
+                if not rdef.has_perm(action, req, **kwargs):
+                    return False
+        else:
+            for rdef in self.rdefs.itervalues():
+                if not rdef.has_perm(action, req, **kwargs):
+                    return False
 
-    def rql_expression(self, expression, mainvars=None, eid=None):
-        """rql expression factory"""
-        if self.final:
-            return ERQLExpression(expression, mainvars, eid)
-        return RRQLExpression(expression, mainvars, eid)
+    @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)')
+    def cardinality(self, subjtype, objtype, target):
+        return self.rdef(subjtype, objtype).role_cardinality(target)
 
 
 class CubicWebSchema(Schema):
@@ -460,13 +455,10 @@
         ybo.register_base_types(self)
         rschema = self.add_relation_type(ybo.RelationType('eid'))
         rschema.final = True
-        rschema.set_default_groups()
         rschema = self.add_relation_type(ybo.RelationType('has_text'))
         rschema.final = True
-        rschema.set_default_groups()
         rschema = self.add_relation_type(ybo.RelationType('identity'))
         rschema.final = False
-        rschema.set_default_groups()
 
     def add_entity_type(self, edef):
         edef.name = edef.name.encode()
@@ -508,11 +500,10 @@
         rdef.name = rdef.name.lower()
         rdef.subject = bw_normalize_etype(rdef.subject)
         rdef.object = bw_normalize_etype(rdef.object)
-        if super(CubicWebSchema, self).add_relation_def(rdef):
+        rdefs = super(CubicWebSchema, self).add_relation_def(rdef)
+        if rdefs:
             try:
-                self._eid_index[rdef.eid] = (self.eschema(rdef.subject),
-                                             self.rschema(rdef.name),
-                                             self.eschema(rdef.object))
+                self._eid_index[rdef.eid] = rdefs
             except AttributeError:
                 pass # not a serialized schema
 
@@ -523,7 +514,9 @@
 
     def del_relation_def(self, subjtype, rtype, objtype):
         for k, v in self._eid_index.items():
-            if v == (subjtype, rtype, objtype):
+            if not isinstance(v, RelationDefinitionSchema):
+                continue
+            if v.subject == subjtype and v.rtype == rtype and v.object == objtype:
                 del self._eid_index[k]
                 break
         super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype)
@@ -654,6 +647,11 @@
     def __repr__(self):
         return '%s(%s)' % (self.__class__.__name__, self.full_rql)
 
+    def __cmp__(self, other):
+        if hasattr(other, 'expression'):
+            return cmp(other.expression, self.expression)
+        return False
+
     def __deepcopy__(self, memo):
         return self.__class__(self.expression, self.mainvars)
     def __getstate__(self):
@@ -755,7 +753,7 @@
                 for eaction, var, col in has_perm_defs:
                     for i in xrange(len(rset)):
                         eschema = get_eschema(rset.description[i][col])
-                        eschema.check_perm(session, eaction, rset[i][col])
+                        eschema.check_perm(session, eaction, eid=rset[i][col])
                 if self.eid is not None:
                     session.local_perm_cache[key] = True
                 return True
--- a/schemas/Bookmark.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schemas/Bookmark.py	Thu Nov 19 12:55:47 2009 +0100
@@ -13,7 +13,7 @@
 
 class Bookmark(EntityType):
     """bookmarks are used to have user's specific internal links"""
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', 'users',),
         'delete': ('managers', 'owners',),
@@ -29,7 +29,7 @@
 
 
 class bookmarked_by(RelationType):
-    permissions = {'read':   ('managers', 'users', 'guests',),
+    __permissions__ = {'read':   ('managers', 'users', 'guests',),
                    # test user in users group to avoid granting permission to anonymous user
                    'add':    ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
                    'delete': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
--- a/schemas/base.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schemas/base.py	Thu Nov 19 12:55:47 2009 +0100
@@ -16,7 +16,7 @@
 
 class CWUser(WorkflowableEntityType):
     """define a CubicWeb user"""
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', ERQLExpression('X identity U')),
         'add':    ('managers',),
         'delete': ('managers',),
@@ -37,12 +37,12 @@
 
     in_group = SubjectRelation('CWGroup', cardinality='+*',
                                constraints=[RQLConstraint('NOT O name "owners"')],
-                               description=_('groups grant permissions to the user'))
+                               description=_('groups grant __permissions__ to the user'))
 
 
 class EmailAddress(EntityType):
     """an electronic mail address associated to a short alias"""
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',), # XXX if P use_email X, U has_read_permission P
         'add':    ('managers', 'users',),
         'delete': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
@@ -59,7 +59,7 @@
 
 class use_email(RelationType):
     """ """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', RRQLExpression('U has_update_permission S'),),
         'delete': ('managers', RRQLExpression('U has_update_permission S'),),
@@ -68,12 +68,12 @@
 
 class primary_email(RelationType):
     """the prefered email"""
-    permissions = use_email.permissions
+    __permissions__ = use_email.__permissions__
 
 class prefered_form(RelationType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
-        # XXX should have update permissions on both subject and object,
+        # XXX should have update __permissions__ on both subject and object,
         #     though by doing this we will probably have no way to add
         #     this relation in the web ui. The easiest way to acheive this
         #     is probably to be able to have "U has_update_permission O" as
@@ -85,13 +85,13 @@
 
 class in_group(RelationType):
     """core relation indicating a user's groups"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class owned_by(RelationType):
     """core relation indicating owners of an entity. This relation
     implicitly put the owner into the owners group for the entity
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', RRQLExpression('S owned_by U'),),
         'delete': ('managers', RRQLExpression('S owned_by U'),),
@@ -104,7 +104,7 @@
 
 class created_by(RelationType):
     """core relation indicating the original creator of an entity"""
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'delete': ('managers',),
@@ -139,7 +139,7 @@
     """used for cubicweb configuration. Once a property has been created you
     can't change the key.
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', 'users',),
         'update': ('managers', 'owners',),
@@ -163,7 +163,7 @@
     """link a property to the user which want this property customization. Unless
     you're a site manager, this relation will be handled automatically.
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'delete': ('managers',),
@@ -174,7 +174,7 @@
 class CWPermission(EntityType):
     """entity type that may be used to construct some advanced security configuration
     """
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
                   description=_('name or identifier of the permission'))
@@ -189,7 +189,7 @@
     """link a permission to the entity. This permission should be used in the
     security definition of the entity's type to be useful.
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'delete': ('managers',),
@@ -197,7 +197,7 @@
 
 class require_group(RelationType):
     """used to grant a permission to a group"""
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'delete': ('managers',),
@@ -217,7 +217,7 @@
     NOTE: You'll have to explicitly declare which entity types can have a
     same_as relation
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', 'users'),
         'delete': ('managers', 'owners'),
@@ -237,7 +237,7 @@
 
     Also, checkout the AppObject.get_cache() method.
     """
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'update': ('managers', 'users',), # XXX
@@ -254,9 +254,9 @@
 class identical_to(RelationType):
     """identical to"""
     symetric = True
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
-        # XXX should have update permissions on both subject and object,
+        # XXX should have update __permissions__ on both subject and object,
         #     though by doing this we will probably have no way to add
         #     this relation in the web ui. The easiest way to acheive this
         #     is probably to be able to have "U has_update_permission O" as
@@ -269,7 +269,7 @@
 class see_also(RelationType):
     """generic relation to link one entity to another"""
     symetric = True
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', RRQLExpression('U has_update_permission S'),),
         'delete': ('managers', RRQLExpression('U has_update_permission S'),),
--- a/schemas/bootstrap.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schemas/bootstrap.py	Thu Nov 19 12:55:47 2009 +0100
@@ -17,7 +17,7 @@
 # access to this
 class CWEType(EntityType):
     """define an entity type, used to build the instance schema"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
     description = RichString(internationalizable=True,
@@ -28,7 +28,7 @@
 
 class CWRType(EntityType):
     """define a relation type, used to build the instance schema"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
     description = RichString(internationalizable=True,
@@ -48,7 +48,7 @@
 
     used to build the instance schema
     """
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final TRUE')],
                                     composite='object')
@@ -85,7 +85,7 @@
 
     used to build the instance schema
     """
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final FALSE')],
                                     composite='object')
@@ -115,8 +115,8 @@
 
 # not restricted since it has to be read when checking allowed transitions
 class RQLExpression(EntityType):
-    """define a rql expression used to define permissions"""
-    permissions = META_ETYPE_PERMS
+    """define a rql expression used to define __permissions__"""
+    __permissions__ = META_ETYPE_PERMS
     exprtype = String(required=True, vocabulary=['ERQLExpression', 'RRQLExpression'])
     mainvars = String(maxsize=8,
                       description=_('name of the main variables which should be '
@@ -131,11 +131,11 @@
                                       'relation\'subject, object and to '
                                       'the request user. '))
 
-    read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+?', composite='subject',
+    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='+?', composite='subject',
                                       description=_('rql expression allowing to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
                                      description=_('rql expression allowing to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+    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'))
@@ -143,14 +143,14 @@
 
 class CWConstraint(EntityType):
     """define a schema constraint"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
     value = String(description=_('depends on the constraint type'))
 
 
 class CWConstraintType(EntityType):
     """define a schema constraint type"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
@@ -158,15 +158,15 @@
 # not restricted since it has to be read when checking allowed transitions
 class CWGroup(EntityType):
     """define a CubicWeb users group"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
-    read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+*',
+    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='+*',
                                       description=_('groups allowed to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWRType'),
+    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
                                      description=_('groups allowed to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWRType'),
+    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'))
@@ -175,50 +175,50 @@
 
 class relation_type(RelationType):
     """link a relation definition to its relation type"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class from_entity(RelationType):
     """link a relation definition to its subject entity type"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class to_entity(RelationType):
     """link a relation definition to its object entity type"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class constrained_by(RelationType):
     """constraints applying on this relation"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class cstrtype(RelationType):
     """constraint factory"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class read_permission(RelationType):
     """core relation giving to a group the permission to read an entity or
     relation type
     """
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class add_permission(RelationType):
     """core relation giving to a group the permission to add an entity or
     relation type
     """
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class delete_permission(RelationType):
     """core relation giving to a group the permission to delete an entity or
     relation type
     """
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class update_permission(RelationType):
     """core relation giving to a group the permission to update an entity type
     """
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 
 class is_(RelationType):
@@ -227,7 +227,7 @@
     name = 'is'
     # don't explicitly set composite here, this is handled anyway
     #composite = 'object'
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    (),
         'delete': (),
@@ -242,7 +242,7 @@
     """
     # don't explicitly set composite here, this is handled anyway
     #composite = 'object'
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    (),
         'delete': (),
@@ -253,7 +253,7 @@
 
 class specializes(RelationType):
     name = 'specializes'
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
         'delete': ('managers',),
--- a/schemas/workflow.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schemas/workflow.py	Thu Nov 19 12:55:47 2009 +0100
@@ -15,7 +15,7 @@
                               HOOKS_RTYPE_PERMS)
 
 class Workflow(EntityType):
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256)
@@ -33,7 +33,7 @@
 
 class default_workflow(RelationType):
     """default workflow for an entity type"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
     subject = 'CWEType'
     object = 'Workflow'
@@ -45,7 +45,7 @@
     """used to associate simple states to an entity type and/or to define
     workflows
     """
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256)
@@ -64,7 +64,7 @@
 
 class BaseTransition(EntityType):
     """abstract base class for transitions"""
-    permissions = META_ETYPE_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256)
@@ -126,7 +126,7 @@
 class TrInfo(EntityType):
     """workflow history item"""
     # 'add' security actually done by hooks
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',), # XXX U has_read_permission O ?
         'add':    ('managers', 'users', 'guests',),
         'delete': (), # XXX should we allow managers to delete TrInfo?
@@ -142,11 +142,11 @@
     # get actor and date time using owned_by and creation_date
 
 class from_state(RelationType):
-    permissions = HOOKS_RTYPE_PERMS.copy()
+    __permissions__ = HOOKS_RTYPE_PERMS.copy()
     inlined = True
 
 class to_state(RelationType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers',),
         'delete': (),
@@ -155,7 +155,7 @@
 
 class by_transition(RelationType):
     # 'add' security actually done by hooks
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', 'users', 'guests',),
         'delete': (),
@@ -164,52 +164,52 @@
 
 class workflow_of(RelationType):
     """link a workflow to one or more entity type"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class state_of(RelationType):
     """link a state to one or more workflow"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class transition_of(RelationType):
     """link a transition to one or more workflow"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class subworkflow(RelationType):
     """link a transition to one or more workflow"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class exit_point(RelationType):
     """link a transition to one or more workflow"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class subworkflow_state(RelationType):
     """link a transition to one or more workflow"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class initial_state(RelationType):
     """indicate which state should be used by default when an entity using
     states is created
     """
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class destination_state(RelationType):
     """destination state of a transition"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class allowed_transition(RelationType):
     """allowed transition from this state"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 
 # "abstract" relations, set by WorkflowableEntityType ##########################
 
 class custom_workflow(RelationType):
     """allow to set a specific workflow for an entity"""
-    permissions = META_RTYPE_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
     cardinality = '?*'
     constraints = [RQLConstraint('S is ET, O workflow_of ET')]
@@ -219,7 +219,7 @@
 class wf_info_for(RelationType):
     """link a transition information to its object"""
     # 'add' security actually done by hooks
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', 'users', 'guests',),
         'delete': (),
@@ -234,7 +234,7 @@
 
 class in_state(RelationType):
     """indicate the current state of an entity"""
-    permissions = HOOKS_RTYPE_PERMS
+    __permissions__ = HOOKS_RTYPE_PERMS
 
     # not inlined intentionnaly since when using ldap sources, user'state
     # has to be stored outside the CWUser table
--- a/schemaviewer.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/schemaviewer.py	Thu Nov 19 12:55:47 2009 +0100
@@ -13,6 +13,7 @@
 
 I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
 
+
 class SchemaViewer(object):
     """return an ureport layout for some part of a schema"""
     def __init__(self, req=None, encoding=None):
@@ -68,7 +69,8 @@
         _ = self.req._
         data = [_('attribute'), _('type'), _('default'), _('constraints')]
         for rschema, aschema in eschema.attribute_definitions():
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+            rdef = eschema.rdef(rschema)
+            if not rdef.may_have_permission('read', self.req):
                 continue
             aname = rschema.type
             if aname == 'eid':
@@ -78,7 +80,7 @@
             defaultval = eschema.default(aname)
             if defaultval is not None:
                 default = self.to_string(defaultval)
-            elif eschema.rproperty(rschema, 'cardinality')[0] == '1':
+            elif rdef.cardinality[0] == '1':
                 default = _('required field')
             else:
                 default = ''
@@ -119,20 +121,23 @@
         t_vars = []
         rels = []
         first = True
-        for rschema, targetschemas, x in eschema.relation_definitions():
+        for rschema, targetschemas, role in eschema.relation_definitions():
             if rschema.type in skiptypes:
                 continue
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
-                continue
             rschemaurl = self.rschema_link_url(rschema)
             for oeschema in targetschemas:
+                rdef = rschema.role_rdef(eschema, oeschema, role)
+                if not rdef.may_have_permission('read', self.req):
+                    continue
                 label = rschema.type
-                if x == 'subject':
+                if role == 'subject':
                     cards = rschema.rproperty(eschema, oeschema, 'cardinality')
                 else:
                     cards = rschema.rproperty(oeschema, eschema, 'cardinality')
                     cards = cards[::-1]
-                label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label, display_name(self.req, label, x), CARD_MAP[cards[0]])
+                label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label,
+                                           display_name(self.req, label, role),
+                                           CARD_MAP[cards[0]])
                 rlink = Link(rschemaurl, label)
                 elink = Link(self.eschema_link_url(oeschema), oeschema.type)
                 if first:
--- a/selectors.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/selectors.py	Thu Nov 19 12:55:47 2009 +0100
@@ -665,19 +665,6 @@
         self.target_etype = target_etype
         self.action = action
 
-    @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        rschema = cls.schema.rschema(self.rtype)
-        if not (rschema.has_perm(req, self.action)
-                or rschema.has_local_role(self.action)):
-            return 0
-        if self.action != 'read':
-            if not (rschema.has_perm(req, 'read')
-                    or rschema.has_local_role('read')):
-                return 0
-        score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
-        return score
-
     def score_class(self, eclass, req):
         eschema = eclass.e_schema
         try:
@@ -689,12 +676,13 @@
             return 0
         if self.target_etype is not None:
             try:
-                if self.role == 'subject':
-                    return int(self.target_etype in rschema.objects(eschema))
-                else:
-                    return int(self.target_etype in rschema.subjects(eschema))
+                rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+                if not rdef.may_have_permission(self.action, req):
+                    return 0
             except KeyError:
                 return 0
+        else:
+            return rschema.may_have_permission(self.action, req, eschema, self.role)
         return 1
 
 
@@ -1070,11 +1058,11 @@
     perm = getattr(cls, 'require_permission', 'read')
     if hasattr(cls, 'etype'):
         eschema = schema.eschema(cls.etype)
-        if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
+        if not eschema.may_have_permission(perm, req):
             return 0
     if hasattr(cls, 'rtype'):
         rschema = schema.rschema(cls.rtype)
-        if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
+        if not rschema.may_have_permission(perm, req):
             return 0
     return 1
 etype_rtype_selector = deprecated()(etype_rtype_selector)
--- a/server/hookhelper.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/hookhelper.py	Thu Nov 19 12:55:47 2009 +0100
@@ -27,7 +27,7 @@
     rschema = session.repo.schema[rtype]
     subjtype = session.describe(eidfrom)[0]
     objtype = session.describe(eidto)[0]
-    return rschema.rproperty(subjtype, objtype, rprop)
+    return getattr(rschema.rdef(subjtype, objtype), rprop)
 
 def check_internal_entity(session, eid, internal_names):
     """check that the entity's name is not in the internal_names list.
--- a/server/hooks.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/hooks.py	Thu Nov 19 12:55:47 2009 +0100
@@ -260,7 +260,7 @@
     schema = session.vreg.schema
     for attr in entity.edited_attributes:
         if schema.rschema(attr).final:
-            constraints = [c for c in entity.e_schema.constraints(attr)
+            constraints = [c for c in entity.e_schema.rdef(attr).constraints
                            if isinstance(c, RQLVocabularyConstraint)]
             if constraints:
                 CheckConstraintsOperation(session, rdef=(entity.eid, attr, None),
@@ -317,22 +317,20 @@
     if session.is_super_session:
         return
     eid = entity.eid
-    for rschema, targetschemas, x in entity.e_schema.relation_definitions():
+    for rschema, targetschemas, role in entity.e_schema.relation_definitions():
         # skip automatically handled relations
         if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
             continue
-        if x == 'subject':
+        if role == 'subject':
             subjtype = entity.e_schema
             objtype = targetschemas[0].type
-            cardindex = 0
             opcls = CheckSRelationOp
         else:
             subjtype = targetschemas[0].type
             objtype = entity.e_schema
-            cardindex = 1
             opcls = CheckORelationOp
-        card = rschema.rproperty(subjtype, objtype, 'cardinality')
-        if card[cardindex] in '1+':
+        card = rschema.rdef(subjtype, objtype).role_cardinality(role)
+        if card in '1+':
             checkrel_if_necessary(session, opcls, rschema.type, eid)
 
 def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto):
--- a/server/migractions.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/migractions.py	Thu Nov 19 12:55:47 2009 +0100
@@ -305,35 +305,33 @@
 
     # schema synchronization internals ########################################
 
-    def _synchronize_permissions(self, ertype):
+    def _synchronize_permissions(self, erschema, teid):
         """permission synchronization for an entity or relation type"""
-        if ertype in VIRTUAL_RTYPES:
+        if erschema in VIRTUAL_RTYPES:
             return
-        newrschema = self.fs_schema[ertype]
-        teid = self.repo.schema[ertype].eid
-        if 'update' in newrschema.ACTIONS or newrschema.final:
+        assert teid, erschema
+        if 'update' in erschema.ACTIONS or erschema.final:
             # entity type
             exprtype = u'ERQLExpression'
         else:
             # relation type
             exprtype = u'RRQLExpression'
-        assert teid, ertype
         gm = self.group_mapping()
         confirm = self.verbosity >= 2
         # * remove possibly deprecated permission (eg in the persistent schema
         #   but not in the new schema)
         # * synchronize existing expressions
         # * add new groups/expressions
-        for action in newrschema.ACTIONS:
+        for action in erschema.ACTIONS:
             perm = '%s_permission' % action
             # handle groups
-            newgroups = list(newrschema.get_groups(action))
+            newgroups = list(erschema.get_groups(action))
             for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
                                             'T eid %%(x)s' % perm, {'x': teid}, 'x',
                                             ask_confirm=False):
                 if not gname in newgroups:
                     if not confirm or self.confirm('remove %s permission of %s to %s?'
-                                                   % (action, ertype, gname)):
+                                                   % (action, erschema, gname)):
                         self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
                                      % (perm, teid),
                                      {'x': geid}, 'x', ask_confirm=False)
@@ -341,18 +339,18 @@
                     newgroups.remove(gname)
             for gname in newgroups:
                 if not confirm or self.confirm('grant %s permission of %s to %s?'
-                                               % (action, ertype, gname)):
+                                               % (action, erschema, gname)):
                     self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s'
                                  % (perm, teid),
                                  {'x': gm[gname]}, 'x', ask_confirm=False)
             # handle rql expressions
-            newexprs = dict((expr.expression, expr) for expr in newrschema.get_rqlexprs(action))
+            newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action))
             for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
                                                     'T eid %s' % (perm, teid),
                                                     ask_confirm=False):
                 if not expression in newexprs:
                     if not confirm or self.confirm('remove %s expression for %s permission of %s?'
-                                                   % (expression, action, ertype)):
+                                                   % (expression, action, erschema)):
                         # deleting the relation will delete the expression entity
                         self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
                                      % (perm, teid),
@@ -362,7 +360,7 @@
             for expression in newexprs.values():
                 expr = expression.expression
                 if not confirm or self.confirm('add %s expression for %s permission of %s?'
-                                               % (expr, action, ertype)):
+                                               % (expr, action, erschema)):
                     self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, '
                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
                                  'WHERE T eid %%(x)s' % perm,
@@ -394,9 +392,7 @@
             for subj, obj in rschema.iter_rdefs():
                 if not reporschema.has_rdef(subj, obj):
                     continue
-                self._synchronize_rdef_schema(subj, rschema, obj)
-        if syncperms:
-            self._synchronize_permissions(rtype)
+                self._synchronize_rdef_schema(subj, rschema, obj, syncperms=syncperms)
 
     def _synchronize_eschema(self, etype, syncperms=True):
         """synchronize properties of the persistent entity schema against
@@ -446,9 +442,9 @@
                         continue
                     self._synchronize_rdef_schema(subj, rschema, obj)
         if syncperms:
-            self._synchronize_permissions(etype)
+            self._synchronize_permissions(eschema, repoeschema.eid)
 
-    def _synchronize_rdef_schema(self, subjtype, rtype, objtype):
+    def _synchronize_rdef_schema(self, subjtype, rtype, objtype, syncperms=True):
         """synchronize properties of the persistent relation definition schema
         against its current definition:
         * order and other properties
@@ -467,12 +463,14 @@
         self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
                         ask_confirm=confirm)
         # constraints
-        newconstraints = list(rschema.rproperty(subjtype, objtype, 'constraints'))
+        rdef = rschema.rdef(subjtype, objtype)
+        repordef = reporschema.rdef(subjtype, objtype)
+        newconstraints = list(rdef.constraints)
         # 1. remove old constraints and update constraints of the same type
         # NOTE: don't use rschema.constraint_by_type because it may be
         #       out of sync with newconstraints when multiple
         #       constraints of the same type are used
-        for cstr in reporschema.rproperty(subjtype, objtype, 'constraints'):
+        for cstr in repordef.constraints:
             for newcstr in newconstraints:
                 if newcstr.type() == cstr.type():
                     break
@@ -496,6 +494,8 @@
             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
                                               newcstr),
                             ask_confirm=confirm)
+        if syncperms:
+            self._synchronize_permissions(rdef, repordef.eid)
 
     # base actions ############################################################
 
@@ -888,7 +888,8 @@
             if isinstance(ertype, (tuple, list)):
                 assert len(ertype) == 3, 'not a relation definition'
                 assert syncprops, 'can\'t update permission for a relation definition'
-                self._synchronize_rdef_schema(*ertype)
+                self._synchronize_rdef_schema(ertype[0], ertype[1], ertype[2],
+                                              syncperms=syncperms)
             elif syncprops:
                 erschema = self.repo.schema[ertype]
                 if isinstance(erschema, CubicWebRelationSchema):
@@ -897,13 +898,14 @@
                 else:
                     self._synchronize_eschema(erschema, syncperms=syncperms)
             else:
-                self._synchronize_permissions(ertype)
+                erschema = self.repo.schema[ertype]
+                self._synchronize_permissions(self.fs_schema[ertype], erschema.eid)
         else:
             for etype in self.repo.schema.entities():
                 if syncprops:
                     self._synchronize_eschema(etype, syncperms=syncperms)
                 else:
-                    self._synchronize_permissions(etype)
+                    self._synchronize_permissions(self.fs_schema[etype], erschema.eid)
         if commit:
             self.commit()
 
--- a/server/querier.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/querier.py	Thu Nov 19 12:55:47 2009 +0100
@@ -71,14 +71,23 @@
             # XXX has_text may have specific perm ?
             if rel.r_type in READ_ONLY_RTYPES:
                 continue
-            if not schema.rschema(rel.r_type).has_access(user, 'read'):
+            rschema = schema.rschema(rel.r_type)
+            if rschema.final:
+                eschema = schema.eschema(solution[rel.children[0].name])
+                rdef = eschema.rdef(rschema)
+            else:
+                rdef = rschema.rdef(solution[rel.children[0].name],
+                                    solution[rel.children[1].children[0].name])
+            if not user.matching_groups(rdef.get_groups('read')):
                 raise Unauthorized('read', rel.r_type)
     localchecks = {}
     # iterate on defined_vars and not on solutions to ignore column aliases
     for varname in rqlst.defined_vars:
         etype = solution[varname]
         eschema = schema.eschema(etype)
-        if not eschema.has_access(user, 'read'):
+        if eschema.final:
+            continue
+        if not user.matching_groups(eschema.get_groups('read')):
             erqlexprs = eschema.get_rqlexprs('read')
             if not erqlexprs:
                 ex = Unauthorized('read', etype)
--- a/server/repository.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/repository.py	Thu Nov 19 12:55:47 2009 +0100
@@ -1087,7 +1087,7 @@
                 continue
             rschema = eschema.subjrels[attr]
             if rschema.final:
-                if eschema.rproperty(attr, 'fulltextindexed'):
+                if getattr(eschema.rdef(attr), 'fulltextindexed', False):
                     need_fti_update = True
                 only_inline_rels = False
             else:
--- a/server/schemahooks.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/schemahooks.py	Thu Nov 19 12:55:47 2009 +0100
@@ -180,18 +180,6 @@
         return i + 1
 
 
-class MemSchemaPermissionOperation(MemSchemaOperation):
-    """base class to synchronize schema permission definitions"""
-    def __init__(self, session, perm, etype_eid):
-        self.perm = perm
-        try:
-            self.name = session.entity_from_eid(etype_eid).name
-        except IndexError:
-            self.error('changing permission of a no more existant type #%s',
-                etype_eid)
-        else:
-            Operation.__init__(self, session)
-
 
 # operations for high-level source database alteration  ########################
 
@@ -487,7 +475,7 @@
             return
         subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid)
         cstrtype = self.entity.type
-        oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+        oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
         newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
         table = SQL_PREFIX + str(subjtype)
         column = SQL_PREFIX + str(rtype)
@@ -567,8 +555,7 @@
     """actually add the relation type to the instance's schema"""
     eid = None # make pylint happy
     def commit_event(self):
-        rschema = self.schema.add_relation_type(self.kobj)
-        rschema.set_default_groups()
+        self.schema.add_relation_type(self.kobj)
 
 
 class MemSchemaCWRTypeUpdate(MemSchemaOperation):
@@ -606,7 +593,7 @@
     def commit_event(self):
         # structure should be clean, not need to remove entity's relations
         # at this point
-        self.rschema._rproperties[self.kobj].update(self.values)
+        self.rschema.rdef[self.kobj].update(self.values)
 
 
 class MemSchemaRDefDel(MemSchemaOperation):
@@ -638,7 +625,7 @@
         subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid)
         self.prepare_constraints(subjtype, rtype, objtype)
         cstrtype = self.entity.type
-        self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+        self.cstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
         self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
         self.newcstr.eid = self.entity.eid
 
@@ -664,33 +651,33 @@
         self.constraints.remove(self.cstr)
 
 
-class MemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation):
+class MemSchemaPermissionAdd(MemSchemaOperation):
     """synchronize schema when a *_permission relation has been added on a group
     """
-    def __init__(self, session, perm, etype_eid, group_eid):
-        self.group = session.entity_from_eid(group_eid).name
-        super(MemSchemaPermissionCWGroupAdd, self).__init__(
-            session, perm, etype_eid)
 
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
-            erschema = self.schema[self.name]
+            erschema = self.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)
             return
-        groups = list(erschema.get_groups(self.perm))
+        perms = list(erschema.action_permissions(self.action))
+        if hasattr(self, group_eid):
+            perm = self.session.entity_from_eid(self.group_eid).name
+        else:
+            perm = erschema.rql_expression(self.expr)
         try:
-            groups.index(self.group)
-            self.warning('group %s already have permission %s on %s',
-                         self.group, self.perm, erschema.type)
+            perms.index(perm)
+            self.warning('%s already in permissions for %s on %s',
+                         perm, self.action, erschema)
         except ValueError:
-            groups.append(self.group)
-            erschema.set_groups(self.perm, groups)
+            perms.append(perm)
+            erschema.set_action_permissions(self.action, perms)
 
 
-class MemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd):
+class MemSchemaPermissionDel(MemSchemaPermissionAdd):
     """synchronize schema when a *_permission relation has been deleted from a
     group
     """
@@ -703,60 +690,17 @@
             # duh, schema not found, log error and skip operation
             self.error('no schema for %s', self.name)
             return
-        groups = list(erschema.get_groups(self.perm))
-        try:
-            groups.remove(self.group)
-            erschema.set_groups(self.perm, groups)
-        except ValueError:
-            self.error('can\'t remove permission %s on %s to group %s',
-                self.perm, erschema.type, self.group)
-
-
-class MemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation):
-    """synchronize schema when a *_permission relation has been added on a rql
-    expression
-    """
-    def __init__(self, session, perm, etype_eid, expression):
-        self.expr = expression
-        super(MemSchemaPermissionRQLExpressionAdd, self).__init__(
-            session, perm, etype_eid)
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
+        perms = list(erschema.action_permissions(self.action))
+        if hasattr(self, group_eid):
+            perm = self.session.entity_from_eid(self.group_eid).name
+        else:
+            perm = erschema.rql_expression(self.expr)
         try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        exprs = list(erschema.get_rqlexprs(self.perm))
-        exprs.append(erschema.rql_expression(self.expr))
-        erschema.set_rqlexprs(self.perm, exprs)
-
-
-class MemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd):
-    """synchronize schema when a *_permission relation has been deleted from an
-    rql expression
-    """
-
-    def commit_event(self):
-        """the observed connections pool has been commited"""
-        try:
-            erschema = self.schema[self.name]
-        except KeyError:
-            # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.name)
-            return
-        rqlexprs = list(erschema.get_rqlexprs(self.perm))
-        for i, rqlexpr in enumerate(rqlexprs):
-            if rqlexpr.expression == self.expr:
-                rqlexprs.pop(i)
-                break
-        else:
-            self.error('can\'t remove permission %s on %s for expression %s',
-                self.perm, erschema.type, self.expr)
-            return
-        erschema.set_rqlexprs(self.perm, rqlexprs)
+            perms.remove(self.group)
+            erschema.set_action_permissions(self.action, perms)
+        except ValueError:
+            self.error('can\'t remove permission %s for %s on %s',
+                       perm, self.action, erschema)
 
 
 class MemSchemaSpecializesAdd(MemSchemaOperation):
@@ -894,7 +838,6 @@
     # but remove it before doing anything more dangerous...
     schema = session.schema
     eschema = schema.add_entity_type(etype)
-    eschema.set_default_groups()
     # generate table sql and rql to add metadata
     tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
                            prefix=SQL_PREFIX)
@@ -1033,8 +976,8 @@
         entity = session.entity_from_eid(toeid)
         subjtype, rtype, objtype = schema.schema_by_eid(fromeid)
         try:
-            cstr = rtype.constraint_by_type(subjtype, objtype,
-                                            entity.cstrtype[0].name)
+            cstr = rtype.rdef(subjtype, objtype).constraint_by_type(
+                entity.cstrtype[0].name)
         except IndexError:
             session.critical('constraint type no more accessible')
         else:
@@ -1053,13 +996,14 @@
 
 def after_add_permission(session, subject, rtype, object):
     """added entity/relation *_permission, need to update schema"""
-    perm = rtype.split('_', 1)[0]
+    action = rtype.split('_', 1)[0]
     if session.describe(object)[0] == 'CWGroup':
-        MemSchemaPermissionCWGroupAdd(session, perm, subject, object)
+        MemSchemaPermissionAdd(session, action=action, eid=subject,
+                               group_eid=object)
     else: # RQLExpression
-        expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
-                               {'x': object}, 'x')[0][0]
-        MemSchemaPermissionRQLExpressionAdd(session, perm, subject, expr)
+        expr = session.entity_from_eid(object).expression
+        MemSchemaPermissionAdd(session, action=action, eid=subject,
+                               expr=expr)
 
 
 def before_del_permission(session, subject, rtype, object):
@@ -1069,13 +1013,12 @@
     """
     if subject in session.transaction_data.get('pendingeids', ()):
         return
-    perm = rtype.split('_', 1)[0]
+    action = rtype.split('_', 1)[0]
     if session.describe(object)[0] == 'CWGroup':
-        MemSchemaPermissionCWGroupDel(session, perm, subject, object)
+        MemSchemaPermissionDel(session, action=action, eid=subject, group_eid=object)
     else: # RQLExpression
-        expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
-                               {'x': object}, 'x')[0][0]
-        MemSchemaPermissionRQLExpressionDel(session, perm, subject, expr)
+        expr = session.entity_from_eid(object).expression
+        MemSchemaPermissionDel(session, action=action, eid=subject, expr=expr)
 
 
 def after_add_specializes(session, subject, rtype, object):
--- a/server/schemaserial.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/schemaserial.py	Thu Nov 19 12:55:47 2009 +0100
@@ -256,7 +256,7 @@
                 actperms.append(erschema.rql_expression(*something))
             else: # group name
                 actperms.append(something)
-        erschema.set_permissions(action, actperms)
+        erschema.set_action_permissions(action, actperms)
 
 
 def deserialize_rdef_constraints(session):
@@ -513,25 +513,51 @@
         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(eschema)
+            yield rql + 'X is CWEType, X name %(name)s', args
+
+def _erperms2rql(erschema, groupmapping):
     """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
     and CWGroup entities
     """
-    etype = isinstance(erschema, schemamod.EntitySchema) and 'CWEType' or 'CWRType'
     for action in erschema.ACTIONS:
-        for group in sorted(erschema.get_groups(action)):
-            try:
-                yield ('SET X %s_permission Y WHERE X is %s, X name %%(name)s, Y eid %s'
-                       % (action, etype, groupmapping[group]), {'name': str(erschema)})
-            except KeyError:
-                continue
-        for rqlexpr in sorted(erschema.get_rqlexprs(action)):
-            yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
-                   'E mainvars %%(v)s, X %s_permission E '
-                   'WHERE X is %s, X name "%s"' % (action, etype, erschema),
-                   {'e': unicode(rqlexpr.expression), 'v': unicode(rqlexpr.mainvars),
-                    't': unicode(rqlexpr.__class__.__name__)})
+        for group_or_rqlexpr in erschema.action_permissions(action):
+            if isinstance(group_or_rqlexpr, basestring):
+                # group
+                try:
+                    yield ('SET X %s_permission Y WHERE Y eid %%(g)s' % action,
+                           {'g': groupmapping[group_or_rqlexpr]})
+                except KeyError:
+                    continue
+            else:
+                # rqlexpr
+                rqlexpr = group_or_rqlexpr
+                yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
+                       'E mainvars %%(v)s, X %s_permission E WHERE ' % action,
+                       {'e': unicode(rqlexpr.expression),
+                        'v': unicode(rqlexpr.mainvars),
+                        't': unicode(rqlexpr.__class__.__name__)})
 
 
 def updateeschema2rql(eschema):
--- a/server/securityhooks.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/securityhooks.py	Thu Nov 19 12:55:47 2009 +0100
@@ -25,10 +25,10 @@
     for attr in editedattrs:
         if attr in defaults:
             continue
-        rschema = eschema.subjrels[attr]
-        if rschema.final: # non final relation are checked by other hooks
+        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' ?)
-            rschema.check_perm(session, 'add', eid)
+            rdef.check_perm(session, 'add', eid=eid)
 
 
 class CheckEntityPermissionOp(LateOperation):
@@ -43,7 +43,10 @@
 
 class CheckRelationPermissionOp(LateOperation):
     def precommit_event(self):
-        self.rschema.check_perm(self.session, self.action, self.fromeid, self.toeid)
+        rdef = self.rschema.rdef(self.session.describe(self.fromeid)[0],
+                                 self.session.describe(self.toeid)[0])
+        rdef.check_perm(self.session, self.action,
+                        fromeid=self.fromeid, toeid=self.toeid)
 
     def commit_event(self):
         pass
@@ -65,7 +68,7 @@
 def before_del_entity(session, eid):
     if not session.is_super_session:
         eschema = session.repo.schema[session.describe(eid)[0]]
-        eschema.check_perm(session, 'delete', eid)
+        eschema.check_perm(session, 'delete', eid=eid)
 
 
 def before_add_relation(session, fromeid, rtype, toeid):
@@ -74,26 +77,33 @@
         if (fromeid, rtype, toeid) in nocheck:
             return
         rschema = session.repo.schema[rtype]
-        rschema.check_perm(session, 'add', fromeid, toeid)
+        rdef = rschema.rdef(session.describe(fromeid)[0],
+                            session.describe(toeid)[0])
+        rdef.check_perm(session, 'add', fromeid=fromeid, toeid=toeid)
 
 def after_add_relation(session, fromeid, rtype, toeid):
     if not rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
         nocheck = session.transaction_data.get('skip-security', ())
         if (fromeid, rtype, toeid) in nocheck:
             return
-        rschema = session.repo.schema[rtype]
+        rschema = session.repo.schema.rschema(rtype)
         if rtype in ON_COMMIT_ADD_RELATIONS:
             CheckRelationPermissionOp(session, action='add', rschema=rschema,
                                       fromeid=fromeid, toeid=toeid)
         else:
-            rschema.check_perm(session, 'add', fromeid, toeid)
+            rdef = rschema.rdef(session.describe(fromeid)[0],
+                                session.describe(toeid)[0])
+            rdef.check_perm(session, 'add', fromeid=fromeid, toeid=toeid)
 
 def before_del_relation(session, fromeid, rtype, toeid):
     if not session.is_super_session:
         nocheck = session.transaction_data.get('skip-security', ())
         if (fromeid, rtype, toeid) in nocheck:
             return
-        session.repo.schema[rtype].check_perm(session, 'delete', fromeid, toeid)
+        rschema = session.vreg.schema.rschema(rtype)
+        rdef = rschema.rdef(session.describe(fromeid)[0],
+                            session.describe(toeid)[0])
+        rdef.check_perm(session, 'delete', fromeid=fromeid, toeid=toeid)
 
 def register_security_hooks(hm):
     """register meta-data related hooks on the hooks manager"""
--- a/server/test/data/migratedapp/schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/test/data/migratedapp/schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -13,7 +13,7 @@
                              ERQLExpression, RRQLExpression)
 
 class Affaire(EntityType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
         'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
@@ -27,7 +27,7 @@
     concerne = SubjectRelation('Societe')
 
 class concerne(RelationType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', RRQLExpression('U has_update_permission S')),
         'delete': ('managers', RRQLExpression('O owned_by U')),
@@ -42,7 +42,7 @@
 class Note(Para):
     __specializes_schema__ = True
 
-    permissions = {'read':   ('managers', 'users', 'guests',),
+    __permissions__ = {'read':   ('managers', 'users', 'guests',),
                    'update': ('managers', 'owners',),
                    'delete': ('managers', ),
                    'add':    ('managers',
@@ -63,7 +63,7 @@
     summary = String(maxsize=512)
 
 class ecrit_par(RelationType):
-    permissions = {'read':   ('managers', 'users', 'guests',),
+    __permissions__ = {'read':   ('managers', 'users', 'guests',),
                    'delete': ('managers', ),
                    'add':    ('managers',
                               RRQLExpression('O require_permission P, P name "add_note", '
@@ -105,7 +105,7 @@
     connait = SubjectRelation('Personne', symetric=True)
 
 class Societe(WorkflowableEntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', 'users', 'guests'),
         'update': ('managers', 'owners'),
         'delete': ('managers', 'owners'),
--- a/server/test/data/schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/test/data/schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -13,7 +13,7 @@
                              ERQLExpression, RRQLExpression)
 
 class Affaire(WorkflowableEntityType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers',
                    ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
         'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -39,7 +39,7 @@
 
 
 class Societe(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', 'users', 'guests'),
         'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
         'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
@@ -121,28 +121,28 @@
     symetric = True
 
 class concerne(RelationType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', RRQLExpression('U has_update_permission S')),
         'delete': ('managers', RRQLExpression('O owned_by U')),
         }
 
 class travaille(RelationType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', RRQLExpression('U has_update_permission S')),
         'delete': ('managers', RRQLExpression('O owned_by U')),
         }
 
 class para(RelationType):
-    permissions = {
+    __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"')),
         }
 
 class test(RelationType):
-    permissions = {'read': ('managers', 'users', 'guests'),
+    __permissions__ = {'read': ('managers', 'users', 'guests'),
                    'delete': ('managers',),
                    'add': ('managers',)}
 
--- a/server/test/unittest_security.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/server/test/unittest_security.py	Thu Nov 19 12:55:47 2009 +0100
@@ -14,13 +14,13 @@
     def setUp(self):
         RepositoryBasedTC.setUp(self)
         self.create_user('iaminusersgrouponly')
-        self.readoriggroups = self.schema['Personne'].get_groups('read')
-        self.addoriggroups = self.schema['Personne'].get_groups('add')
+        self.readoriggroups = self.schema['Personne'].permissions['read']
+        self.addoriggroups = self.schema['Personne'].permissions['add']
 
     def tearDown(self):
         RepositoryBasedTC.tearDown(self)
-        self.schema['Personne'].set_groups('read', self.readoriggroups)
-        self.schema['Personne'].set_groups('add', self.addoriggroups)
+        self.schema['Personne'].set_action_permissions('read', self.readoriggroups)
+        self.schema['Personne'].set_action_permissions('add', self.addoriggroups)
 
 
 class LowLevelSecurityFunctionTC(BaseSecurityTC):
@@ -29,7 +29,7 @@
         rql = u'Personne U where U nom "managers"'
         rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
         origgroups = self.schema['Personne'].get_groups('read')
-        self.schema['Personne'].set_groups('read', ('users', 'managers'))
+        self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
         self.repo.vreg.rqlhelper.compute_solutions(rqlst)
         solution = rqlst.solutions[0]
         check_read_access(self.schema, self.session.user, rqlst, solution)
@@ -96,8 +96,8 @@
     def test_update_security_2(self):
         cnx = self.login('anon')
         cu = cnx.cursor()
-        self.repo.schema['Personne'].set_groups('read', ('users', 'managers'))
-        self.repo.schema['Personne'].set_groups('add', ('guests', 'users', 'managers'))
+        self.repo.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
+        self.repo.schema['Personne'].set_action_permissions('add', ('guests', 'users', 'managers'))
         self.assertRaises(Unauthorized, cu.execute, "SET X nom 'bidulechouette' WHERE X is Personne")
         #self.assertRaises(Unauthorized, cnx.commit)
         # test nothing has actually been inserted
@@ -177,7 +177,7 @@
         ent = rset.get_entity(0, 0)
         session.set_pool() # necessary
         self.assertRaises(Unauthorized,
-                          ent.e_schema.check_perm, session, 'update', ent.eid)
+                          ent.e_schema.check_perm, session, 'update', eid=ent.eid)
         self.assertRaises(Unauthorized,
                           cu.execute, "SET P travaille S WHERE P is Personne, S is Societe")
         # test nothing has actually been inserted:
@@ -230,7 +230,7 @@
     # read security test
 
     def test_read_base(self):
-        self.schema['Personne'].set_groups('read', ('users', 'managers'))
+        self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
         cnx = self.login('anon')
         cu = cnx.cursor()
         self.assertRaises(Unauthorized,
@@ -281,7 +281,7 @@
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Societe X: X nom 'bidule'")
         self.commit()
-        self.schema['Personne'].set_groups('read', ('managers',))
+        self.schema['Personne'].set_action_permissions('read', ('managers',))
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
         rset = cu.execute('Any N WHERE N has_text "bidule"')
@@ -293,7 +293,7 @@
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Societe X: X nom 'bidule'")
         self.commit()
-        self.schema['Personne'].set_groups('read', ('managers',))
+        self.schema['Personne'].set_action_permissions('read', ('managers',))
         cnx = self.login('anon')
         cu = cnx.cursor()
         rset = cu.execute('Any N,U WHERE N has_text "bidule", N owned_by U?')
@@ -371,8 +371,8 @@
 
     def test_attribute_read_security(self):
         # anon not allowed to see users'login, but they can see users
-        self.repo.schema['CWUser'].set_groups('read', ('guests', 'users', 'managers'))
-        self.repo.schema['login'].set_groups('read', ('users', 'managers'))
+        self.repo.schema['CWUser'].set_action_permissions('read', ('guests', 'users', 'managers'))
+        self.repo.schema['CWUser'].rdef('login').set_action_permissions('read', ('users', 'managers'))
         cnx = self.login('anon')
         cu = cnx.cursor()
         rset = cu.execute('CWUser X')
@@ -494,11 +494,11 @@
         # needed to avoid check_perm error
         session.set_pool()
         # needed to remove rql expr granting update perm to the user
-        self.schema['Affaire'].set_rqlexprs('update', ())
+        self.schema['Affaire'].set_action_permissions('update', self.schema['Affaire'].get_groups('update'))
         self.assertRaises(Unauthorized,
-                          self.schema['Affaire'].check_perm, session, 'update', eid)
+                          self.schema['Affaire'].check_perm, session, 'update', eid=eid)
         cu = cnx.cursor()
-        self.schema['Affaire'].set_groups('read', ('users',))
+        self.schema['Affaire'].set_action_permissions('read', ('users',))
         try:
             aff = cu.execute('Any X WHERE X ref "ARCT01"').get_entity(0, 0)
             aff.fire_transition('abort')
@@ -510,7 +510,7 @@
             # from the current state but Unauthorized if it exists but user can't pass it
             self.assertRaises(ValidationError, user.fire_transition, 'deactivate')
         finally:
-            self.schema['Affaire'].set_groups('read', ('managers',))
+            self.schema['Affaire'].set_action_permissions('read', ('managers',))
 
     def test_trinfo_security(self):
         aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
--- a/sobjects/notification.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/sobjects/notification.py	Thu Nov 19 12:55:47 2009 +0100
@@ -269,14 +269,16 @@
         changes = self.req.transaction_data['changes'][self.rset[0][0]]
         _ = self.req._
         formatted_changes = []
+        entity = self.entity(self.row or 0, self.col or 0)
         for attr, oldvalue, newvalue in sorted(changes):
             # check current user has permission to see the attribute
             rschema = self.vreg.schema[attr]
             if rschema.final:
-                if not rschema.has_perm(self.req, 'read', eid=self.rset[0][0]):
+                rdef = entity.e_schema.rdef(rschema)
+                if not rdef.has_perm(self.req, 'read', eid=self.rset[0][0]):
                     continue
             # XXX suppose it's a subject relation...
-            elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]):
+            elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]): # XXX toeid
                 continue
             if attr in self.no_detailed_change_attrs:
                 msg = _('%s updated') % _(attr)
--- a/test/data/erqlexpr_on_ertype.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/data/erqlexpr_on_ertype.py	Thu Nov 19 12:55:47 2009 +0100
@@ -9,7 +9,7 @@
 from cubicweb.schema import ERQLExpression
 
 class ToTo(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers',),
         'add': ('managers',),
         'update': ('managers',),
@@ -18,7 +18,7 @@
     toto = SubjectRelation('TuTu')
 
 class TuTu(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers',),
         'add': ('managers',),
         'update': ('managers',),
@@ -26,7 +26,7 @@
         }
 
 class toto(RelationType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', ),
         'add': ('managers', ERQLExpression('S bla Y'),),
         'delete': ('managers',),
--- a/test/data/rewrite/schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/data/rewrite/schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -2,7 +2,7 @@
 from cubicweb.schema import ERQLExpression
 
 class Affaire(EntityType):
-    permissions = {
+    __permissions__ = {
         'read':   ('managers',
                    ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
         'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -15,7 +15,7 @@
 
 
 class Societe(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', 'users', 'guests'),
         'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
         'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
--- a/test/data/rqlexpr_on_ertype_read.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/data/rqlexpr_on_ertype_read.py	Thu Nov 19 12:55:47 2009 +0100
@@ -9,7 +9,7 @@
 from cubicweb.schema import RRQLExpression
 
 class ToTo(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers',),
         'add': ('managers',),
         'update': ('managers',),
@@ -18,7 +18,7 @@
     toto = SubjectRelation('TuTu')
 
 class TuTu(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers',),
         'add': ('managers',),
         'update': ('managers',),
@@ -26,7 +26,7 @@
         }
 
 class toto(RelationType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', RRQLExpression('S bla Y'), ),
         'add': ('managers',),
         'delete': ('managers',),
--- a/test/data/rrqlexpr_on_attr.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/data/rrqlexpr_on_attr.py	Thu Nov 19 12:55:47 2009 +0100
@@ -9,7 +9,7 @@
 from cubicweb.schema import RRQLExpression
 
 class ToTo(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers',),
         'add': ('managers',),
         'update': ('managers',),
@@ -18,7 +18,7 @@
     attr = String()
 
 class attr(RelationType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', ),
         'add': ('managers', RRQLExpression('S bla Y'),),
         'delete': ('managers',),
--- a/test/data/rrqlexpr_on_eetype.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/data/rrqlexpr_on_eetype.py	Thu Nov 19 12:55:47 2009 +0100
@@ -9,7 +9,7 @@
 from cubicweb.schema import RRQLExpression
 
 class ToTo(EntityType):
-    permissions = {
+    __permissions__ = {
         'read': ('managers', RRQLExpression('S bla Y'),),
         'add': ('managers',),
         'update': ('managers',),
--- a/test/unittest_entity.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/unittest_entity.py	Thu Nov 19 12:55:47 2009 +0100
@@ -63,7 +63,7 @@
     def test_copy_with_nonmeta_composite_inlined(self):
         p = self.add_entity('Personne', nom=u'toto')
         oe = self.add_entity('Note', type=u'x')
-        self.schema['ecrit_par'].set_rproperty('Note', 'Personne', 'composite', 'subject')
+        self.schema['ecrit_par'].rdef('Note', 'Personne').composite = 'subject'
         self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
                      {'t': oe.eid, 'u': p.eid}, ('t','u'))
         e = self.add_entity('Note', type=u'z')
@@ -127,10 +127,10 @@
         Note = self.vreg['etypes'].etype_class('Note')
         peschema = Personne.e_schema
         seschema = Societe.e_schema
-        peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '1*')
-        peschema.subjrels['connait'].set_rproperty(peschema, peschema, 'cardinality', '11')
-        peschema.subjrels['evaluee'].set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
-        seschema.subjrels['evaluee'].set_rproperty(seschema, Note.e_schema, 'cardinality', '1*')
+        peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '1*'
+        peschema.subjrels['connait'].rdef(peschema, peschema).cardinality = '11'
+        peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema).cardinality = '1*'
+        seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema).cardinality = '1*'
         # testing basic fetch_attrs attribute
         self.assertEquals(Personne.fetch_rql(user),
                           'Any X,AA,AB,AC ORDERBY AA ASC '
@@ -165,13 +165,13 @@
             self.assertEquals(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC '
                               'WHERE X is Personne, X nom AA, X connait AB?')
             # testing optional relation
-            peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '?*')
+            peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '?*'
             Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
             Societe.fetch_attrs = ('nom',)
             self.assertEquals(Personne.fetch_rql(user),
                               'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD')
             # testing relation with cardinality > 1
-            peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '**')
+            peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '**'
             self.assertEquals(Personne.fetch_rql(user),
                               'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
             # XXX test unauthorized attribute
--- a/test/unittest_rqlrewrite.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/unittest_rqlrewrite.py	Thu Nov 19 12:55:47 2009 +0100
@@ -18,7 +18,8 @@
 config = TestServerConfiguration('data/rewrite')
 config.bootstrap_cubes()
 schema = config.load_schema()
-schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*'))
+from yams.buildobjs import RelationDefinition
+schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*'))
 
 rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
                                                  'has_text': 'fti'})
--- a/test/unittest_schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/test/unittest_schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -19,7 +19,7 @@
 from yams.reader import PyFileReader
 
 from cubicweb.schema import CubicWebSchema, CubicWebEntitySchema, \
-     RQLConstraint, CubicWebSchemaLoader, ERQLExpression, RRQLExpression, \
+     RQLConstraint, CubicWebSchemaLoader, RQLExpression, ERQLExpression, RRQLExpression, \
      normalize_expression, order_eschemas
 from cubicweb.devtools import TestServerConfiguration as TestConfiguration
 
@@ -44,7 +44,7 @@
 schema = CubicWebSchema('Test Schema')
 enote = schema.add_entity_type(EntityType('Note'))
 eaffaire = schema.add_entity_type(EntityType('Affaire'))
-eperson = schema.add_entity_type(EntityType('Personne', permissions=PERSONNE_PERMISSIONS))
+eperson = schema.add_entity_type(EntityType('Personne', __permissions__=PERSONNE_PERMISSIONS))
 esociete = schema.add_entity_type(EntityType('Societe'))
 
 RELS = (
@@ -73,12 +73,13 @@
 for rel in RELS:
     _from, _type, _to = rel.split()
     if not _type.lower() in done:
-        if _type == 'concerne':
-            schema.add_relation_type(RelationType(_type, permissions=CONCERNE_PERMISSIONS))
-        else:
-            schema.add_relation_type(RelationType(_type))
+        schema.add_relation_type(RelationType(_type))
         done[_type.lower()] = True
-    schema.add_relation_def(RelationDefinition(_from, _type, _to))
+    if _type == 'concerne':
+        schema.add_relation_def(RelationDefinition(_from, _type, _to,
+                                                   __permissions__=CONCERNE_PERMISSIONS))
+    else:
+        schema.add_relation_def(RelationDefinition(_from, _type, _to))
 
 class CubicWebSchemaTC(TestCase):
 
@@ -94,26 +95,24 @@
         self.assertEqual(schema.rschema('concerne').type, 'concerne')
 
     def test_entity_perms(self):
-        eperson.set_default_groups()
         self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests')))
         self.assertEqual(eperson.get_groups('update'), set(('managers', 'owners',)))
         self.assertEqual(eperson.get_groups('delete'), set(('managers', 'owners')))
         self.assertEqual(eperson.get_groups('add'), set(('managers',)))
         self.assertEqual([str(e) for e in eperson.get_rqlexprs('add')],
                          ['Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s'])
-        eperson.set_groups('read', ('managers',))
+        eperson.set_action_permissions('read', ('managers',))
         self.assertEqual(eperson.get_groups('read'), set(('managers',)))
 
     def test_relation_perms(self):
-        rconcerne = schema.rschema('concerne')
-        rconcerne.set_default_groups()
+        rconcerne = schema.rschema('concerne').rdef('Personne', 'Societe')
         self.assertEqual(rconcerne.get_groups('read'), set(('managers', 'users', 'guests')))
         self.assertEqual(rconcerne.get_groups('delete'), set(('managers',)))
         self.assertEqual(rconcerne.get_groups('add'), set(('managers', )))
-        rconcerne.set_groups('read', ('managers',))
+        rconcerne.set_action_permissions('read', ('managers',))
         self.assertEqual(rconcerne.get_groups('read'), set(('managers',)))
         self.assertEqual([str(e) for e in rconcerne.get_rqlexprs('add')],
-                         ['Any S WHERE U has_update_permission S, S eid %(s)s, U eid %(u)s'])
+                         ['Any S,U WHERE U has_update_permission S, S eid %(s)s, U eid %(u)s'])
 
     def test_erqlexpression(self):
         self.assertRaises(RQLSyntaxError, ERQLExpression, '1')
@@ -124,7 +123,7 @@
         self.assertRaises(Exception, RRQLExpression, '1')
         self.assertRaises(RQLSyntaxError, RRQLExpression, 'O X Y')
         expr = RRQLExpression('U has_update_permission O')
-        self.assertEquals(str(expr), 'Any O WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
+        self.assertEquals(str(expr), 'Any O,U WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
 
 loader = CubicWebSchemaLoader()
 config = TestConfiguration('data')
@@ -215,9 +214,9 @@
         self.assertListEquals(rels, ['bookmarked_by', 'created_by', 'for_user',
                                      'identity', 'owned_by', 'wf_info_for'])
         rschema = schema.rschema('relation_type')
-        properties = rschema.rproperties('CWAttribute', 'CWRType')
-        self.assertEquals(properties['cardinality'], '1*')
-        constraints = properties['constraints']
+        properties = rschema.rdef('CWAttribute', 'CWRType')
+        self.assertEquals(properties.cardinality, '1*')
+        constraints = properties.constraints
         self.failUnlessEqual(len(constraints), 1, constraints)
         constraint = constraints[0]
         self.failUnless(isinstance(constraint, RQLConstraint))
@@ -246,13 +245,13 @@
         self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on an entity type, use an ERQLExpression (ToTo)")
 
     def test_erqlexpr_on_rtype(self):
-        self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on a relation type, use a RRQLExpression (toto)")
+        self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
 
     def test_rqlexpr_on_rtype_read(self):
-        self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of a relation type (toto)")
+        self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu")
 
     def test_rrqlexpr_on_attr(self):
-        self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on a final relation type (eg attribute relation), use an ERQLExpression (attr)")
+        self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
 
 
 class NormalizeExpressionTC(TestCase):
@@ -261,5 +260,9 @@
         self.assertEquals(normalize_expression('X  bla Y,Y blur Z  ,  Z zigoulou   X '),
                                                'X bla Y, Y blur Z, Z zigoulou X')
 
+class RQLExpressionTC(TestCase):
+    def test_comparison(self):
+        self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
+        self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
 if __name__ == '__main__':
     unittest_main()
--- a/web/uicfg.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/uicfg.py	Thu Nov 19 12:55:47 2009 +0100
@@ -73,30 +73,24 @@
 from cubicweb.web import formwidgets
 
 
-def card_from_role(card, role):
-    if role == 'subject':
-        return card[0]
-    assert role in ('object', 'sobject'), repr(role)
-    return card[1]
-
 # primary view configuration ##################################################
 
 def init_primaryview_section(rtag, sschema, rschema, oschema, role):
     if rtag.get(sschema, rschema, oschema, role) is None:
-        card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role)
-        composed = rschema.rproperty(sschema, oschema, 'composite') == neg_role(role)
+        rdef = rschema.rdef(sschema, oschema)
         if rschema.final:
             if rschema.meta or sschema.is_metadata(rschema) \
                     or oschema.type in ('Password', 'Bytes'):
                 section = 'hidden'
             else:
                 section = 'attributes'
-        elif card in '1+':
-            section = 'attributes'
-        elif composed:
-            section = 'relations'
         else:
-            section = 'sideboxes'
+            if rdef.role_cardinality(role) in '1+':
+                section = 'attributes'
+            elif rdef.composite == neg_role(role):
+                section = 'relations'
+            else:
+                section = 'sideboxes'
         rtag.tag_relation((sschema, rschema, oschema, role), section)
 
 primaryview_section = RelationTags('primaryview_section',
@@ -177,13 +171,8 @@
         elif sschema.is_metadata(rschema):
             section = 'metadata'
         else:
-            if role == 'subject':
-                card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
-            else:
-                card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
-            if card in '1+':
+            rdef = rschema.rdef(sschema, oschema)
+            if rdef.role_cardinality(role) in '1+':
                 section = 'primary'
             elif rschema.final:
                 section = 'secondary'
@@ -217,9 +206,8 @@
 # 'link' / 'create' relation tags, used to control the "add entity" submenu
 def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
     if rtag.get(sschema, rschema, oschema, role) is None:
-        card = rschema.rproperty(sschema, oschema, 'cardinality')[role == 'object']
-        if not card in '?1' and \
-               rschema.rproperty(sschema, oschema, 'composite') == role:
+        rdef = rschema.rdef(sschema, oschema)
+        if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
             rtag.tag_relation((sschema, rschema, oschema, role), True)
 
 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
--- a/web/views/actions.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/views/actions.py	Thu Nov 19 12:55:47 2009 +0100
@@ -268,20 +268,21 @@
             for rschema in rschemas:
                 if rschema.final:
                     continue
-                # check the relation can be added as well
-                # XXX consider autoform_permissions_overrides?
-                if role == 'subject'and not rschema.has_perm(req, 'add',
-                                                             fromeid=entity.eid):
-                    continue
-                if role == 'object'and not rschema.has_perm(req, 'add',
-                                                            toeid=entity.eid):
-                    continue
-                # check the target types can be added as well
                 for teschema in rschema.targets(eschema, role):
                     if not appearsin_addmenu.etype_get(eschema, rschema,
                                                        role, teschema):
                         continue
-                    if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
+                    rdef = rschema.role_rdef(eschema, teschema, role)
+                    # check the relation can be added
+                    # XXX consider autoform_permissions_overrides?
+                    if role == 'subject'and not rdef.has_perm(
+                        req, 'add', fromeid=entity.eid):
+                        continue
+                    if role == 'object'and not rdef.has_perm(
+                        req, 'add', toeid=entity.eid):
+                        continue
+                    # check the target types can be added as well
+                    if teschema.may_have_permission('add', req):
                         yield rschema, teschema, role
 
     def linkto_url(self, entity, rtype, etype, target):
--- a/web/views/autoform.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/views/autoform.py	Thu Nov 19 12:55:47 2009 +0100
@@ -75,10 +75,18 @@
             # check category first, potentially lower cost than checking
             # permission which may imply rql queries
             if categories is not None:
-                targetschemas = [tschema for tschema in targetschemas
-                                 if rtags.etype_get(eschema, rschema, role, tschema) in categories]
-                if not targetschemas:
+                _targetschemas = []
+                for tschema in targetschemas:
+                    if not rtags.etype_get(eschema, rschema, role, tschema) in categories:
+                        continue
+                    rdef = rschema.role_rdef(eschema, tschema, role)
+                    if not ((not strict and rdef.has_local_role(permission)) or
+                            rdef.has_perm(entity.req, permission, fromeid=eid)):
+                        continue
+                    _targetschemas.append(tschema)
+                if not _targetschemas:
                     continue
+                targetschemas = _targetschemas
             if permission is not None:
                 # tag allowing to hijack the permission machinery when
                 # permission is not verifiable until the entity is actually
@@ -87,28 +95,22 @@
                     yield (rschema, targetschemas, role)
                     continue
                 if rschema.final:
-                    if not rschema.has_perm(entity.req, permission, eid):
+                    if not eschema.rdef(rschema).has_perm(entity.req, permission, eid=eid):
                         continue
                 elif role == 'subject':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity.req, permission, fromeid=eid)):
-                        continue
                     # on relation with cardinality 1 or ?, we need delete perm as well
                     # if the relation is already set
                     if (permission == 'add'
-                        and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
+                        and rschema.rdef(eschema, targetschemas[0]).role_cardinality(role) in '1?'
                         and eid and entity.related(rschema.type, role)
                         and not rschema.has_perm(entity.req, 'delete', fromeid=eid,
                                                  toeid=entity.related(rschema.type, role)[0][0])):
                         continue
                 elif role == 'object':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity.req, permission, toeid=eid)):
-                        continue
                     # on relation with cardinality 1 or ?, we need delete perm as well
                     # if the relation is already set
                     if (permission == 'add'
-                        and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
+                        and rschema.rdef(targetschemas[0], eschema).role_cardinality(role) in '1?'
                         and eid and entity.related(rschema.type, role)
                         and not rschema.has_perm(entity.req, 'delete', toeid=eid,
                                                  fromeid=entity.related(rschema.type, role)[0][0])):
--- a/web/views/owl.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/views/owl.py	Thu Nov 19 12:55:47 2009 +0100
@@ -71,10 +71,8 @@
         if writeprefix:
             self.w(OWL_CLOSING_ROOT)
 
-    def should_display_rschema(self, rschema):
-        return not rschema in self.skiptypes and (
-            rschema.has_local_role('read') or
-            rschema.has_perm(self.req, 'read'))
+    def should_display_rschema(self, eschema, rschema, tschemas, role):
+        return rschema.may_have_permissions('read', self.req, eschema, role)
 
     def visit_schema(self, skiptypes):
         """get a layout for a whole schema"""
@@ -94,7 +92,7 @@
         self.w(u'<owl:Class rdf:ID="%s">'% eschema)
         self.w(u'<!-- relations -->')
         for rschema, targetschemas, role in eschema.relation_definitions():
-            if not self.should_display_rschema(rschema):
+            if not self.should_display_rschema(eschema, rschema, targetschemas, role):
                 continue
             for oeschema in targetschemas:
                 if role == 'subject':
@@ -112,7 +110,7 @@
 
         self.w(u'<!-- attributes -->')
         for rschema, aschema in eschema.attribute_definitions():
-            if not self.should_display_rschema(rschema):
+            if not self.should_display_rschema(eschema, rschema, (aschema,), 'subject'):
                 continue
             self.w(u'''<rdfs:subClassOf>
   <owl:Restriction>
@@ -125,7 +123,7 @@
     def visit_property_schema(self, eschema):
         """get a layout for property entity OWL schema"""
         for rschema, targetschemas, role in eschema.relation_definitions():
-            if not self.should_display_rschema(rschema):
+            if not self.should_display_rschema(eschema, rschema, targetschemas, role):
                 continue
             for oeschema in targetschemas:
                 self.w(u'''<owl:ObjectProperty rdf:ID="%s">
@@ -135,7 +133,7 @@
 
     def visit_property_object_schema(self, eschema):
         for rschema, aschema in eschema.attribute_definitions():
-            if not self.should_display_rschema(rschema):
+            if not self.should_display_rschema(eschema, rschema, (aschema,), 'subject'):
                 continue
             self.w(u'''<owl:DatatypeProperty rdf:ID="%s">
   <rdfs:domain rdf:resource="#%s"/>
@@ -174,7 +172,8 @@
         for rschema, aschema in eschema.attribute_definitions():
             if rschema.meta:
                 continue
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+            rdef = rschema.rdef(eschema, aschema)
+            if not rdef.may_have_permission('read', self.req):
                 continue
             aname = rschema.type
             if aname == 'eid':
@@ -189,8 +188,10 @@
         for rschema, targetschemas, role in eschema.relation_definitions():
             if rschema.meta:
                 continue
-            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
-                continue
+            for tschema in targetschemas:
+                rdef = rschema.role_rdef(eschema, tschema, role)
+                if not rdef.may_have_permission('read', self.req):
+                    continue
             if role == 'object':
                 attr = 'reverse_%s' % rschema.type
             else:
--- a/web/views/schema.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/views/schema.py	Thu Nov 19 12:55:47 2009 +0100
@@ -357,13 +357,11 @@
 
     def should_display_schema(self, rschema):
         return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
-                and (rschema.has_local_role('read')
-                     or rschema.has_perm(self.req, 'read')))
+                and rschema.may_have_permission('read', self.req))
 
-    def should_display_attr(self, rschema):
+    def should_display_attr(self, eschema, rschema):
         return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(rschema)
-                and (rschema.has_local_role('read')
-                     or rschema.has_perm(self.req, 'read')))
+                and eschema.rdef(rschema).may_have_permission('read'))
 
 
 class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
--- a/web/views/startup.py	Wed Nov 18 09:16:38 2009 +0100
+++ b/web/views/startup.py	Thu Nov 19 12:55:47 2009 +0100
@@ -26,6 +26,8 @@
     @classmethod
     def vreg_initialization_completed(cls):
         for eschema in cls.schema.entities():
+            if eschema.final:
+                continue
             if eschema.schema_entity():
                 uicfg.indexview_etype_section.setdefault(eschema, 'schema')
             elif eschema.is_subobject(strict=True):
@@ -140,8 +142,7 @@
         """
         req = self.req
         for eschema in eschemas:
-            if eschema.final or (not eschema.has_perm(req, 'read') and
-                                      not eschema.has_local_role('read')):
+            if eschema.final or not eschema.may_have_permission('read', req):
                 continue
             etype = eschema.type
             label = display_name(req, etype, 'plural')