backout 7780:a1d5365fefc1 stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 13 Sep 2011 15:40:06 +0200
branchstable
changeset 7782 40a49f4350a5
parent 7781 e95cfd5eca61
child 7783 8b70a0fb840a
backout 7780:a1d5365fefc1
__pkginfo__.py
devtools/testlib.py
doc/book/en/devrepo/datamodel/baseschema.rst
doc/book/en/devrepo/datamodel/definition.rst
entities/authobjs.py
entities/schemaobjs.py
misc/migration/bootstrapmigration_repository.py
misc/migration/postcreate.py
schema.py
schemas/__init__.py
schemas/base.py
schemas/workflow.py
server/migractions.py
server/repository.py
server/session.py
server/test/data/bootstrap_cubes
test/data/bootstrap_cubes
web/views/actions.py
web/views/autoform.py
web/views/management.py
web/views/primary.py
web/views/schema.py
--- a/__pkginfo__.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/__pkginfo__.py	Tue Sep 13 15:40:06 2011 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 14, 0)
+numversion = (3, 13, 5)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
--- a/devtools/testlib.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/devtools/testlib.py	Tue Sep 13 15:40:06 2011 +0200
@@ -387,6 +387,31 @@
                 req.cnx.commit()
         return user
 
+    @iclassmethod # XXX turn into a class method
+    def grant_permission(self, session, entity, group, pname=None, plabel=None):
+        """insert a permission on an entity. Will have to commit the main
+        connection to be considered
+        """
+        if not isinstance(session, Session):
+            warn('[3.12] grant_permission arguments are now (session, entity, group, pname[, plabel])',
+                 DeprecationWarning, stacklevel=2)
+            plabel = pname
+            pname = group
+            group = entity
+            entity = session
+            assert not isinstance(self, type)
+            session = self.session
+        pname = unicode(pname)
+        plabel = plabel and unicode(plabel) or unicode(group)
+        e = getattr(entity, 'eid', entity)
+        with security_enabled(session, False, False):
+            peid = session.execute(
+            'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,'
+            'X require_group G, E require_permission X '
+            'WHERE G name %(group)s, E eid %(e)s',
+            locals())[0][0]
+        return peid
+
     def login(self, login, **kwargs):
         """return a connection for the given login/password"""
         if login == self.admlogin:
--- a/doc/book/en/devrepo/datamodel/baseschema.rst	Tue Sep 13 14:54:00 2011 +0200
+++ b/doc/book/en/devrepo/datamodel/baseschema.rst	Tue Sep 13 15:40:06 2011 +0200
@@ -19,6 +19,7 @@
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * _`CWUser`, system users
 * _`CWGroup`, users groups
+* _`CWPermission`, used to configure the security of the instance
 
 Entity types used to manage workflows
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/doc/book/en/devrepo/datamodel/definition.rst	Tue Sep 13 14:54:00 2011 +0200
+++ b/doc/book/en/devrepo/datamodel/definition.rst	Tue Sep 13 15:40:06 2011 +0200
@@ -646,7 +646,68 @@
   RelationType declaration which offers some advantages in the context
   of reusable cubes.
 
-  
+Definition of permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+The entity type `CWPermission` from the standard library
+allows to build very complex and dynamic security architectures. The schema of
+this entity type is as follow:
+
+.. sourcecode:: python
+
+    class CWPermission(EntityType):
+        """entity type that may be used to construct some advanced security configuration
+        """
+        name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
+        require_group = SubjectRelation('CWGroup', cardinality='+*',
+                                        description=_('groups to which the permission is granted'))
+        require_state = SubjectRelation('State',
+                                        description=_("entity's state in which the permission is applicable"))
+        # can be used on any entity
+        require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
+                                            description=_("link a permission to the entity. This "
+                                                          "permission should be used in the security "
+                                                          "definition of the entity's type to be useful."))
+
+
+Example of configuration:
+
+.. sourcecode:: python
+
+    class Version(EntityType):
+        """a version is defining the content of a particular project's release"""
+
+        __permissions__ = {'read':   ('managers', 'users', 'guests',),
+                           'update': ('managers', 'logilab', 'owners',),
+                           'delete': ('managers', ),
+                           'add':    ('managers', 'logilab',
+                                       ERQLExpression('X version_of PROJ, U in_group G,'
+                                                 'PROJ require_permission P, P name "add_version",'
+                                                 'P require_group G'),)}
+
+
+    class version_of(RelationType):
+        """link a version to its project. A version is necessarily linked to one and only one project.
+        """
+        __permissions__ = {'read':   ('managers', 'users', 'guests',),
+                           'delete': ('managers', ),
+                           'add':    ('managers', 'logilab',
+                                  RRQLExpression('O require_permission P, P name "add_version",'
+                                                 'U in_group G, P require_group G'),)
+                       }
+        inlined = True
+
+
+This configuration indicates that an entity `CWPermission` named
+"add_version" can be associated to a project and provides rights to create
+new versions on this project to specific groups. It is important to notice that:
+
+* in such case, we have to protect both the entity type "Version" and the relation
+  associating a version to a project ("version_of")
+
+* because of the genericity of the entity type `CWPermission`, we have to execute
+  a unification with the groups and/or the states if necessary in the expression
+  ("U in_group G, P require_group G" in the above example)
+
 
 
 Handling schema changes
