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/foo
tree. This creates a bunch of linkedMutableTree
objects. -
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
. TheSecureNodeBuilder
performs permission checks before delegating the calls to the delegated builder. -
For non admin sessions the
SecureNodeBuilder
fetches its tree permissions viagetTreePermission()
. -
The
TreePermission
is responsible for evaluating the permissions granted or denied for a given OakTree
and it's properties. In order to test if a the tree itself is accessibleTreePermission#canRead()
is called and checks theREAD_NODE
permission for normal trees (as in this example) or theREAD_ACCESS_CONTROL
permission on AC trees. The result is remembered in theReadStatus
kept with thisTreePermission
instance. -
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_NODE
permission, then it's ambiguous to determine if all properties can be read. -
Once the
ReadStatus
is 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 newPropertyDelegate
is created from the parent node and the name of the property, which internal obtains thePropertyState
from 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 underlyingPropertyState
is 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
. TheSecureNodeBuilder
performs permission checks before delegating the calls to the delegated builder. -
For non admin sessions the
SecureNodeBuilder
fetches its tree permissions viagetTreePermission()
. -
The
TreePermission
is responsible for evaluating the permissions granted or denied for a given OakTree
and it's properties. In order to test if the property is accessibleTreePermission#canRead(PropertyState)
is called and checks theREAD_PROPERTY
permission for regular properties or theREAD_ACCESS_CONTROL
permission 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 theReadStatus
kept with thisTreePermission
instance; otherwise the permission entries are collected and evaluated as described above.
Session Write-Operations
Adding a Node
-
Node.addNode(String)
will internally callNodeDelegate.addChild
which in term, adds a new child to the corresponding OakTree
and generate all autocreated child items. -
Once
Session.save()
is called all pending changes will be merged into theNodeStore
present with the editing OakRoot
. This is achieved by callingRoot#commit
. -
The permission evaluation is triggered by means of a specific
Validator
implementation that is passed over to the merge along with the complete set of validators and editors that are combined into a singleCommitHook
. -
The
PermissionValidator
will be notified about the new node being added. -
It again obtains the
TreePermission
object form thePermissionProvider
and evaluates ifADD_NODE
permission 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
commit
will fail immediately with anCommitFailedException
of typeACCESS
.
Changing a Property
-
Property.setValue
will internally callPropertyDelegate.setState
with an newPropertyState
created from the new value (or the new set of values). -
Once
Session.save()
is called all pending changes will be merged into theNodeStore
present with the editing OakRoot
. This is achieved by callingRoot#commit
. -
The permission evaluation is triggered by means of a specific
Validator
implementation that is passed over to the merge along with the complete set of validators and editors that are combined into a singleCommitHook
. -
The
PermissionValidator
will be notified about the modified property. -
It again obtains the
TreePermission
object form thePermissionProvider
and evaluates ifMODIFY_PROPERTY
permission is being granted. The evaluation follows the same principals as described above. -
If changing this property is allowed the validation continues otherwise the
commit
will fail immediately with anCommitFailedException
of typeACCESS
.
Workspace Operations
Copying Nodes
-
Workspac.copy
will internally callWorkspaceDelegate.copy
. -
After some preliminary validation the delegate will create a new
WorkspaceCopy
and call it'sperform
method passing in the separateRoot
instance 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.commit
is 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
Validator
that handles transient operations. -
The
PermissionValidator
will be notified about the new items created by the copy and checks the corresponding permissions with theTreePermission
associated with the individual new nodes. The evaluation follows the same principals as described above. -
If a permission violation is detected the
commit
will fail immediately with anCommitFailedException
of typeACCESS
.
Locking a Node
-
LockManager.lock
will internally callNodeDelegate.lock
, which will obtain a newRoot
from the editingContentSession
and 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.commit
on 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
Validator
that handles transient operations. -
The
PermissionValidator
will 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_MANAGEMENT
permissions being granted at the affected tree. The evaluation triggered by callingTreePermission.isGranted
and follows the same principals as described above. -
If a permission violation is detected the
commit
will fail immediately with anCommitFailedException
of typeACCESS
.
Repository Operations
Registering a Privilege
-
PrivilegeManager.registerPrivilege
will obtain a newRoot
from the editingContentSession
and pass it to a newPrivilegeDefinitionWriter
that 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
Validator
that handles transient operations. -
The
PermissionValidator
will be notified about changes being made to the dedicated tree storing privilege information and will specifically verify thatPRIVILEGE_MANAGEMENT
permissions being granted at the repository level. This is achieved by obtaining theRepositoryPermission
object from thePermissionProvider
and callingRepositoryPermission.isGranted
. The evaluation follows the same principals as described above. -
If a permission violation is detected the
commit
will fail immediately with anCommitFailedException
of typeACCESS
. -
Once the registration is successfully completed the manager will refresh the editing session.