In order to evaluate the permissions for a given item, the 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:
/content allow - everyone - READ permission
Result:
/content allow - everyone - READ permission deny - everyone - READ_PROPERTY permission - restriction rep:itemNames = ['prop1', 'prop2']
Result:
/content deny - everyone - READ permission /content/public allow - everyone - READ permission
Result:
/content allow - everyone - READ permission /content/public allow - everyone - REMOVE permission
Result:
/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:
/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:
The following section describes what happens on Session.getNode("/foo").getProperty("jcr:title")
in terms of permission evaluation:
SessionImpl.getNode()
internally calls SessionDelegate.getNode()
which calls Root.getTree()
which calls Tree.getTree()
on the /foo
tree. This creates a bunch of linked MutableTree
objects.
The session delegate then checks if the tree really exists, by calling Tree.exists()
which then calls NodeBuilder.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
. The SecureNodeBuilder
performs permission checks before delegating the calls to the delegated builder.
For non admin sessions the SecureNodeBuilder
fetches its tree permissions via getTreePermission()
.
The TreePermission
is responsible for evaluating the permissions granted or denied for a given Oak Tree
and it's properties. In order to test if a the tree itself is accessible TreePermission#canRead()
is called and checks the READ_NODE
permission for normal trees (as in this example) or the READ_ACCESS_CONTROL
permission on AC trees. The result is remembered in the ReadStatus
kept with this TreePermission
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 the READ_NODE
permission, then it's ambiguous to determine if all properties can be read.
Once the ReadStatus
is calculated (or was calculated earlier) the canRead()
method returns ReadStatus.allowsThis()
which specifies if this node is allowed to be read.
Node.getProperty()
internally calls NodeDelegate.getPropertyOrNull()
which first resolves the parent node as indicated by the relative path without testing for it's existence. Then a new PropertyDelegate
is created from the parent node and the name of the property, which internal obtains the PropertyState
from the Oak Tree
, which may return null
.
The node delegate then checks if the property really exists (or is accessible to the reading session by calling PropertyDelegate.exists()
asserting if the underlying PropertyState
is not null
.
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
. The SecureNodeBuilder
performs permission checks before delegating the calls to the delegated builder.
For non admin sessions the SecureNodeBuilder
fetches its tree permissions via getTreePermission()
.
The TreePermission
is responsible for evaluating the permissions granted or denied for a given Oak Tree
and it's properties. In order to test if the property is accessible TreePermission#canRead(PropertyState)
is called and checks the READ_PROPERTY
permission for regular properties or the READ_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 the ReadStatus
kept with this TreePermission
instance; otherwise the permission entries are collected and evaluated as described above.
Node.addNode(String)
will internally call NodeDelegate.addChild
which in term, adds a new child to the corresponding Oak Tree
and generate all autocreated child items.
Once Session.save()
is called all pending changes will be merged into the NodeStore
present with the editing Oak Root
. This is achieved by calling Root#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 single CommitHook
.
The PermissionValidator
will be notified about the new node being added.
It again obtains the TreePermission
object form the PermissionProvider
and evaluates if ADD_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 an CommitFailedException
of type ACCESS
.
Property.setValue
will internally call PropertyDelegate.setState
with an new PropertyState
created from the new value (or the new set of values).
Once Session.save()
is called all pending changes will be merged into the NodeStore
present with the editing Oak Root
. This is achieved by calling Root#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 single CommitHook
.
The PermissionValidator
will be notified about the modified property.
It again obtains the TreePermission
object form the PermissionProvider
and evaluates if MODIFY_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 an CommitFailedException
of type ACCESS
.
Workspac.copy
will internally call WorkspaceDelegate.copy
.
After some preliminary validation the delegate will create a new WorkspaceCopy
and call it's perform
method passing in the separate Root
instance obtained from ContentSession.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 the TreePermission
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 an CommitFailedException
of type ACCESS
.
LockManager.lock
will internally call NodeDelegate.lock
, which will obtain a new Root
from the editing ContentSession
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 for LOCK_MANAGEMENT
permissions being granted at the affected tree. The evaluation triggered by calling TreePermission.isGranted
and follows the same principals as described above.
If a permission violation is detected the commit
will fail immediately with an CommitFailedException
of type ACCESS
.
PrivilegeManager.registerPrivilege
will obtain a new Root
from the editing ContentSession
and pass it to a new PrivilegeDefinitionWriter
that is in charge of writing the repository content associated with a new privilege definition. Finally the writer will persist the changes by calling Root.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 that PRIVILEGE_MANAGEMENT
permissions being granted at the repository level. This is achieved by obtaining the RepositoryPermission
object from the PermissionProvider
and calling RepositoryPermission.isGranted
. The evaluation follows the same principals as described above.
If a permission violation is detected the commit
will fail immediately with an CommitFailedException
of type ACCESS
.
Once the registration is successfully completed the manager will refresh the editing session.