--- a/entities/authobjs.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/entities/authobjs.py	Tue Sep 13 15:40:06 2011 +0200
@@ -29,6 +29,22 @@
     fetch_attrs, fetch_order = fetch_config(['name'])
     fetch_unrelated_order = fetch_order
 
+    def grant_permission(self, entity, pname, plabel=None):
+        """grant local `pname` permission on `entity` to this group using
+        :class:`CWPermission`.
+
+        If a similar permission already exists, add the group to it, else create
+        a new one.
+        """
+        if not self._cw.execute(
+            'SET X require_group G WHERE E eid %(e)s, G eid %(g)s, '
+            'E require_permission X, X name %(name)s, X label %(label)s',
+            {'e': entity.eid, 'g': self.eid,
+             'name': pname, 'label': plabel}):
+            self._cw.create_entity('CWPermission', name=pname, label=plabel,
+                                   require_group=self,
+                                   reverse_require_permission=entity)
+
 
 class CWUser(AnyEntity):
     __regid__ = 'CWUser'
@@ -123,6 +139,18 @@
             return False
     owns = cached(owns, keyarg=1)
 
+    def has_permission(self, pname, contexteid=None):
+        rql = 'Any P WHERE P is CWPermission, U eid %(u)s, U in_group G, '\
+              'P name %(pname)s, P require_group G'
+        kwargs = {'pname': pname, 'u': self.eid}
+        if contexteid is not None:
+            rql += ', X require_permission P, X eid %(x)s'
+            kwargs['x'] = contexteid
+        try:
+            return self._cw.execute(rql, kwargs)
+        except Unauthorized:
+            return False
+
     # presentation utilities ##################################################
 
     def name(self):
--- a/entities/schemaobjs.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/entities/schemaobjs.py	Tue Sep 13 15:40:06 2011 +0200
@@ -176,3 +176,13 @@
 
     def check_expression(self, *args, **kwargs):
         return self._rqlexpr().check(*args, **kwargs)
+
+
+class CWPermission(AnyEntity):
+    __regid__ = 'CWPermission'
+    fetch_attrs, fetch_order = fetch_config(['name', 'label'])
+
+    def dc_title(self):
+        if self.label:
+            return '%s (%s)' % (self._cw._(self.name), self.label)
+        return self._cw._(self.name)
--- a/misc/migration/bootstrapmigration_repository.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Tue Sep 13 15:40:06 2011 +0200
@@ -41,15 +41,6 @@
         'FROM cw_CWSource, cw_source_relation '
         'WHERE entities.eid=cw_source_relation.eid_from AND cw_source_relation.eid_to=cw_CWSource.cw_eid')
 
-if applcubicwebversion <= (3, 14, 0) and cubicwebversion >= (3, 14, 0):
-    if 'require_permission' in schema and not 'localperms'in repo.config.cubes():
-        from cubicweb import ExecutionError
-        try:
-            add_cube('localperms', update_database=False)
-        except ImportError:
-            raise ExecutionError('In cubicweb 3.14, CWPermission and related stuff '
-                                 'has been moved to cube localperms. Install it first.')
-
 if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0):
     CSTRMAP = dict(rql('Any T, X WHERE X is CWConstraintType, X name T',
                        ask_confirm=False))
