folders, files, images and comments should have one of the following visibility:

public, everyone can see it

authenticated, only authenticated users can see it

restricted, only a subset of authenticated users can see it

managers (e.g. me) can see everything

only authenticated users can see people

everyone can see classifier entities, such as tag and zone

Also, unless explicitly specified, the visibility of an image should be the same as
its parent folder, as well as visibility of a comment should be the same as the
commented entity. If there is no parent entity, the default visibility is
authenticated.

In schema, you can grant access according to groups, or to some RQL expressions:
users get access if the expression returns some results. To implement the read
security defined earlier, groups are not enough, we’ll need some RQL expression. Here
is the idea:

add a visibility attribute on Folder, File and Comment, which may be one of
the value explained above

add a may_be_read_by relation from Folder, File and Comment to users,
which will define who can see the entity

security propagation will be done in hook.

So the first thing to do is to modify my cube’s schema.py to define those
relations:

we’ve added a new visibility attribute to folder, file, image and comment
using a RelationDefinition

cardinality = ‘11’ means this attribute is required. This is usually hidden
under the required argument given to the String constructor, but we can
rely on this here (same thing for StaticVocabularyConstraint, which is usually
hidden by the vocabulary argument)

the parent possible value will be used for visibility propagation

think to secure the may_be_read_by permissions, else any user can add/delete it
by default, which somewhat breaks our security model…

Now, we should be able to define security rules in the schema, based on these new
attribute and relation. Here is the code to add to schema.py:

VISIBILITY_PERMISSIONS provides read access to managers group, if
visibility attribute’s value is ‘public’, or if user (designed by the ‘U’
variable in the expression) is linked to the entity (the ‘X’ variable) through
the may_be_read_by permission

we modify permissions of the entity types we use by importing them and
modifying their __permissions__ attribute

notice the .copy(): we only want to modify ‘add’ permission for Comment,
not for all entity types using VISIBILITY_PERMISSIONS!

the remaining part of the security model is done using regular groups:

This kind of active rule will be done using CubicWeb’s hook
system. Hooks are triggered on database events such as addition of a new
entity or relation.

The tricky part of the requirement is in unless explicitly specified, notably
because when the entity is added, we don’t know yet its ‘parent’
entity (e.g. Folder of an File, File commented by a Comment). To handle such things,
CubicWeb provides Operation, which allow to schedule things to do at commit time.

In our case we will:

on entity creation, schedule an operation that will set default visibility

when a “parent” relation is added, propagate parent’s visibility unless the
child already has a visibility set

hooks are application objects, hence have selectors that should match entity or
relation types to which the hook applies. To match a relation type, we use the
hook specific match_rtype selector.

usage of DataOperationMixIn: instead of adding an operation for each added entity,
DataOperationMixIn allows to create a single one and to store entity’s eids to be
processed in the transaction data. This is a good pratice to avoid heavy
operations manipulation cost when creating a lot of entities in the same
transaction.

the precommit_event method of the operation will be called at transaction’s
commit time.

in a hook, self._cw is the repository session, not a web request as usually
in views

according to hook’s event, you have access to different attributes on the hook
instance. Here:

self.entity is the newly added entity on ‘after_add_entity’ events

self.eidfrom / self.eidto are the eid of the subject / object entity on
‘after_add_relation’ events (you may also get the relation type using
self.rtype)

The parent visibility value is used to tell “propagate using parent security”
because we want that attribute to be required, so we can’t use None value else
we’ll get an error before we get any chance to propagate…

Now, we also want to propagate the may_be_read_by relation. Fortunately,
CubicWeb provides some base hook classes for such things, so we only have to add
the following code to hooks.py:

# relations where the "parent" entity is the subjectS_RELS=set()# relations where the "parent" entity is the objectO_RELS=set(('filed_under','comments',))classAddEntitySecurityPropagationHook(hook.PropagateRelationHook):"""propagate permissions when new entity are added"""__regid__='sytweb.addentity_security_propagation'__select__=(hook.PropagateRelationHook.__select__&hook.match_rtype_sets(S_RELS,O_RELS))main_rtype='may_be_read_by'subject_relations=S_RELSobject_relations=O_RELSclassAddPermissionSecurityPropagationHook(hook.PropagateRelationAddHook):"""propagate permissions when new entity are added"""__regid__='sytweb.addperm_security_propagation'__select__=(hook.PropagateRelationAddHook.__select__&hook.match_rtype('may_be_read_by',))subject_relations=S_RELSobject_relations=O_RELSclassDelPermissionSecurityPropagationHook(hook.PropagateRelationDelHook):__regid__='sytweb.delperm_security_propagation'__select__=(hook.PropagateRelationDelHook.__select__&hook.match_rtype('may_be_read_by',))subject_relations=S_RELSobject_relations=O_RELS

the AddEntitySecurityPropagationHook will propagate the relation
when filed_under or comments relations are added

the S_RELS and O_RELS set as well as the match_rtype_sets selector are
used here so that if my cube is used by another one, it’ll be able to
configure security propagation by simply adding relation to one of the two
sets.

the two others will propagate permissions changes on parent entities to
children entities

Then I update the version number in the cube’s __pkginfo__.py to 0.2.0. And
that’s it! Those instructions will:

update the instance’s schema by adding our two new relations and update the
underlying database tables accordingly (the first two instructions)

update schema’s permissions definition (the last instruction)

To migrate my instance I simply type:

cubicweb-ctlupgradesytweb_instance

You’ll then be asked some questions to do the migration step by step. You should say
YES when it asks if a backup of your database should be done, so you can get back
to initial state if anything goes wrong…