Default Permission Evaluation in Detail
Order and Evaluation of Permission Entries
In order to evaluate the permissions for a given item, the default PermissionProvider
lazily builds an iterator of PermissionsEntry representing the rep:Permission
present in the permission store that take effect for the given set of principals
at the given node (or property).
Each PermissionsEntry stores the privileges granted/denied together with any
restrictions that may be defined with the original access control entry.
This iterator is a concatenation between all entries associated with user principals followed by the entries associated with group principals.
The order of precedence is as follows:
- permissions are inherited throughout the item hierarchy
- user principals always take precedence over group principals irrespective of
- their order in the access control list
- their position in the node hierarchy
- within a given type of principal (user vs. group principal) the order of executing is
- reverse order of entries as specified originally (the index of the permission entry)
- entries associated with the target tree take precedence over inherited entries
Examples
Simple Inheritance
/content
allow - everyone - READ permission
Result:
- everyone is allowed to read the complete tree defined by /content
Simple Inheritance with Restrictions
/content
allow - everyone - READ permission
deny - everyone - READ_PROPERTY permission - restriction rep:itemNames = ['prop1', 'prop2']
Result:
- everyone is can read the complete tree defined by /content except for properties named ‘prop1’ or ‘prop2’ which are explicitly denied by the restricting entry.
Inheritance with Allow and Deny
/content
deny - everyone - READ permission
/content/public
allow - everyone - READ permission
Result:
- everyone cannot read items at the tree defined by /content
- except for tree defined by /content/public which is accessible.
Inheritance with Multiple Allows
/content
allow - everyone - READ permission
/content/public
allow - everyone - REMOVE permission
Result:
- everyonce can read item at /content and the complete subtree
- in addition everyone can remove items underneath /content/public
Inheritance with Different Principals
/content
allow - everyone - READ permission
allow - authorGroup - REMOVE permission
Result:
-
a subject being member of everyone is allowed to read at /content and the complete subtree
-
a subject being member of authorGroup is only allowed to remove items at /content
-
a subject being member of both everyone and authorGroup has full read-access at /content and can also remove items.
/content allow - everyone - READ permission
/content/private deny - everyone - READ permission allow - powerfulGroup - ALL permission
Result:
- a subject being member of everyone
- is allowed to read at /content and the complete subtree
- except for /content/private
- a subject being member of powerfulGroup
- has full permission at /content/private
- a subject being member of both everyone and powerfulGroup
- has full read-access at /content
- has full permission underneath /content/private
Interaction of User and Group Principals
/home/jackrabbit
allow - jackrabbit - ALL permission
deny - everyone - ALL permission
Result:
-
a subject containing the ‘jackrabbit’ user principal has full permission at /home/jackrabbit irrespective of the presense of everyone group principal in the subject.
-
any other subject has not access at /home/jackrabbit
/home/jackrabbit allow - jackrabbit - ALL permission
/home/jackrabbit/private deny - everyone - ALL permission
Result:
- a subject containing the ‘jackrabbit’ user principal has full permission at the tree defined by /home/jackrabbit irrespective of the presense of everyone group principal in the subject.
- any other subject is explicitly denied access to /home/jackrabbit/private
Some Examples: Step by Step
Reading
Reading a Node
The following section describes what happens on Session.getNode("/foo").getProperty("jcr:title")
in terms of permission evaluation:
-
SessionImpl.getNode()internally callsSessionDelegate.getNode()which callsRoot.getTree()which callsTree.getTree()on the/footree. This creates a bunch of linkedMutableTreeobjects. -
The session delegate then checks if the tree really exists, by calling
Tree.exists()which then callsNodeBuilder.exists(). -
If the session performing the operation is an admin session, then the node builder from the persistence layer is directly used. In all other cases, the original node builder is wrapped by a
SecureNodeBuilder. TheSecureNodeBuilderperforms permission checks before delegating the calls to the delegated builder. -
For non admin sessions the
SecureNodeBuilderfetches its tree permissions viagetTreePermission(). -
The
TreePermissionis responsible for evaluating the permissions granted or denied for a given OakTreeand it's properties. In order to test if a the tree itself is accessibleTreePermission#canRead()is called and checks theREAD_NODEpermission for normal trees (as in this example) or theREAD_ACCESS_CONTROLpermission on AC trees. The result is remembered in theReadStatuskept with thisTreePermissioninstance. -
The read status is based on the evaluation of the permission entries that are effective for this tree and the set of principals associated with the permission provider. They are retrieved internally by calling
getEntryIterator(). -
The permission entries are analyzed if they include the respective permission and if so, the read status is set accordingly. Note that the sequence of the permission entries from the iterator is already in the correct order for this kind of evaluation. This is ensured by the way how they are stored in the permission store and how they are feed into the iterator (see Order and Evaluation of Permission Entries above).
The iteration also detects if the evaluated permission entries cover this node and all its properties. If this is the case, subsequent calls that evaluate the property read permissions would then not need to do the same iteration again. In order to detect this, the iteration checks if a non-matching permission entry or privilege was skipped and eventually sets the respective flag in the
ReadStatus. This flag indicates if the present permission entries are sufficient to tell if the session is allowed to read this node and all its properties. If there are more entries present than the ones needed for evaluating theREAD_NODEpermission, then it's ambiguous to determine if all properties can be read. -
Once the
ReadStatusis calculated (or was calculated earlier) thecanRead()method returnsReadStatus.allowsThis()which specifies if this node is allowed to be read.
Reading a Property
-
Node.getProperty()internally callsNodeDelegate.getPropertyOrNull()which first resolves the parent node as indicated by the relative path without testing for it's existence. Then a newPropertyDelegateis created from the parent node and the name of the property, which internal obtains thePropertyStatefrom the OakTree, which may returnnull. -
The node delegate then checks if the property really exists (or is accessible to the reading session by calling
PropertyDelegate.exists()asserting if the underlyingPropertyStateis notnull. -
If the session performing the operation is an admin session, then the property state from the persistence layer is directly used. In all other cases, the original node builder is wrapped by a
SecureNodeBuilder. TheSecureNodeBuilderperforms permission checks before delegating the calls to the delegated builder. -
For non admin sessions the
SecureNodeBuilderfetches its tree permissions viagetTreePermission(). -
The
TreePermissionis responsible for evaluating the permissions granted or denied for a given OakTreeand it's properties. In order to test if the property is accessibleTreePermission#canRead(PropertyState)is called and checks theREAD_PROPERTYpermission for regular properties or theREAD_ACCESS_CONTROLpermission for properties defining access control related content. In case all properties defined with the parent tree are accessible to the editing session the result is remembered in theReadStatuskept with thisTreePermissioninstance; otherwise the permission entries are collected and evaluated as described above.
Session Write-Operations
Adding a Node
-
Node.addNode(String)will internally callNodeDelegate.addChildwhich in term, adds a new child to the corresponding OakTreeand generate all autocreated child items. -
Once
Session.save()is called all pending changes will be merged into theNodeStorepresent with the editing OakRoot. This is achieved by callingRoot#commit. -
The permission evaluation is triggered by means of a specific
Validatorimplementation that is passed over to the merge along with the complete set of validators and editors that are combined into a singleCommitHook. -
The
PermissionValidatorwill be notified about the new node being added. -
It again obtains the
TreePermissionobject form thePermissionProviderand evaluates ifADD_NODEpermission is being granted for the new target node. The evaluation follows the same principals as described above. -
If added the new node is granted the validation continues otherwise the
commitwill fail immediately with anCommitFailedExceptionof typeACCESS.
Changing a Property
-
Property.setValuewill internally callPropertyDelegate.setStatewith an newPropertyStatecreated from the new value (or the new set of values). -
Once
Session.save()is called all pending changes will be merged into theNodeStorepresent with the editing OakRoot. This is achieved by callingRoot#commit. -
The permission evaluation is triggered by means of a specific
Validatorimplementation that is passed over to the merge along with the complete set of validators and editors that are combined into a singleCommitHook. -
The
PermissionValidatorwill be notified about the modified property. -
It again obtains the
TreePermissionobject form thePermissionProviderand evaluates ifMODIFY_PROPERTYpermission is being granted. The evaluation follows the same principals as described above. -
If changing this property is allowed the validation continues otherwise the
commitwill fail immediately with anCommitFailedExceptionof typeACCESS.
Workspace Operations
Copying Nodes
-
Workspac.copywill internally callWorkspaceDelegate.copy. -
After some preliminary validation the delegate will create a new
WorkspaceCopyand call it'sperformmethod passing in the separateRootinstance obtained fromContentSession.getLatestRoot(); in other words the modifications made by the copy operation will not show up as transient changes on the editing session. -
Upon completion of the copy operation
Root.commitis called on that latest root instance and the delegated will refresh the editing session to reflect the changes made by the copy. -
The permission evaluation is triggered upon committing the changes associated with the copy by the same
Validatorthat handles transient operations. -
The
PermissionValidatorwill be notified about the new items created by the copy and checks the corresponding permissions with theTreePermissionassociated with the individual new nodes. The evaluation follows the same principals as described above. -
If a permission violation is detected the
commitwill fail immediately with anCommitFailedExceptionof typeACCESS.
Locking a Node
-
LockManager.lockwill internally callNodeDelegate.lock, which will obtain a newRootfrom the editingContentSessionand perform the required changes on that dedicated root such that the editing session is not affected. -
Once the lock operation is complete the delegate will call
Root.commiton the latest root instance in order to persist the changes. Finally the lock manager will refresh the editing session to reflect the changes made. -
The permission evaluation is triggered upon committing the changes associated with the lock operation by the same
Validatorthat handles transient operations. -
The
PermissionValidatorwill be notified about the new items created by the lock and identify that they are associated with a lock specific operations. Consequently it will checks forLOCK_MANAGEMENTpermissions being granted at the affected tree. The evaluation triggered by callingTreePermission.isGrantedand follows the same principals as described above. -
If a permission violation is detected the
commitwill fail immediately with anCommitFailedExceptionof typeACCESS.
Repository Operations
Registering a Privilege
-
PrivilegeManager.registerPrivilegewill obtain a newRootfrom the editingContentSessionand pass it to a newPrivilegeDefinitionWriterthat is in charge of writing the repository content associated with a new privilege definition. Finally the writer will persist the changes by callingRoot.commit. -
Validation of the new privilege definition if delegated to a dedicated
PrivilegeValidator. -
The permission evaluation is triggered upon committing the changes associated by the same
Validatorthat handles transient operations. -
The
PermissionValidatorwill be notified about changes being made to the dedicated tree storing privilege information and will specifically verify thatPRIVILEGE_MANAGEMENTpermissions being granted at the repository level. This is achieved by obtaining theRepositoryPermissionobject from thePermissionProviderand callingRepositoryPermission.isGranted. The evaluation follows the same principals as described above. -
If a permission violation is detected the
commitwill fail immediately with anCommitFailedExceptionof typeACCESS. -
Once the registration is successfully completed the manager will refresh the editing session.