--- a/misc/migration/postcreate.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/misc/migration/postcreate.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -69,3 +69,10 @@
             if value != default:
                 rql('INSERT CWProperty X: X pkey %(k)s, X value %(v)s',
                     {'k': key, 'v': value})
+
+# add PERM_USE_TEMPLATE_FORMAT permission
+from cubicweb.schema import PERM_USE_TEMPLATE_FORMAT
+usetmplperm = create_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT,
+                            label=_('use template languages'))
+rql('SET X require_group G WHERE G name "managers", X eid %(x)s',
+    {'x': usetmplperm.eid})
--- a/schema.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/schema.py	Tue Sep 13 15:40:06 2011 +0200
@@ -59,11 +59,12 @@
                            'from_state', 'to_state', 'condition',
                            'subworkflow', 'subworkflow_state', 'subworkflow_exit',
                            ))
-SYSTEM_RTYPES = set(('in_group', 'require_group',
+SYSTEM_RTYPES = set(('in_group', 'require_group', 'require_permission',
                      # cwproperty
                      'for_user',
                      )) | WORKFLOW_RTYPES
 NO_I18NCONTEXT = META_RTYPES | WORKFLOW_RTYPES
+NO_I18NCONTEXT.add('require_permission')
 
 SKIP_COMPOSITE_RELS = [('cw_source', 'subject')]
 
@@ -84,7 +85,7 @@
                       'WorkflowTransition', 'BaseTransition',
                       'SubWorkflowExitPoint'))
 
-INTERNAL_TYPES = set(('CWProperty', 'CWCache', 'ExternalUri',
+INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri',
                       'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig'))
 
 
@@ -1173,7 +1174,7 @@
 
 # _() is just there to add messages to the catalog, don't care about actual
 # translation
-MAY_USE_TEMPLATE_FORMAT = set(('managers',))
+PERM_USE_TEMPLATE_FORMAT = _('use_template_format')
 NEED_PERM_FORMATS = [_('text/cubicweb-page-template')]
 
 @monkeypatch(FormatConstraint)
@@ -1188,9 +1189,9 @@
             # cw is a server session
             hasperm = not cw.write_security or \
                       not cw.is_hook_category_activated('integrity') or \
-                      cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT)
+                      cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
         else:
-            hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT)
+            hasperm = cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
         if hasperm:
             return self.regular_formats + tuple(NEED_PERM_FORMATS)
     return self.regular_formats
--- a/schemas/__init__.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/schemas/__init__.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,10 +15,12 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""some constants and classes to define schema permissions"""
+"""some utilities to define schema permissions
 
