Access Control / Authorization
Overview
JCR 2.0 specifies Access Control Management in section 16. The JCR API package is javax.jcr.security. It covers the authorization part, ie. what a certain user is allowed to do with the repository, but not UserManagement, which is provided by Jackrabbit as an implementation-specific feature.
Permissions / Privileges
These permissions, called "privileges", are defined by the JCR 2.0 specification. This is basically the set of read, create, modify and delete operations that can be done on nodes and properties via the JCR API. A repository can also define custom privileges.
Basic privileges:
jcr:read
The privilege to retrieve a node and get its properties and their values.jcr:modifyProperties
The privilege to create, remove and modify the values of the properties of a node.jcr:addChildNodes
The privilege to create child nodes of a node.jcr:removeNode
The privilege to remove a node.jcr:removeChildNodes
The privilege to remove child nodes of a node.jcr:write
An aggregate privilege that contains:jcr:read
,jcr:modifyProperties
,jcr:addChildNodes
,jcr:removeNode
,jcr:removeChildNodes
jcr:all
An aggregate privilege that contains all available permissions, includingjcr:read
,jcr:write
and the advanced permssions.
For advanced privileges, see section 16.2.3 of the JCR 2.0 specification.
Resource-based ACLs
The JCR 2.0 specification is modeled with resource-based access control lists in mind. That means that a resource = node is associated with a list of allow/deny entries for certain principals (users or groups), which naturally maps to store them along the JCR node. A core concept of resource-based ACLs is that they inherit the ACLs from the parent node, thus for each node, all the ACLs of its ancestor come into play as well.
Resource-based ACLs are the default access control mechanism in Jackrabbit 2.x.
Advantages:
- fully supported by the JCR API / specification
- very widely available ACL model (eg. file systems)
- simple resource inheritance
- default mechanism in Jackrabbit, no configuration needed
Disadvantages:
- cannot assign ACLs to non-existent nodes
- cumbersome when many users need un-groupable ACLs on a few resources (e.g. "subscriptions"), lots of ACL entries per resource
- permissions are stored right inside the content (can be cumbersome for backups, etc.)
How Resource-based ACLs are stored
Resource-based ACLs are stored per resource/node in a special child node rep:policy
. This one will have a list of rep:GrantACE
child nodes (usually named allow
, allow0
,...) for grant access control entries and rep:DenyACE
child nodes (usually named deny
, deny0
,...) for deny access control entries.
Each ACE node has a rep:principalName
STRING property pointing to the user or group this ACE belongs to, and a rep:privileges
NAME multi-value property, containing all the privileges of this ACE.
Note that you can read/browse these nodes using the JCR API, but cannot modify them. This must always happen through the JCR access control API.
API for setting resource-based ACLs
Note that there is a utility AccessControlUtils in jackrabbit-jcr-commons that simplifies the JCR API calls for ACLs a bit.
This is an example granting all rights to everyone, using the plain JCR API:
AccessControlManager aMgr = session.getAccessControlManager(); // create a privilege set with jcr:all Privilege[] privileges = new Privilege[] { aMgr.privilegeFromName(Privilege.JCR_ALL) }; AccessControlList acl; try { // get first applicable policy (for nodes w/o a policy) acl = aMgr.getApplicablePolicies(path).nextAccessControlPolicy(); } catch (NoSuchElementException e) { // else node already has a policy, get that one acl = aMgr.getPolicies(path)[0]; } // remove all existing entries for (AccessControlEntry e : acl.getAccessControlEntries()) { acl.removeAccessControlEntry(e); } // add a new one for the special "everyone" principal acl.addAccessControlEntry(EveryonePrincipal.getInstance(), privileges); // the policy must be re-set aMgr.setPolicy(path, acl); // and the session must be saved for the changes to be applied session.save();
(for links and more code, see below)
Difference between getPolicies() and getApplicablePolicies()
AccessControlManager.getApplicablePolicies(path)
returns an iterator over all applicable policies that you can potentially define for that node. This depends on what the implementation provides. Jackrabbit only supports one policy, the AccessControlList
. If this is already applied to that node (in case the node is not new) you get an empty iterator.
A more correct way (than the code sample above) would check if any of the policies returned by the iterator is an AccessControlList
and use that one. If you cannot find one for a new node, then that policy is not supported by that specific JCR implementation.
The AccessControlManager.getPolicies(path)
only returns the policies that are already applied (i.e. persisted and active) on that node. In case of a new node, this should be empty.
Principal-based ACLs
A different approach for specifying and storing ACLs is to assign certain principals (users or groups) a list of nodes that they are allowed or denied to work on. The nodes will be referenced by paths, and might even include wildcards.
To work with principal-based ACLs, a Jackrabbit-propietary extension to the ACL management API is required.
Advantages:
- permissions can be assigned to non-existent nodes
- permissions are stored separately from the content (good for content replication, backup etc.)
- good for having many users with un-groupable ACLs (e.g. "subscriptions"); resources don't get filled up with ACL entries
Disadvantages:
- additional Jackrabbit API has to be used for setting ACLs
- modeling resource inheritance requires more ACLs than resource-based
How Principal-based ACLs are stored
An access control list (rep:ACL
) is stored for each user and group (this is transparent, currently it's mirroring the users's home path at /rep:accesscontrol/<principal-path>/rep:policy/
). This consists of entries (rep:ACE
), which are either allow (rep:GrantACE
) or deny (rep:DenyACE
) entries.
The rep:ACE
nodetype (used by both resource- and principal-based ACLs) defines the following two properties for principal-based usage. These exact same names need to be used as restrictions when using the Jackrabbit API (JackrabbitAccessControlList.addEntry()):
rep:nodePath
defines the (base) path of a subtree the ACL applies to (mandatory, PATH property)rep:glob
defines a glob pattern to restrict the subtree, both child nodes & properties (only a path matching is done) (optional, STRING property)
For the glob pattern, see http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/core/security/authorization/GlobPattern.html (NodePath is the rep:nodePath
and restriction is the rep:glob
).
API for setting principal-based ACLs
This uses the Jackrabbit (security) API extending the base JCR (security) API. Please note that this is example code and might need to be adapted
// usual entry point into the Jackrabbit API JackrabbitSession js = (JackrabbitSession) session; // get user/principal for whom to read/set ACLs // Note: the ACL security API works using Java Principals as high-level abstraction and does not // assume the users are actually stored in the JCR with the Jackrabbit UserManagement; an example // are external users provided by a custom LoginModule via LDAP PrincipalManager pMgr = js.getPrincipalManager(); Principal principal = pMgr.getPrincipal(session.getUserID()); // alternatively: get the current user as Authorizable from the user management // (there is a one-to-one mapping between Authorizables and Principals) User user = ((User) js.getUserManager().getAuthorizable(session.getUserID())); Principal principal = user.getPrincipal(); // get the Jackrabbit access control manager JackrabbitAccessControlManager acMgr = (JackrabbitAccessControlManager) session.getAccessControlManager(); JackrabbitAccessControlPolicy[] ps = acMgr.getPolicies(principal); // or getApplicablePolicies() JackrabbitAccessControlList list = (JackrabbitAccessControlList) ps[0]; // list entries JackrabbitAccessControlEntry[] entries = (JackrabbitAccessControlEntry[]) list.getAccessControlEntries(); JackrabbitAccessControlEntry entry = entries[0]; // remove entry list.removeAccessControlEntry(entry); // add entry Privilege[] privileges = new Privileges[] { acMgr.privilegeFromName(Privilege.JCR_READ) }; Map<String, Value> restrictions = new HashMap<String, Value>(); ValueFactory vf = session.getValueFactory(); restrictions.put("rep:nodePath", vf.createValue("/some/path", PropertyType.PATH)); restrictions.put("rep:glob", vf.createValue("*")); list.addEntry(principal, privileges, true /* allow or deny */, restrictions); // reorder entries list.orderBefore(entry, entry2); // finally set policy again & save acMgr.setPolicy(list.getPath(), list); session.save();
Classes used are:
- JackrabbitSession
- AccessControlManager
- JackrabbitAccessControlManager
- AccessControlPolicy
- JackrabbitAccessControlPolicy
- AccessControlList
- JackrabbitAccessControlList
- AccessControlEntry
- JackrabbitAccessControlEntry
- Privilege
- PrincipalManager
- UserManager
- Authorizable
Repository Configuration
To use a different ACL mechanism, a different AccessControlProvider must be configured. This is a per-workspace config, and must be set in the workspace.xml
of each workspace, or the <Workspace/>
element of the repository.xml
for to-be-created workspaces.
<WorkspaceSecurity> <AccessControlProvider class="org.apache.jackrabbit.core.security.authorization.combined.CombinedProvider" /> </WorkspaceSecurity>
The following are built-in jackrabbit-core:
- Resource-based (default):
org.apache.jackrabbit.core.security.authorization.acl.ACLProvider
- Principal-based:
org.apache.jackrabbit.core.security.authorization.principalbased.ACLProvider
- Combined, resource+principal-based:
org.apache.jackrabbit.core.security.authorization.combined.CombinedProvider
TODO
- AccessControlProvider as an interface to extend for custom acl
- general security config