[Cubicweb] Notes about the localperms cube

Aurélien Campéas aurelien.campeas at logilab.fr
Thu Dec 19 17:52:20 CET 2013


Dear cubicwebians,

I've been thinking about complex security recently, and here are some
notes on the localperms cube.



Some notes about the localperms cube
====================================

The `localperms` cube is a common/widely used "generic"
security-mechanism-providing cube. E.g. the `tracker` and `vcsfile`
cubes directly use it to specify their security policies.

Here we try to explain how it is commonly used (feel free to correct
my views on the topic if you know better) and some common pitfalls.

Some comparison with security containers is made (should be expanded,
but this is a start).

Let's start to review what's provided.

Model
-----

Entities
........

* `CWPermission`

  has a "name", used in security rql expressions

  lives at the edge of granted_permission and require_permission
  relations (see below).

  This provides the semantics of a "role", e.g. "customer", "staff",
  "auditor", "modeller", ... you should get it.


Relations
.........

* <ET> `granted_permission` CWPermission

  The explicit "grant point" from an entity type to a "role", to be
  set by admins.

* <ET> `require_permission` CWPermission

  The "trickle down" propagation relation. Relation paths made of
  `require_permission` ought to lead to one granted_permission entity.

* `require_group`

  Relate the CWPermission entity with actuel CWUsers through a CWGroup

* `has_group_permission`

  computed relation that shortcuts `U in_group G, P require_group G`
  for maintainability and ... performance reasons

  This is also propagated along the require_permission paths.


Hooks
.....

* granted_permission -> require_permission derivation
* require_group -> has_group_permission derivation
* in_group -> has_group_permission derivation


Let's see how this has to be used (as far as I know it).


Usage pattern
-------------

Initial schema
..............

Let's start from a small schema.py::

 class Project(EntityType):
      name = String()

 class Ticket(EntityType):
      title = String()
      description = FatString()
      subjproject = Relation('Project', cardinalit='*1',
composite='subject')

 class concerns(RelationDefinition):
     subject = 'Ticket'
     object = 'Project'
     cardinality = '1*'
     composite = 'object'

Let's assume we want to add a security policy using the localperms
infrastructure.

Bring localperms
................

We need to add two relation declarations::

 class granted_permission(RelationDefinition):
     subject = 'Project'
     object = 'CWPermission'

 class require_permission(RelationDefinition):
     subject = ('Project', 'Ticket')
     object = 'CWPermission'


Hooks
.....

We need to add the propagation to the O_RELS or S_RELS localperms.hook
module globals::

 from cubes.localperms import O_RELS, S_RELS

 O_RELS.add('concerns')
 S_RELS.add('subproject')


Having this in place, we can now write the actual security rules.

Adding security rules to the schema
...................................

We don't repeat the previous declarations here::

 Project.__permissions__ = {
     'read':   ('managers', 'users', 'guests',),
     'add':    ('managers', 'staff',),
     'update': ('managers', 'staff', 'owners',),
     'delete': ('managers', 'owners'),
 }

 Ticket.__permissions__ = {
     'read':   ('managers', 'users', 'guests',),
     # right to add Ticket is actually granted on the concerns relation
     'add':    ('managers', 'users',),
     # client can only modify tickets when they are in the "open" state
     'update': ('managers', 'staff',
                ERQLExpression('X require_permission P, P name
"developper", '
                               'U has_group_permission P'),
                ERQLExpression('X require_permission P, P name "client", '
                               'U has_group_permission P, X in_state S,
S name "created"')),
     'delete': ('managers', ),
 }

 concerns.__permissions__ = {
     'read':   ('managers', 'users', 'guests'),
     'add':    ('managers', 'staff',
                RRQLExpression('O require_permission P, P name IN
("developer", "client"),'
                               'U has_group_permission P')),
     'delete': ('managers', 'staff',),
 }


I just omitted subproject because I'm tired right now ...
But these sort of rules have to be put in many places.
And that terminates the code side of things.

Deployment
..........

Now in the web UI, an admin will be able to:

* create two new user groups (for the project developpers and for the
  project clients)

* add the relevant users into the groups

* go to the project, using the "manage permissions" UI, add two
  CWPermission entities, named "developper" and "client", and linked
  to the relevant just created groups.


Critic
------

The good part
.............

* this organization is extremely flexible and allows to create complex
  and arbitrary security rules for clusters of related entity types


The bad parts
.............

* the downside of flexibility is complicatedness, which makes it
  difficult to properly use

  It is specially difficult to debug any error while constructing a
  security policy with it.

  The global O_RELS/S_RELS sets contain all the propagation paths of
  all clusters. The names are confusing and do not help guessing which
  one to use (having to think about roles is just error-prone in this
  context).

* The CWPermission provides a completely free-form "name" field, but
  the names are typically used hard-coded in the schema security rules
  (see the above rql expressions).

  This somehow will confuse the admin which, unless she _reads_ the
  schema.py, can't know in advance which permissions names to use.

* There is probably one costly join performed for each security check:

    X require_permission P, P name "developper", U has_group_permission P

  There's no reason (that I can see at least) not to have e.g.:

    U can_update X



Possible enhancements
.....................

There should be a closed set of permission names, e.g. carried in a
vocabulary, to help the developpers and the poor admins.

There should be automatically derived `U can_read X`, `U can_update
X`, etc. relations for the security implementor to use in the rql
expressions. The flexibility of localperms allows that. It may be
considered too write-costly (massive data imports would obviously
suffer) however.

The propagation path definition API should be more structured and
helpful. It is basically about defining a subgraph with special nodes
(those with the granted_permission relation). Defining subgraphs for
whatever reason (e.g. notification as with the nosylist cube) is in
itself an important activity that should (maybe) be made clear & easy
to handle, and certainly we can do better than populate a couple of
global sets with relation type names.

There should also be abundant documentation on how to go TDD with
this.


Comparison with container + security
------------------------------------

The `container` cube also helps defining entity types "clusters" and
provide security scopes over them.

It is interesting to note that the `container` cube defines a subgraph
within the schema, that can be used as scope for security, but also
i/o, cloning, etc.

Hence, the grant_permission/require_permission/.... of localperms is
made redundant.

I'd contend that some of the common use cases of application of
localperms are the same cases where a container may be defined and
used (this is indeed the case with the tracker cube, where the
O/S_RELS propagation paths are the composite relations that would be
used by container to maintain its <root> relation).

Actually where I had used initially localperms, but developped the
concept of a generalised container, I found myself suddenly dropping
localperms and replacing it with a simpler (MUCH more automatic)
security architecture.

This is food for thought.

Obviously, the differences between container and localperms are:

* container is not primarily about security, but makes great security
  scopes, and provides many other features, whereas localperms is just
  about security,

* the structure of a container is made of composite relations, whereas
  the propagation paths of localperms can be any arbitrary relations
  (whether this is really the case in practice remains to be seen)

* doing security with container leads to security rules of the
  following kind:

  ERQLExpression('X root R, U can_read R') # etype

  and the security of the relations will be handled _automatically_
  once a specific set of declarations have been made. Switching back
  to manual mode is also supported for the relations that need it.

  No need to say that in practice, it turned out less hard to debug &
  test security containers than localperms-based security models, but
  this is only my experience.


Conclusion
----------

The localperms is powerful but a bit too generic and suffer from some
glaring flaws. Once these are fixed, we might want to examine some
possibilities of convergence between the patterns of both localperms &
security containers.



More information about the Cubicweb mailing list