+"""
 __docformat__ = "restructuredtext en"
 
+from rql.utils import quote
 from cubicweb.schema import RO_REL_PERMS, RO_ATTR_PERMS, \
      PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, \
      ERQLExpression, RRQLExpression
@@ -33,19 +35,59 @@
 # execute, readable by anyone
 HOOKS_RTYPE_PERMS = RO_REL_PERMS # XXX deprecates
 
+def _perm(names):
+    if isinstance(names, (list, tuple)):
+        if len(names) == 1:
+            names = quote(names[0])
+        else:
+            names = 'IN (%s)' % (','.join(quote(name) for name in names))
+    else:
+        names = quote(names)
+    #return u' require_permission P, P name %s, U in_group G, P require_group G' % names
+    return u' require_permission P, P name %s, U has_group_permission P' % names
 
-from logilab.common.modutils import LazyObject
-from logilab.common.deprecation import deprecated
-class MyLazyObject(LazyObject):
+
+def xperm(*names):
+    return 'X' + _perm(names)
+
+def xexpr(*names):
+    return ERQLExpression(xperm(*names))
+
+def xrexpr(relation, *names):
+    return ERQLExpression('X %s Y, Y %s' % (relation, _perm(names)))
+
+def xorexpr(relation, etype, *names):
+    return ERQLExpression('Y %s X, X is %s, Y %s' % (relation, etype, _perm(names)))
+
+
+def sexpr(*names):
+    return RRQLExpression('S' + _perm(names), 'S')
 
-    def _getobj(self):
-        try:
-            return super(MyLazyObject, self)._getobj()
-        except ImportError:
-            raise ImportError('In cubicweb 3.14, function %s has been moved to '
-                              'cube localperms. Install it first.' % self.obj)
+def restricted_sexpr(restriction, *names):
+    rql = '%s, %s' % (restriction, 'S' + _perm(names))
+    return RRQLExpression(rql, 'S')
+
+def restricted_oexpr(restriction, *names):
+    rql = '%s, %s' % (restriction, 'O' + _perm(names))
+    return RRQLExpression(rql, 'O')
+
+def oexpr(*names):
+    return RRQLExpression('O' + _perm(names), 'O')
+
 
-for name in ('xperm', 'xexpr', 'xrexpr', 'xorexpr', 'sexpr', 'restricted_sexpr',
-             'restricted_oexpr', 'oexpr', 'relxperm', 'relxexpr', '_perm'):
-    msg = '[3.14] import %s from cubes.localperms' % name
-    globals()[name] = deprecated(msg)(MyLazyObject('cubes.localperms', name))
+# def supdate_perm():
+#     return RRQLExpression('U has_update_permission S', 'S')
+
+# def oupdate_perm():
+#     return RRQLExpression('U has_update_permission O', 'O')
+
+def relxperm(rel, role, *names):
+    assert role in ('subject', 'object')
+    if role == 'subject':
+        zxrel = ', X %s Z' % rel
+    else:
+        zxrel = ', Z %s X' % rel
+    return 'Z' + _perm(names) + zxrel
+
+def relxexpr(rel, role, *names):
+    return ERQLExpression(relxperm(rel, role, *names))
--- a/schemas/base.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/schemas/base.py	Tue Sep 13 15:40:06 2011 +0200
@@ -180,6 +180,31 @@
     cardinality = '?*'
 
 
+class CWPermission(EntityType):
+    """entity type that may be used to construct some advanced security configuration
+    """
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
+
+    name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
+                  description=_('name or identifier of the permission'))
+    label = String(required=True, internationalizable=True, maxsize=100,
+                   description=_('distinct label to distinguate between other '
+                                 'permission entity of the same name'))
+    require_group = SubjectRelation('CWGroup',
+                                    description=_('groups to which the permission is granted'))
+
+# explicitly add X require_permission CWPermission for each entity that should have
+# configurable security
+class require_permission(RelationType):
+    """link a permission to the entity. This permission should be used in the
+    security definition of the entity's type to be useful.
+    """
+    __permissions__ = PUB_SYSTEM_REL_PERMS
+
+class require_group(RelationType):
+    """used to grant a permission to a group"""
+    __permissions__ = PUB_SYSTEM_REL_PERMS
+
 
 class ExternalUri(EntityType):
     """a URI representing an object in external data store"""
@@ -357,5 +382,3 @@
         'add':    ('managers', RRQLExpression('U has_update_permission S'),),
         'delete': ('managers', RRQLExpression('U has_update_permission S'),),
         }
-
-
--- a/schemas/workflow.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/schemas/workflow.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -21,15 +21,14 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
-                            SubjectRelation,
+from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
                             RichString, String, Int)
 from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
-from cubicweb.schemas import (PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS,
-                              RO_REL_PERMS)
+from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS,
+                              HOOKS_RTYPE_PERMS)
 
 class Workflow(EntityType):
-    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256)
@@ -48,7 +47,7 @@
 
 class default_workflow(RelationType):
     """default workflow for an entity type"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
     subject = 'CWEType'
     object = 'Workflow'
@@ -61,7 +60,7 @@
     """used to associate simple states to an entity type and/or to define
     workflows
     """
-    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256,
@@ -84,7 +83,7 @@
 
 class BaseTransition(EntityType):
     """abstract base class for transitions"""
-    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
+    __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256,
@@ -92,34 +91,22 @@
                                                    _('workflow already have a transition of that name'))])
     type = String(vocabulary=(_('normal'), _('auto')), default='normal')
     description = RichString(description=_('semantic description of this transition'))
+    condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
+                                description=_('a RQL expression which should return some results, '
+                                              'else the transition won\'t be available. '
+                                              'This query may use X and U variables '
+                                              'that will respectivly represents '
+                                              'the current entity and the current user'))
 
