Hi,

(I use WebDAV ACL specification terminology in this message)

Need
====
At Hippo we are building a CMS and like all other CMSes we need to be able to specify 
authorization rules. Slide provides this possiblity because it implements the WebDAV 
ACL specification. Unfortunately this is not flexible enough for our requirements. We 
want to do the following things:
= assign a user/group to a role per object (e.g. John is an author for /files/news/ 
and an editor for /files/departments/sales/, Sue is an editor for /files/news/);
= use Slide to store authorization rules for external systems (e.g. who is allowed to 
invoke the 'publish' action of the workflow).

The first issue can be solve in two ways:
= grant the appropriate read, write, etc. privileges associated with a specific role. 
This is undesirable for two reasons:
- the role gets 'lost'; there is no direct indication of which role the user has, but 
this has to be deduced from the privileges granted.
- when the privileges associated with a role change, the ACLs of all objects have to 
be modified. This is difficult to do, because it is hard to deduce if a user/group was 
in a specific role before the change.
= create a uniquely-named group for each role for each object and assign users/groups 
to these groups. This solution would introduce enormous administrative overhead to 
keep the data and groups in sync, and would litter the repository with loads of 
generated groups.

The second issue can be solved by adding a custom property to an object which would 
indicate who is authorized for what.

All solutions to both issues need custom programming to maintain them which prevents 
standard WebDAV tools from using them.

Solution
========
Our solution to the problem is to introduce custom privileges to emulate privileges 
for external systems and to emulate roles. Some examples (xmlns:D="DAV:" 
xmlns:hp="http://hippo.nl/cms/privileges"; xmlns:hr="http://hippo.nl/cms/roles";):
= hp:requestReview: request a review for changes;
= hp:publish: publish the changes;
= hp:disapprove: disapprove the changes;
= hp:all: aggregation of the three privileges above;
= hr:author: this is the author role. It is an aggregation of: D:read, D:write and 
hp:requestReview;
= hr:editor: this is the editor role. It is an aggregation of: D:read, D:write, 
hp:publish and hp:disapprove.

The ACLs for /files/news/ and for /files/departments/sales/ would contain this:
/files/news/
...
grant /users/John/ privilege hr:author
grant /users/Sue/ privilege hr:editor
...

/files/departments/sales/
...
grant /users/Johan/ privilege hr:editor
...

As you can see this is a simple way to specify what role a subject plays for an 
object. It makes the system a lot easier to use because users don't have to deal with 
loads of 'obscure', low-level privileges. When the privileges of a role change, the 
changes are reflected immediatly throughout the repository.

These custom privileges are treated like the normal (D:read, D:write, etc.) privileges 
so all DACL properties and DACL reports will work as expected. A PROPFIND for 
D:current-user-privilege-set for user John on /files/news/ would return:
hr:author
hp:requestReview
D:read
D:write
... (all aggregated privileges of D:read and D:write)

How
===
To implement the above solution I had to come up with a way to define custom 
privileges. The implementation had to be backwards-compatible with existing 
repositories. Privileges (including the standard ones) are defined in the following 
way: all leaf nodes (at any depth) under /actions/ are considered to be a privilege. 
The last segment of their URI determines their name. If a privilege has a 
D:privilege-namespace property than this will be used as its namespace, otherwise the 
DAV: namespace is assumed. For example:
/actions/read/ (no D:privilege-namespace property) -> D:read
/actions/hippo-privileges/publish/ (D:privilege-namespace = 
http://hippo.nl/cms/privileges) -> hp:publish
/actions/hippo-roles/editor/ (D:privilege-namespace = http://hippo.nl/cms/roles) -> 
hr:editor

The namespace prefixes for custom privileges are there for brevity. There is currently 
no way to specify the namespace prefix to use for a namespace, because there is no 
single point of definition of a namespace. Results from DACL queries will therefore 
contain 'xmlns:' attributes in elements representing custom privileges.

Changes
=======
I modified SecurityImpl to be aware of the custom privileges. The privileges cache was 
only loaded during construction of the object, but I added a detection of changes to 
the privileges which invalidates the cache. Because the cache is not needed very often 
this won't lead to a lot of reloading of the cache when multiple changes to the 
privileges are taking place.

A namespace attribute has been added to ActionNode which is set to the 
D:privilege-namespace property of the privilege. This namespace is not needed and it 
is expensive to determine in certain situations, so it is optional. However when an 
attempt is made to retrieve the namespace and it is not present an exception will be 
thrown (hasn't happened to me yet).

The PropPatchMethod converts absolute URIs in href elements to relative ones (the 
Servlet context path is removed). This is a reversal of the conversion in 
PropertyRetrieverImpl.

Throughout the code comparison of ObjectNode and its subtypes, and subject and 
privilege URIs were performed by an identity comparison. I changed all these identity 
comparison into the use of equals. I also removed code from SubjectNode which 
retrieved the singleton of a specific URI (for both Strings and SubjectNodes).

Remarks
=======
Currently the knowledge about how actions are defined is in multiple locations. This 
will have to refactored. The locations are:
=SecurityImpl.addActionLeafsToActionAggregation(...)
= AclMethod.findAction(...)
= PropertyHelper.addGrantedActionsToPrivilegeSet(...)

Cache invalidation detection is done by the checkPermission(...) methods of 
SecurityImpl. This is obviously not the way to go, so suggestions for a better 
location are welcome.

The system was and still is very fragile. Granting/denying a privilege which does not 
exists leads to exceptions. More checks need to be built in to handle invalid input 
and inconsistent state (e.g. a privilege which has been granted/denied and has its 
defining node under /actions/ deleted).

Some DACL reports did not and still do not work according to the specification.

That's it. I hope this extension is useful for other people as well. I am eagerly 
awaiting your questions and/or comments.

Kind regards,

Johan Stuyts
Hippo

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to