+    require_group = SubjectRelation('CWGroup', cardinality='**',
+                                    description=_('group in which a user should be to be '
+                                                  'allowed to pass this transition'))
     transition_of = SubjectRelation('Workflow', cardinality='1*', composite='object',
                                     description=_('workflow to which this transition belongs'),
                                     constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N', 'Y',
                                                                      _('workflow already have a transition of that name'))])
 
 
-class require_group(RelationDefinition):
-    """group in which a user should be to be allowed to pass this transition"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
-    subject = 'BaseTransition'
-    object = 'CWGroup'
-
-
-class condition(RelationDefinition):
-    """a RQL expression which should return some results, else the transition
-    won't be available.
-
-    This query may use X and U variables that will respectivly represents the
-    current entity and the current user.
-    """
-    __permissions__ = PUB_SYSTEM_REL_PERMS
-    subject = 'BaseTransition'
-    object = 'RQLExpression'
-    cardinality = '*?'
-    composite = 'subject'
-
-
 class Transition(BaseTransition):
     """use to define a transition from one or multiple states to a destination
     states in workflow's definitions. Transition without destination state will
@@ -190,11 +177,11 @@
     # get actor and date time using owned_by and creation_date
 
 class from_state(RelationType):
-    __permissions__ = RO_REL_PERMS.copy()
+    __permissions__ = HOOKS_RTYPE_PERMS.copy()
     inlined = True
 
 class to_state(RelationType):
-    __permissions__ = RO_REL_PERMS.copy()
+    __permissions__ = HOOKS_RTYPE_PERMS.copy()
     inlined = True
 
 class by_transition(RelationType):
@@ -209,52 +196,60 @@
 
 class workflow_of(RelationType):
     """link a workflow to one or more entity type"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class state_of(RelationType):
     """link a state to one or more workflow"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class transition_of(RelationType):
     """link a transition to one or more workflow"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class destination_state(RelationType):
     """destination state of a transition"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class allowed_transition(RelationType):
     """allowed transitions from this state"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class initial_state(RelationType):
     """indicate which state should be used by default when an entity using
     states is created
     """
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 
 class subworkflow(RelationType):
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 class exit_point(RelationType):
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
 class subworkflow_state(RelationType):
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
     inlined = True
 
 
+class condition(RelationType):
+    __permissions__ = META_RTYPE_PERMS
+
+# already defined in base.py
+# class require_group(RelationType):
+#     __permissions__ = META_RTYPE_PERMS
+
+
 # "abstract" relations, set by WorkflowableEntityType ##########################
 
 class custom_workflow(RelationType):
     """allow to set a specific workflow for an entity"""
-    __permissions__ = PUB_SYSTEM_REL_PERMS
+    __permissions__ = META_RTYPE_PERMS
 
     cardinality = '?*'
     constraints = [RQLConstraint('S is ET, O workflow_of ET',
@@ -280,7 +275,7 @@
 
 class in_state(RelationType):
     """indicate the current state of an entity"""
-    __permissions__ = RO_REL_PERMS
+    __permissions__ = HOOKS_RTYPE_PERMS
 
     # not inlined intentionnaly since when using ldap sources, user'state
     # has to be stored outside the CWUser table
--- a/server/migractions.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/server/migractions.py	Tue Sep 13 15:40:06 2011 +0200
@@ -117,15 +117,7 @@
             # which is called on regular start
             repo.hm.call_hooks('server_maintenance', repo=repo)
         if not schema and not getattr(config, 'quick_start', False):
-            insert_lperms = self.repo.get_versions()['cubicweb'] < (3, 14, 0) and 'localperms' in config.available_cubes()
-            if insert_lperms:
-                cubes = config._cubes
-                config._cubes += ('localperms',)
-            try:
-                schema = config.load_schema(expand_cubes=True)
-            finally:
-                if insert_lperms:
-                    config._cubes = cubes
+            schema = config.load_schema(expand_cubes=True)
         self.fs_schema = schema
         self._synchronized = set()
 
--- a/server/repository.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/server/repository.py	Tue Sep 13 15:40:06 2011 +0200
@@ -60,7 +60,8 @@
      security_enabled
 from cubicweb.server.ssplanner import EditedEntity
 
-NO_CACHE_RELATIONS = set( [('owned_by', 'object'),
+NO_CACHE_RELATIONS = set( [('require_permission', 'object'),
+                           ('owned_by', 'object'),
                            ('created_by', 'object'),
                            ('cw_source', 'object'),
                            ])
--- a/server/session.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/server/session.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1319,6 +1319,9 @@
     def owns(self, eid):
         return True
 
+    def has_permission(self, pname, contexteid=None):
+        return True
+
     def property_value(self, key):
         if key == 'ui.language':
             return 'en'
--- a/server/test/data/bootstrap_cubes	Tue Sep 13 14:54:00 2011 +0200
+++ b/server/test/data/bootstrap_cubes	Tue Sep 13 15:40:06 2011 +0200
@@ -1,1 +1,1 @@
-card,comment,folder,tag,basket,email,file,localperms
+card,comment,folder,tag,basket,email,file
--- a/test/data/bootstrap_cubes	Tue Sep 13 14:54:00 2011 +0200
+++ b/test/data/bootstrap_cubes	Tue Sep 13 15:40:06 2011 +0200
@@ -1,1 +1,1 @@
-card, file, tag, localperms
+card, file, tag
--- a/web/views/actions.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/web/views/actions.py	Tue Sep 13 15:40:06 2011 +0200
@@ -182,6 +182,15 @@
     category = 'moreactions'
     order = 15
 
+    @classmethod
+    def __registered__(cls, reg):
+        if 'require_permission' in reg.schema:
+            cls.__select__ = (one_line_rset() & non_final_entity() &
+                              (match_user_groups('managers')
+                               | relation_possible('require_permission', 'subject', 'CWPermission',
+                                                   action='add')))
+        return super(ManagePermissionsAction, cls).__registered__(reg)
+
     def url(self):
         return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).absolute_url(vid='security')
 
@@ -427,6 +436,7 @@
 ## default actions ui configuration ###########################################
 
 addmenu = uicfg.actionbox_appearsin_addmenu
+addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
 addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
 addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
 addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
--- a/web/views/autoform.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/web/views/autoform.py	Tue Sep 13 15:40:06 2011 +0200
@@ -920,6 +920,7 @@
               'owned_by', 'created_by', 'cw_source'):
     _AFS.tag_subject_of(('*', rtype, '*'), 'main', 'metadata')
 
+_AFS.tag_subject_of(('*', 'require_permission', '*'), 'main', 'hidden')
 _AFS.tag_subject_of(('*', 'by_transition', '*'), 'main', 'attributes')
 _AFS.tag_subject_of(('*', 'by_transition', '*'), 'muledit', 'attributes')
 _AFS.tag_object_of(('*', 'by_transition', '*'), 'main', 'hidden')
@@ -928,6 +929,8 @@
 _AFS.tag_subject_of(('*', 'wf_info_for', '*'), 'main', 'attributes')
 _AFS.tag_subject_of(('*', 'wf_info_for', '*'), 'muledit', 'attributes')
 _AFS.tag_object_of(('*', 'wf_info_for', '*'), 'main', 'hidden')
+_AFS.tag_subject_of(('CWPermission', 'require_group', '*'), 'main', 'attributes')
+_AFS.tag_subject_of(('CWPermission', 'require_group', '*'), 'muledit', 'attributes')
 _AFS.tag_attribute(('CWEType', 'final'), 'main', 'hidden')
 _AFS.tag_attribute(('CWRType', 'final'), 'main', 'hidden')
 _AFS.tag_attribute(('CWUser', 'firstname'), 'main', 'attributes')
--- a/web/views/management.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/web/views/management.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -45,9 +45,10 @@
         self.w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
         super(SecurityManagementView, self).call()
 
-    def entity_call(self, entity):
+    def cell_call(self, row, col):
         self._cw.add_js('cubicweb.edition.js')
         self._cw.add_css('cubicweb.acl.css')
+        entity = self.cw_rset.get_entity(row, col)
         w = self.w
         _ = self._cw._
         w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
@@ -64,6 +65,13 @@
             self.owned_by_edit_form(entity)
         else:
             self.owned_by_information(entity)
+        # cwpermissions
+        if 'require_permission' in entity.e_schema.subject_relations():
+            w('<h3>%s</h3>' % _('permissions for this entity'))
+            reqpermschema = self._cw.vreg.schema.rschema('require_permission')
+            self.require_permission_information(entity, reqpermschema)
+            if reqpermschema.has_perm(self._cw, 'add', fromeid=entity.eid):
+                self.require_permission_edit_form(entity)
 
     def owned_by_edit_form(self, entity):
         self.w('<h3>%s</h3>' % self._cw._('ownership'))
@@ -89,6 +97,65 @@
         # else we don't know if this is because entity has no owner or becayse
         # user as no access to owner users entities
 
+    def require_permission_information(self, entity, reqpermschema):
+        if entity.require_permission:
+            w = self.w
+            _ = self._cw._
+            if reqpermschema.has_perm(self._cw, 'delete', fromeid=entity.eid):
+                delurl = self._cw.build_url('edit', __redirectvid='security',
+                                            __redirectpath=entity.rest_path())
+                delurl = delurl.replace('%', '%%')
+                # don't give __delete value to build_url else it will be urlquoted
+                # and this will replace %s by %25s
+                delurl += '&__delete=%s:require_permission:%%s' % entity.eid
+                dellinktempl = u'[<a href="%s" title="%s">-</a>]&#160;' % (
+                    xml_escape(delurl), _('delete this permission'))
+            else:
+                dellinktempl = None
+            w(u'<table class="schemaInfo">')
+            w(u'<tr><th>%s</th><th>%s</th></tr>' % (_("permission"),
+                                                    _('granted to groups')))
+            for cwperm in entity.require_permission:
+                w(u'<tr>')
+                if dellinktempl:
+                    w(u'<td>%s%s</td>' % (dellinktempl % cwperm.eid,
+                                          cwperm.view('oneline')))
+                else:
+                    w(u'<td>%s</td>' % cwperm.view('oneline'))
+                w(u'<td>%s</td>' % self._cw.view('csv', cwperm.related('require_group'), 'null'))
+                w(u'</tr>\n')
+            w(u'</table>')
+        else:
+            self.w(self._cw._('no associated permissions'))
+
+    def require_permission_edit_form(self, entity):
+        newperm = self._cw.vreg['etypes'].etype_class('CWPermission')(self._cw)
+        newperm.eid = self._cw.varmaker.next()
+        self.w(u'<p>%s</p>' % self._cw._('add a new permission'))
+        form = self._cw.vreg['forms'].select('base', self._cw, entity=newperm,
+                                         form_buttons=[wdgs.SubmitButton()],
+                                         domid='reqperm%s' % entity.eid,
+                                         __redirectvid='security',
+                                         __redirectpath=entity.rest_path())
+        form.add_hidden('require_permission', entity.eid, role='object',
+                        eidparam=True)
+        permnames = getattr(entity, '__permissions__', None)
+        cwpermschema = newperm.e_schema
+        if permnames is not None:
+            field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('name'),
+                                widget=wdgs.Select({'size': 1}),
+                                choices=permnames)
+        else:
+            field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('name'))
+        form.append_field(field)
+        field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('label'))
+        form.append_field(field)
+        field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('require_group'))
+        form.append_field(field)
+        renderer = self._cw.vreg['formrenderers'].select(
+            'htable', self._cw, rset=None, display_progress_div=False)
+        form.render(w=self.w, renderer=renderer)
+
 
 class ErrorView(AnyRsetView):
     """default view when no result has been found"""
--- a/web/views/primary.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/web/views/primary.py	Tue Sep 13 15:40:06 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -396,3 +396,5 @@
 for rtype in META_RTYPES:
     _pvs.tag_subject_of(('*', rtype, '*'), 'hidden')
     _pvs.tag_object_of(('*', rtype, '*'), 'hidden')
+_pvs.tag_subject_of(('*', 'require_permission', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'require_permission', '*'), 'hidden')
--- a/web/views/schema.py	Tue Sep 13 14:54:00 2011 +0200
+++ b/web/views/schema.py	Tue Sep 13 15:40:06 2011 +0200
@@ -676,6 +676,14 @@
     def parent_entity(self):
         return self.entity.expression_of
 
+class CWPermissionIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
+    __select__ = is_instance('CWPermission')
+    def parent_entity(self):
+        # XXX useless with permission propagation
+        permissionof = getattr(self.entity, 'reverse_require_permission', ())
+        if len(permissionof) == 1:
+            return permissionof[0]
+
 
 # misc: facets, actions ########################################################