You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2023/04/28 19:12:29 UTC
[jackrabbit-filevault] 01/01: Fix restoring original principal ACLs
This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch bugfix/JCRVLT-683
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git
commit b5c99c7fe7e50b70075ffa661e0889ebe02eb95e
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Apr 18 08:58:54 2023 +0200
Fix restoring original principal ACLs
---
.../vault/fs/impl/io/DocViewAdapter.java | 7 +-
.../vault/fs/impl/io/DocViewImporter.java | 23 +-
.../vault/fs/impl/io/ImportInfoImpl.java | 41 ++
.../vault/fs/impl/io/JackrabbitACLImporter.java | 592 ++++++---------------
.../vault/fs/impl/io/JcrSysViewTransformer.java | 2 +-
.../apache/jackrabbit/vault/fs/io/Importer.java | 65 ++-
.../jackrabbit/vault/fs/spi/ACLManagement.java | 3 +-
.../jackrabbit/vault/fs/spi/UserManagement.java | 13 +-
...anagement.java => JackrabbitACLManagement.java} | 63 +--
.../spi/impl/jcr20/JackrabbitServiceProvider.java | 2 +-
.../spi/impl/jcr20/JackrabbitUserManagement.java | 49 +-
.../vault/fs/spi/impl/jcr20/SimplePrincipal.java | 32 ++
.../accesscontrol/AbstractAccessControlEntry.java | 96 ++++
.../JackrabbitAccessControlEntryBuilder.java} | 19 +-
.../JackrabbitAccessControlPolicy.java | 193 +++++++
.../JackrabbitAccessControlPolicyBuilder.java} | 14 +-
.../PrincipalBasedAccessControlEntry.java | 54 ++
.../PrincipalBasedAccessControlList.java | 108 ++++
.../PrincipalSetAccessControlPolicy.java | 91 ++++
.../ResourceBasedAccessControlEntry.java | 59 ++
.../ResourceBasedAccessControlList.java | 123 +++++
.../jackrabbit/vault/fs/spi/package-info.java | 2 +-
.../jackrabbit/vault/util/DocViewProperty2.java | 44 +-
.../UncheckedValueFormatException.java} | 24 +-
.../packaging/integration/PrincipalBasedIT.java | 148 ++++--
.../spi/impl/AccessControlValidator.java | 4 +-
26 files changed, 1302 insertions(+), 569 deletions(-)
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
index e542f0a4..e50a4ddb 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
@@ -16,6 +16,7 @@
************************************************************************/
package org.apache.jackrabbit.vault.fs.impl.io;
+import java.io.IOException;
import java.util.List;
import javax.jcr.RepositoryException;
@@ -35,19 +36,19 @@ public interface DocViewAdapter {
* @param node the node
* @throws RepositoryException if a import exception occurs.
*/
- void startNode(DocViewNode2 node) throws RepositoryException;
+ void startNode(DocViewNode2 node) throws IOException, RepositoryException;
/**
* Ends node is invoked when the importer ascends from an element.
* @throws RepositoryException if a import exception occurs.
*/
- void endNode() throws RepositoryException;
+ void endNode() throws IOException, RepositoryException;
/**
* Is called by the importer if the adapter is no longer used and must finalize the import.
* @throws RepositoryException if a import exception occurs.
* @return The paths that were created.
*/
- List<String> close() throws RepositoryException;
+ List<String> close() throws IOException, RepositoryException;
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
index dfb92644..fdafce5f 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
@@ -54,7 +54,7 @@ import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
-import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
import javax.jcr.version.VersionException;
import org.apache.jackrabbit.spi.Name;
@@ -482,11 +482,9 @@ public class DocViewImporter implements DocViewParserHandler {
}
}
}
-
- // TODO: how to find authorizables in the quickest way?
- aclManagement.getPrincipalAcls(child);
if (shouldRemoveChild) {
+ stashPrincipalAcls(child);
importInfo.onDeleted(path);
child.remove();
}
@@ -510,6 +508,14 @@ public class DocViewImporter implements DocViewParserHandler {
}
}
+ protected void stashPrincipalAcls(Node node) throws RepositoryException {
+ Map<String, List<? extends AccessControlPolicy>> principalAcls = aclManagement.getPrincipalAcls(node);
+ if (!principalAcls.isEmpty()) {
+ log.debug("Stashing {} principal ACLs below to be deleted node {}", principalAcls.size(), node.getPath());
+ importInfo.onDeletedPrincipalAcls(principalAcls);
+ }
+ }
+
private boolean hasSiblingWithPrimaryTypesAndName(Node node, NodeType[] requiredPrimaryNodeTypes, String requiredName) throws RepositoryException {
NodeIterator iter = node.getParent().getNodes();
while (iter.hasNext()) {
@@ -728,9 +734,10 @@ public class DocViewImporter implements DocViewParserHandler {
* @param node the parent node
* @param docViewNode doc view node of the authorizable
* @throws RepositoryException if an error accessing the repository occurrs.
+ * @throws IOException
* @throws SAXException if an XML parsing error occurrs.
*/
- private void handleAuthorizable(Node node, DocViewNode2 docViewNode) throws RepositoryException {
+ private void handleAuthorizable(Node node, DocViewNode2 docViewNode) throws RepositoryException, IOException {
String id = userManagement.getAuthorizableId(docViewNode);
String newPath = node.getPath() + "/" + npResolver.getJCRName(docViewNode.getName());
boolean isIncluded = wspFilter.contains(newPath);
@@ -744,8 +751,10 @@ public class DocViewImporter implements DocViewParserHandler {
log.trace("Authorizable element detected. Starting sysview transformation {}", newPath);
stack = stack.push();
stack.adapter = new JcrSysViewTransformer(node, wspFilter.getImportMode(newPath));
+ stashPrincipalAcls(node);
stack.adapter.startNode(docViewNode);
importInfo.onCreated(newPath);
+ importInfo.onAuthorizableCreated(id);
return;
}
@@ -787,15 +796,18 @@ public class DocViewImporter implements DocViewParserHandler {
// just replace the entire subtree for now.
log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
stack = stack.push();
+ stashPrincipalAcls(authNode);
stack.adapter = new JcrSysViewTransformer(node, mode);
stack.adapter.startNode(docViewNode);
importInfo.onReplaced(newPath);
+ importInfo.onAuthorizableCreated(id);
break;
case UPDATE:
case UPDATE_PROPERTIES:
log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
stack = stack.push();
+ stashPrincipalAcls(authNode);
stack.adapter = new JcrSysViewTransformer(node, oldPath, mode);
// we need to tweak the ni.name so that the sysview import does not
// rename the authorizable node name
@@ -819,6 +831,7 @@ public class DocViewImporter implements DocViewParserHandler {
stack.adapter.startNode(mapped);
importInfo.onReplaced(newPath);
+ importInfo.onAuthorizableCreated(id);
break;
}
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
index e09f7979..a223e188 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
@@ -17,11 +17,13 @@
package org.apache.jackrabbit.vault.fs.impl.io;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -29,8 +31,10 @@ import java.util.TreeMap;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.security.AccessControlPolicy;
import javax.jcr.version.Version;
+import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.vault.fs.api.ImportInfo;
import org.apache.jackrabbit.vault.fs.api.MultiPathMapping;
import org.apache.jackrabbit.vault.fs.api.NodeNameList;
@@ -66,6 +70,29 @@ public class ImportInfoImpl implements ImportInfo {
private Map<String, String[]> memberships;
+ /**
+ * The principal ACLs which have been deleted during the import (might need to be restored later on in case of moved authorizable)
+ */
+ private final Map<String, List<? extends AccessControlPolicy>> deletedPrincipalAcls;
+
+ public Map<String, List<? extends AccessControlPolicy>> getDeletedPrincipalAcls() {
+ return deletedPrincipalAcls;
+ }
+
+ public List<String> getCreatedAuthorizableIds() {
+ return createdAuthorizableIds;
+ }
+
+ /**
+ * All authorizable ids being deleted during the import
+ */
+ private final List<String> deletedAuthorizableIds;
+
+ /**
+ * All authorizable ids being created during the import
+ */
+ private final List<String> createdAuthorizableIds;
+
public static ImportInfo create(ImportInfo base) {
if (base == null) {
return new ImportInfoImpl();
@@ -75,6 +102,9 @@ public class ImportInfoImpl implements ImportInfo {
}
public ImportInfoImpl() {
+ deletedPrincipalAcls = new HashMap<>();
+ createdAuthorizableIds = new ArrayList<>();
+ deletedAuthorizableIds = new ArrayList<>();
}
public ImportInfoImpl merge(ImportInfo base) {
@@ -94,6 +124,9 @@ public class ImportInfoImpl implements ImportInfo {
} else {
memberships.putAll(baseImpl.getMemberships());
}
+ deletedPrincipalAcls.putAll(baseImpl.deletedPrincipalAcls);
+ deletedAuthorizableIds.addAll(baseImpl.deletedAuthorizableIds);
+ createdAuthorizableIds.addAll(baseImpl.createdAuthorizableIds);
}
return this;
}
@@ -158,6 +191,10 @@ public class ImportInfoImpl implements ImportInfo {
numErrors++;
}
+ public void onDeletedPrincipalAcls(Map<String, List<? extends AccessControlPolicy>> principalAcls) {
+ deletedPrincipalAcls.putAll(principalAcls);
+ }
+
/**
* remembers that a package path was remapped during import. e.g. when the importer follows and existing
* authorizable for MERGE and UPDATE modes.
@@ -258,6 +295,10 @@ public class ImportInfoImpl implements ImportInfo {
return memberships == null ? Collections.<String, String[]>emptyMap() : memberships;
}
+ public void onAuthorizableCreated(String id) throws RepositoryException {
+ createdAuthorizableIds.add(id);
+ }
+
static final class InfoImpl implements Info {
private final String path;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
index f8b5db69..329c3442 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
@@ -16,48 +16,43 @@
************************************************************************/
package org.apache.jackrabbit.vault.fs.impl.io;
-import java.security.Principal;
-import java.util.ArrayList;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.stream.Collectors;
+import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
+import javax.jcr.ValueFormatException;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
-import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
-import org.apache.jackrabbit.api.security.principal.PrincipalManager;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
-import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.AbstractAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlEntryBuilder;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicy;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicyBuilder;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalBasedAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalBasedAccessControlList;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalSetAccessControlPolicy;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.ResourceBasedAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.ResourceBasedAccessControlList;
import org.apache.jackrabbit.vault.util.DocViewNode2;
import org.apache.jackrabbit.vault.util.DocViewProperty2;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+import org.apache.jackrabbit.vault.util.UncheckedValueFormatException;
import org.slf4j.Logger;
/**
@@ -74,29 +69,39 @@ public class JackrabbitACLImporter implements DocViewAdapter {
*/
private static final Logger log = DocViewImporter.log;
- private final JackrabbitSession session;
+ private final Session session;
private final AccessControlHandling aclHandling;
- private final AccessControlManager acMgr;
-
- private final PrincipalManager pMgr;
-
private final String accessControlledPath;
private final NamePathResolver resolver;
- private ImportedPolicy<? extends AccessControlPolicy> importPolicy;
-
+ /**
+ * The state representing the level of the last evaluated node (i.e. the parent)
+ *
+ */
private enum State {
INITIAL,
- ACL,
- ACE,
- RESTRICTION,
- ERROR,
- PRINCIPAL_SET_POLICY
+ RESOURCE_BASED_ACL,
+ PRINCIPAL_BASED_ACL,
+ PRINCIPAL_SET_POLICY,
+ RESOURCE_BASED_ACE,
+ PRINCIPAL_BASED_ACE,
+ RESTRICTION
}
+ /** all property names on either rep:GrantACE/rep:DenyACE or rep:Restrictions which don't represent an access control restriction */
+ private static final Set<Name> NON_RESTRICTION_PROPERTY_NAMES = new HashSet<>(Arrays.asList(
+ NameConstants.REP_PRINCIPAL_NAME,
+ NameConstants.JCR_PRIMARYTYPE,
+ NameConstants.JCR_MIXINTYPES,
+ NameConstants.REP_PRIVILEGES
+ ));
+
+ private JackrabbitAccessControlPolicyBuilder<?> policyBuilder;
+ private JackrabbitAccessControlEntryBuilder<? extends AbstractAccessControlEntry> entryBuilder;
+
private final Deque<State> states = new LinkedList<>();
public JackrabbitACLImporter(Node accessControlledNode, AccessControlHandling aclHandling)
@@ -115,433 +120,156 @@ public class JackrabbitACLImporter implements DocViewAdapter {
throw new RepositoryException("Error while reading access control content: unsupported AccessControlHandling: " + aclHandling);
}
this.accessControlledPath = path;
- this.session = (JackrabbitSession) session;
- this.acMgr = this.session.getAccessControlManager();
- this.pMgr = this.session.getPrincipalManager();
+ this.session = session;
this.aclHandling = aclHandling;
this.states.push(State.INITIAL);
this.resolver = new DefaultNamePathResolver(session);
}
- public void startNode(DocViewNode2 node) {
+ public void startNode(DocViewNode2 node) throws RepositoryException, IOException {
State state = states.peek();
- switch (state) {
- case INITIAL:
- String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("Error while reading access control content: Missing 'jcr:primaryType'"));
- if ("rep:ACL".equals(primaryType)) {
- importPolicy = new ImportedAcList();
- state = State.ACL;
- } else if ("rep:CugPolicy".equals(primaryType)) {
- importPolicy = new ImportedPrincipalSet(node);
- state = State.PRINCIPAL_SET_POLICY;
- } else if ("rep:PrincipalPolicy".equals(primaryType)) {
- importPolicy = new ImportedPrincipalAcList(node);
- state = State.ACL;
- } else {
- log.error("Error while reading access control content: Expected rep:ACL or rep:CugPolicy but was: {}", node.getPrimaryType());
- state = State.ERROR;
- }
- break;
- case ACL:
- case ACE:
- case RESTRICTION:
- state = importPolicy.append(state, node);
- break;
- case PRINCIPAL_SET_POLICY:
- state = importPolicy.append(state, node);
- break;
- case ERROR:
- // stay in error
- break;
- }
- states.push(state);
- }
-
- public void endNode() {
- State state = states.pop();
- importPolicy.endNode(state);
- }
-
- public List<String> close() throws RepositoryException {
- if (states.peek() != State.INITIAL) {
- log.error("Unexpected end state: {}", states.peek());
- }
- List<String> paths = new ArrayList<>();
- importPolicy.apply(paths, resolver);
- return paths;
- }
-
- private void addPathIfExists(List<String> paths, String path) throws RepositoryException {
- if (session.nodeExists(path)) {
- paths.add(path);
- }
- }
-
- private abstract class ImportedPolicy<T extends AccessControlPolicy> {
-
- abstract State append(State state, DocViewNode2 node);
-
- abstract void endNode(State state);
-
- abstract void apply(List<String> paths, NameResolver resolver) throws RepositoryException;
-
- Principal getPrincipal(final String principalName) {
- Principal principal = new Principal() {
- public String getName() {
- return principalName;
- }
- };
- return principal;
- }
-
- T getPolicy(Class<T> clz) throws RepositoryException {
- for (AccessControlPolicy p : acMgr.getPolicies(accessControlledPath)) {
- if (clz.isAssignableFrom(p.getClass())) {
- return clz.cast(p);
- }
- }
- return null;
- }
-
- T getPolicy(Class<T> clz, Principal principal) throws RepositoryException {
- if (acMgr instanceof JackrabbitAccessControlManager) {
- for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getPolicies(principal)) {
- if (clz.isAssignableFrom(p.getClass())) {
- return clz.cast(p);
- }
- }
- }
- return null;
- }
-
- T getApplicablePolicy(Class<T> clz) throws RepositoryException {
- AccessControlPolicyIterator iter = acMgr.getApplicablePolicies(accessControlledPath);
- while (iter.hasNext()) {
- AccessControlPolicy p = iter.nextAccessControlPolicy();
- if (clz.isAssignableFrom(p.getClass())) {
- return clz.cast(p);
- }
- }
-
- // no applicable policy
- throw new RepositoryException("no applicable AccessControlPolicy of type "+ clz + " on " +
- (accessControlledPath == null ? "'root'" : accessControlledPath));
- }
-
- T getApplicablePolicy(Class<T> clz, Principal principal) throws RepositoryException {
- if (acMgr instanceof JackrabbitAccessControlManager) {
- for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getApplicablePolicies(principal)) {
- if (clz.isAssignableFrom(p.getClass())) {
- return clz.cast(p);
+ try {
+ switch (state) {
+ case INITIAL:
+ String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("Error while reading access control content: Missing 'jcr:primaryType'"));
+ if ("rep:ACL".equals(primaryType)) {
+ policyBuilder = new ResourceBasedAccessControlList.Builder();
+ state = State.RESOURCE_BASED_ACL;
+ } else if ("rep:CugPolicy".equals(primaryType)) {
+ // just collect the rep:principalNames property
+ Collection<String> principalNames = node.getPropertyValues(NAME_REP_PRINCIPAL_NAMES);
+ policyBuilder = new PrincipalSetAccessControlPolicy.Builder(principalNames);
+ state = State.PRINCIPAL_SET_POLICY;
+ } else if ("rep:PrincipalPolicy".equals(primaryType)) {
+ String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing on principal policy node"));
+ policyBuilder = new PrincipalBasedAccessControlList.Builder(principalName);
+ state = State.PRINCIPAL_BASED_ACL;
+ } else {
+ throw new IOException("Error while reading access control content: Expected rep:ACL, rep:PrincipalPolicy or rep:CugPolicy primary type but found: " + node.getPrimaryType().toString());
}
- }
+ break;
+ case RESOURCE_BASED_ACL:
+ case PRINCIPAL_BASED_ACL:
+ case RESOURCE_BASED_ACE:
+ case PRINCIPAL_BASED_ACE:
+ case RESTRICTION:
+ state = startEntryNode(node, state);
+ break;
+ case PRINCIPAL_SET_POLICY:
+ throw new IOException("Error while reading access control content: Unexpected node: " + node.getPrimaryType().orElse("") + " for state " + state);
}
-
- // no applicable policy
- throw new AccessControlException("no applicable AccessControlPolicy of type "+ clz + " for " + principal.getName());
- }
+ } catch (UncheckedRepositoryException e) {
+ throw e.getCause();
+ }
+ states.push(state);
}
- private final class ImportedAcList extends ImportedPolicy<JackrabbitAccessControlList> {
-
- private List<ACE> aceList = new ArrayList<>();
- private ACE currentACE;
-
- private ImportedAcList() {
- }
-
- @Override
- State append(State state, DocViewNode2 childNode) {
- if (state == State.ACL) {
- try {
- currentACE = new ACE(childNode);
- aceList.add(currentACE);
- return State.ACE;
- } catch (IllegalArgumentException e) {
- log.error("Error while reading access control content: {}", e);
- return State.ERROR;
+ /**
+ * Extracts all information from rep:GrantACE/rep:DenyACE and children.
+ * This is used for both resource-based and principal based access control entries.
+ *
+ * @param node
+ * @param state
+ * @return
+ * @see <a href="https://jackrabbit.apache.org/oak/docs/security/accesscontrol/default.html#representation-in-the-repository">Oak Access Control Management : The Default Implementation</a>
+ * @see <a href="https://jackrabbit.apache.org/oak/docs/security/authorization/principalbased.html#representation-in-the-repository">Oak Principal Based Access Control Management</a>
+ * @see <a href="https://jackrabbit.apache.org/oak/docs/security/authorization/restriction.html#representation-in-the-repository">Oak Restrictions</a>
+ */
+ private State startEntryNode(DocViewNode2 node, State state) throws IOException {
+ final State newState;
+ switch(state) {
+ case RESOURCE_BASED_ACL: {
+ final boolean allow;
+ final String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on ace node"));
+ if ("rep:GrantACE".equals(primaryType)) {
+ allow = true;
+ } else if ("rep:DenyACE".equals(primaryType)) {
+ allow = false;
+ } else {
+ throw new IOException("Unexpected node ACE type inside resource based ACL: " + node.getPrimaryType());
}
- } else if (state == State.ACE) {
- currentACE.addRestrictions(childNode);
- return State.RESTRICTION;
- } else {
- log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
- return State.ERROR;
- }
- }
-
- @Override
- void endNode(State state) {
- if (state == State.ACE) {
- currentACE = null;
+ final String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing"));
+ Collection<String> privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
+ entryBuilder = new ResourceBasedAccessControlEntry.Builder(privileges, allow, principalName);
+ extractRestrictions(node).entrySet().stream().forEach( entry -> entryBuilder.addRestriction(entry.getKey(), entry.getValue()));
+ newState = State.RESOURCE_BASED_ACE;
+ break;
}
- }
-
- @Override
- void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
- // find principals of existing ACL
- JackrabbitAccessControlList acl = getPolicy(JackrabbitAccessControlList.class);
- Set<String> existingPrincipals = new HashSet<String>();
- if (acl != null) {
- for (AccessControlEntry ace: acl.getAccessControlEntries()) {
- existingPrincipals.add(ace.getPrincipal().getName());
- }
-
- // remove existing policy for 'overwrite'
- if (aclHandling == AccessControlHandling.OVERWRITE) {
- acMgr.removePolicy(accessControlledPath, acl);
- acl = null;
+ case PRINCIPAL_BASED_ACL: {
+ if (!"rep:PrincipalEntry".equals(node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
+ throw new IOException("Unexpected node ACE type inside principal based ACL: " + node.getPrimaryType());
}
- }
-
- if (acl == null) {
- acl = getApplicablePolicy(JackrabbitAccessControlList.class);
- }
-
- // clear all ACEs of the package principals for merge (VLT-94), otherwise the `acl.addEntry()` below
- // might just combine the privileges.
- if (aclHandling == AccessControlHandling.MERGE) {
- for (ACE entry : aceList) {
- for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- if (ace.getPrincipal().getName().equals(entry.principalName)) {
- acl.removeAccessControlEntry(ace);
- }
- }
+ Collection<String> privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
+ String v = node.getPropertyValue(NAME_REP_EFFECTIVE_PATH).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:effectivePath ' missing on principal entry node"));
+ final String effectivePath;
+ if (v.isEmpty()) {
+ effectivePath = null;
+ } else {
+ effectivePath = v;
}
+ entryBuilder = new PrincipalBasedAccessControlEntry.Builder(privileges, effectivePath);
+ newState = State.PRINCIPAL_BASED_ACE;
+ break;
}
-
- // apply ACEs of package
- for (ACE ace : aceList) {
- final String principalName = ace.principalName;
- if (aclHandling == AccessControlHandling.MERGE_PRESERVE && existingPrincipals.contains(principalName)) {
- // skip principal if it already has an ACL
- continue;
+ case RESOURCE_BASED_ACE:
+ case PRINCIPAL_BASED_ACE: {
+ if (!"rep:Restrictions".equals(node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
+ throw new IllegalArgumentException("Unexpected restriction type inside principal or resource based ACE: " + node.getPrimaryType());
}
- Principal principal = getPrincipal(principalName);
-
- Map<String, Value> svRestrictions = new HashMap<String, Value>();
- Map<String, Value[]> mvRestrictions = new HashMap<String, Value[]>();
- ace.convertRestrictions(acl, session.getValueFactory(), resolver, svRestrictions, mvRestrictions);
- acl.addEntry(principal, ace.getPrivileges(acMgr), ace.allow, svRestrictions, mvRestrictions);
- }
- acMgr.setPolicy(accessControlledPath, acl);
-
- if (accessControlledPath == null) {
- addPathIfExists(paths, "/rep:repoPolicy");
- } else if ("/".equals(accessControlledPath)) {
- addPathIfExists(paths, "/rep:policy");
- } else {
- addPathIfExists(paths, accessControlledPath + "/rep:policy");
+ extractRestrictions(node).entrySet().stream().forEach( entry -> entryBuilder.addRestriction(entry.getKey(), entry.getValue()));
+ newState = State.RESTRICTION;
+ break;
}
+ case RESTRICTION:
+ throw new IOException("Restriction nodes are not supposed to have any children but found " + node.toString());
+ default:
+ throw new IllegalArgumentException("This method must not be called with state " + state);
}
+ return newState;
}
- private final class ImportedPrincipalSet extends ImportedPolicy<PrincipalSetPolicy> {
-
- private final Collection<String> principalNames;
-
- private ImportedPrincipalSet(DocViewNode2 node) {
- // don't change the status as a cug policy may not have child nodes.
- // just collect the rep:principalNames property
- // any subsequent state would indicate an error
- principalNames = node.getPropertyValues(NAME_REP_PRINCIPAL_NAMES);
- }
-
- @Override
- State append(State state, DocViewNode2 childNode) {
- log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
- return State.ERROR;
- }
-
- @Override
- void endNode(State state) {
- // nothing to do
- }
-
- @Override
- void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
- PrincipalSetPolicy psPolicy = getPolicy(PrincipalSetPolicy.class);
- if (psPolicy != null) {
- Set<Principal> existingPrincipals = psPolicy.getPrincipals();
- // remove existing policy for 'overwrite'
- if (aclHandling == AccessControlHandling.OVERWRITE) {
- psPolicy.removePrincipals(existingPrincipals.toArray(new Principal[existingPrincipals.size()]));
- }
- } else {
- psPolicy = getApplicablePolicy(PrincipalSetPolicy.class);
- }
-
- // TODO: correct behavior for MERGE and MERGE_PRESERVE?
- Principal[] principals = principalNames.stream().map(name -> getPrincipal(name)).toArray(Principal[]::new);
-
- psPolicy.addPrincipals(principals);
- acMgr.setPolicy(accessControlledPath, psPolicy);
-
- if ("/".equals(accessControlledPath)) {
- addPathIfExists(paths, "/rep:cugPolicy");
- } else {
- addPathIfExists(paths, accessControlledPath + "/rep:cugPolicy");
- }
- }
+ private Map<String, Value[]> extractRestrictions(DocViewNode2 node) {
+ return node.getProperties().stream()
+ .filter(p -> (!NON_RESTRICTION_PROPERTY_NAMES.contains(p.getName())))
+ .collect(Collectors.<DocViewProperty2, String, Value[]>toMap(
+ p -> {
+ try {
+ return resolver.getJCRName(p.getName());
+ } catch (NamespaceException e) {
+ // should not happen
+ throw new IllegalStateException("Cannot retrieve qualified name for " + p.getName().toString(), e);
+ }
+ },
+ p -> {
+ try {
+ return p.getValues(session.getValueFactory()).toArray(new Value[0]);
+ } catch (ValueFormatException e) {
+ throw new UncheckedValueFormatException(e);
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ }));
}
- private final class ImportedPrincipalAcList extends ImportedPolicy<PrincipalAccessControlList> {
-
- private final Principal principal;
- private final List<PrincipalEntry> entries = new ArrayList<>();
- private PrincipalEntry currentEntry;
-
- private ImportedPrincipalAcList(DocViewNode2 node) {
- String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing on principal policy node"));
- Principal p = pMgr.getPrincipal(principalName);
- if (p == null) {
- try {
- Authorizable a = session.getUserManager().getAuthorizableByPath(accessControlledPath);
- if (a != null) {
- p = a.getPrincipal();
- }
- } catch (RepositoryException e) {
- log.debug("Error while trying to retrieve user/group from access controlled path {}, {}", accessControlledPath, e.getMessage());
- }
- if (p == null) {
- p = getPrincipal(principalName);
- }
- }
- principal = p;
- }
-
- @Override
- State append(State state, DocViewNode2 childNode) {
- if (state == State.ACL) {
- if (!"rep:PrincipalEntry".equals(childNode.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
- log.error("Unexpected node type of access control entry: {}", childNode.getPrimaryType());
- return State.ERROR;
- }
- currentEntry = new PrincipalEntry(childNode);
- entries.add(currentEntry);
- return State.ACE;
- } else if (state == State.ACE) {
- currentEntry.addRestrictions(childNode);
- return State.RESTRICTION;
- } else {
- log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
- return State.ERROR;
- }
- }
-
- @Override
- void endNode(State state) {
- if (state == State.ACE) {
- currentEntry = null;
- }
- }
-
- @Override
- void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
- if (aclHandling == AccessControlHandling.MERGE_PRESERVE) {
- log.debug("MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE.");
- return;
- }
-
- PrincipalAccessControlList acl = getPolicy(PrincipalAccessControlList.class, principal);
- if (acl != null && aclHandling == AccessControlHandling.OVERWRITE) {
- // remove existing policy for 'OVERWRITE'
- acMgr.removePolicy(acl.getPath(), acl);
- acl = null;
- }
-
- if (acl == null) {
- acl = getApplicablePolicy(PrincipalAccessControlList.class, principal);
- }
-
- // apply ACEs of package for MERGE and OVERWRITE
- for (PrincipalEntry entry : entries) {
- Map<String, Value> svRestrictions = new HashMap<>();
- Map<String, Value[]> mvRestrictions = new HashMap<String, Value[]>();
- entry.convertRestrictions(acl, session.getValueFactory(), resolver, svRestrictions, mvRestrictions);
- acl.addEntry(entry.effectivePath, entry.getPrivileges(acMgr), svRestrictions, mvRestrictions);
- }
- acMgr.setPolicy(acl.getPath(), acl);
-
- if (accessControlledPath == null) {
- addPathIfExists(paths, "/rep:repoPolicy");
- } else if ("/".equals(accessControlledPath)) {
- addPathIfExists(paths, "/rep:policy");
- } else {
- addPathIfExists(paths, accessControlledPath + "/rep:policy");
- }
- }
- }
-
- private static class AbstractEntry {
-
- private final Collection<String> privileges;
- private final Map<Name, DocViewProperty2> restrictions;
-
- private AbstractEntry(DocViewNode2 node) {
- privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
- restrictions = new HashMap<>();
- addRestrictions(node);
- }
-
- void addRestrictions(DocViewNode2 node) {
- restrictions.putAll(node.getProperties().stream().collect(Collectors.toMap(DocViewProperty2::getName, Function.identity())));
- }
-
- void convertRestrictions(JackrabbitAccessControlList acl, ValueFactory vf, NameResolver resolver, Map<String, Value> svRestrictions, Map<String, Value[]> mvRestrictions) throws RepositoryException {
- for (String restName : acl.getRestrictionNames()) {
- DocViewProperty2 restriction = restrictions.get(resolver.getQName(restName));
- if (restriction != null) {
- Value[] values = new Value[restriction.getStringValues().size()];
- int type = acl.getRestrictionType(restName);
- for (int i=0; i<values.length; i++) {
- values[i] = vf.createValue(restriction.getStringValues().get(i), type);
- }
- if (restriction.isMultiValue()) {
- mvRestrictions.put(restName, values);
- } else {
- svRestrictions.put(restName, values[0]);
- }
- }
+ public void endNode() {
+ State state = states.pop();
+ switch(state) {
+ case RESOURCE_BASED_ACE:
+ case PRINCIPAL_BASED_ACE: {
+ policyBuilder.addEntry(entryBuilder.build());
+ break;
}
- }
-
- Privilege[] getPrivileges(AccessControlManager acMgr) throws RepositoryException {
- return AccessControlUtils.privilegesFromNames(acMgr, privileges.toArray(new String[0]));
+ default:
+ // nothing happens in all other states
}
}
- private static class ACE extends AbstractEntry {
-
- private final boolean allow;
- private final String principalName;
-
- private ACE(DocViewNode2 childNode) {
- super(childNode);
- String primaryType = childNode.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on ace node"));
- if ("rep:GrantACE".equals(primaryType)) {
- allow = true;
- } else if ("rep:DenyACE".equals(primaryType)) {
- allow = false;
- } else {
- throw new IllegalArgumentException("Unexpected node ACE type: " + childNode.getPrimaryType());
- }
- principalName = childNode.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing"));
+ public List<String> close() throws RepositoryException {
+ if (states.peek() != State.INITIAL) {
+ log.error("Unexpected end state: {}", states.peek());
}
+ JackrabbitAccessControlPolicy policy = policyBuilder.build(accessControlledPath);
+ return policy.apply(session, aclHandling);
}
- private static class PrincipalEntry extends AbstractEntry {
-
- private final String effectivePath;
-
- private PrincipalEntry(DocViewNode2 node) {
- super(node);
- String v = node.getPropertyValue(NAME_REP_EFFECTIVE_PATH).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:effectivePath ' missing on principal entry node"));
- if (v.isEmpty()) {
- effectivePath = null;
- } else {
- effectivePath = v;
- }
- }
- }
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
index 22b75c86..c9d261f5 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
@@ -109,7 +109,7 @@ public class JcrSysViewTransformer implements DocViewAdapter {
this.existingPath = existingPath;
if (existingPath != null) {
// check if there is an existing node with the name
- recovery = new NodeStash(session, existingPath).excludeName("rep:cache");
+ recovery = new NodeStash(session, existingPath).excludeName("rep:cache").excludeName("rep:principalPolicy");
recovery.stash();
}
excludeNode(NAME_REP_CACHE);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
index 81a0b3d8..7bac61b1 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
@@ -39,10 +39,12 @@ import java.util.Set;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.security.AccessControlPolicy;
import javax.jcr.version.Version;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceMapping;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.spi.commons.namespace.SessionNamespaceResolver;
@@ -81,6 +83,7 @@ import org.apache.jackrabbit.vault.fs.spi.PrivilegeInstaller;
import org.apache.jackrabbit.vault.fs.spi.ProgressTracker;
import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.vault.packaging.PackageException;
import org.apache.jackrabbit.vault.packaging.impl.ActivityLog;
import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
@@ -186,12 +189,16 @@ public class Importer {
/**
* set of paths to versionable nodes that need to be checked in after import
*/
- private final Set<String> nodesToCheckin = new HashSet<String>();
+ private final Set<String> nodesToCheckin = new HashSet<>();
/**
* map of group memberships that need to be applied after import
*/
- private final Map<String, String[]> memberships = new HashMap<String, String[]>();
+ private final Map<String, String[]> memberships = new HashMap<>();
+
+ private Map<String, List<? extends AccessControlPolicy>> deletedPrincipalAcls = new HashMap<>();
+
+ private List<String> createdAuthorizableIds = new LinkedList<>();
/**
* general flag that indicates if the import had (recoverable) errors
@@ -259,7 +266,7 @@ public class Importer {
/**
* the checkpoint import info.
*/
- private ImportInfo cpImportInfo;
+ private ImportInfoImpl cpImportInfo;
/**
* retry counter for the batch auto recovery
@@ -280,6 +287,7 @@ public class Importer {
private final boolean isStrictByDefault;
private final boolean overwritePrimaryTypesOfFoldersByDefault;
+
/**
* Default constructor neither setting specific import options nor defaults.
*/
@@ -540,6 +548,7 @@ public class Importer {
if (tracker != null) {
tracker.setMode(ProgressTrackerListener.Mode.TEXT);
}
+ restorePrincipalAcls(session);
checkinNodes(session);
applyMemberships(session);
applyPatches();
@@ -565,6 +574,52 @@ public class Importer {
}
}
+ private void restorePrincipalAcls(Session session) throws RepositoryException {
+ for (String authorizableId : createdAuthorizableIds) {
+ String principalName = userManagement.getPrincipalName(session, authorizableId);
+ if (deletedPrincipalAcls.containsKey(principalName)) {
+ if (opts.isDryRun()) {
+ track("Dry run: Would potentially restore principal ACLs of " + principalName + " ...", "");
+ } else {
+ for (AccessControlPolicy policy : deletedPrincipalAcls.get(principalName)) {
+ // CUG or ACL handling relevant?
+ AccessControlHandling aclHandling;
+ if (policy instanceof PrincipalSetPolicy) {
+ aclHandling = opts.getCugHandling();
+ } else {
+ aclHandling = opts.getAccessControlHandling();
+ }
+ // convert aclHandling (as this was set for the imported ACLs, not the existing ones)
+ final AccessControlHandling aclHandlingForRestoredPolicy;
+ switch (aclHandling) {
+ case OVERWRITE:
+ aclHandlingForRestoredPolicy = AccessControlHandling.IGNORE;
+ break;
+ case IGNORE:
+ aclHandlingForRestoredPolicy = AccessControlHandling.OVERWRITE;
+ break;
+ case CLEAR:
+ aclHandlingForRestoredPolicy = AccessControlHandling.IGNORE;
+ break;
+ case MERGE:
+ aclHandlingForRestoredPolicy = AccessControlHandling.MERGE;
+ break;
+ default:
+ aclHandlingForRestoredPolicy = AccessControlHandling.MERGE;
+
+ }
+ List<String> paths = JackrabbitAccessControlPolicy.fromAccessControlPolicy(policy).apply(session, aclHandlingForRestoredPolicy);
+ for (String path: paths) {
+ track("Restored principal ACLs of " + principalName + " ...", path);
+ }
+ }
+ }
+ }
+ }
+
+
+ }
+
/**
* Returns a human-readable error message from the throwable including all its causes till the root.
* Also the throwable class names are included in the message
@@ -894,8 +949,8 @@ public class Importer {
}
private void commit(Session session, TxInfo info, LinkedList<TxInfo> skipList) throws RepositoryException, IOException {
+ ImportInfoImpl imp = null;
try {
- ImportInfo imp = null;
if (skipList.isEmpty()) {
if (info == cpTxInfo) {
// don't need to import again, just set import info
@@ -907,6 +962,8 @@ public class Importer {
nodesToCheckin.addAll(imp.getToVersion());
memberships.putAll(imp.getMemberships());
autoSave.modified(imp.numModified());
+ deletedPrincipalAcls.putAll(imp.getDeletedPrincipalAcls());
+ createdAuthorizableIds.addAll(imp.getCreatedAuthorizableIds());
}
}
} else if (log.isDebugEnabled()) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
index faa57c54..801b47f8 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
@@ -84,6 +84,5 @@ public interface ACLManagement {
* @see <a href="https://jackrabbit.apache.org/archive/wiki/JCR/AccessControl_115513330.html">Access Control</a>
* @since 3.6.10
*/
- @NotNull
- Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException;
+ @NotNull Map<String, List<? extends AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException;
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
index b9490599..99331466 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
@@ -39,13 +39,22 @@ public interface UserManagement {
/**
* Returns the path of the authorizable or {@code null} if not exists.
- * @param name the authorizable name
+ * @param id the authorizable id
* @param session the session to access the repository
* @return path of authorizable
*
* @since 2.3.26
*/
- String getAuthorizablePath(Session session, String name);
+ String getAuthorizablePath(Session session, String id);
+
+ /**
+ *
+ * @param session the session to access the repository
+ * @param id the authorizable id
+ * @return the principal name corresponding to the given authorizable id or {@code null} if the authorizable id cannot be found
+ * @since 3.6.10
+ */
+ String getPrincipalName(Session session, String id);
/**
* Returns the id of the authorizable from the specified authorizable node
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
similarity index 83%
rename from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
rename to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
index 0090cd0f..447064d6 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
@@ -21,6 +21,7 @@ import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -33,7 +34,9 @@ import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
@@ -42,21 +45,19 @@ import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
import org.jetbrains.annotations.NotNull;
/**
- * {@code JcrACLManagement}...
* This is Jackrabbit/Oak specific as it is not defined by JCR 2.0 how access control policies are persisted.
*/
-public class JcrACLManagement implements ACLManagement {
+public class JackrabbitACLManagement implements ACLManagement {
private String groupsRootPath;
private String usersRootPath;
private final UserManagement userManagement;
- public JcrACLManagement() {
- // figure out the root paths of users and groups
- // how to get the securityProvider in Oak (https://issues.apache.org/jira/browse/OAK-9416) was never implemented
+ public JackrabbitACLManagement() {
userManagement = new JackrabbitUserManagement();
}
@@ -159,7 +160,7 @@ public class JcrACLManagement implements ACLManagement {
}
@Override
- public @NotNull Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException {
+ public @NotNull Map<String, List<? extends AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException {
// first do a quick check if path may contain principal ACLs at all before triggering expensive traversal
if (!areAuthorizablesAllowedBelowPath(node.getSession(), node.getPath())) {
// TODO: Oak does not allow principal based authorizables everywhere, so we may restrict further
@@ -172,42 +173,18 @@ public class JcrACLManagement implements ACLManagement {
}
JackrabbitAccessControlManager jrAcMgr = (JackrabbitAccessControlManager) acMgr;
PrincipalAccessControlPolicyCollector policiesCollector = new PrincipalAccessControlPolicyCollector(jrAcMgr);
- findPrincipalsRecursively(jrSession.getUserManager(), node, policiesCollector);
- return policiesCollector.getPoliciesPerPrincipal();
- }
-
- private static final class SimplePrincipal implements Principal {
- private final String name;
-
- SimplePrincipal(String name) {
- if(name == null) {
- throw new IllegalArgumentException("Name cannot be null");
- }
- this.name = name;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean equals(Object other) {
- return (
- other instanceof SimplePrincipal)
- && (this.name.equals(((SimplePrincipal)other).name));
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
+ try {
+ findPrincipalsRecursively(jrSession.getUserManager(), node, policiesCollector);
+ return policiesCollector.getPoliciesPerPrincipal();
+ } catch (UncheckedRepositoryException e) {
+ throw e.getCause();
}
}
private static final class PrincipalAccessControlPolicyCollector implements Consumer<Principal> {
private final JackrabbitAccessControlManager jrAcMgr;
- private final Map<String, List<AccessControlPolicy>> policiesPerPrincipal;
+ private final Map<String, List<? extends AccessControlPolicy>> policiesPerPrincipal;
public PrincipalAccessControlPolicyCollector(JackrabbitAccessControlManager jrAcMgr) {
super();
@@ -215,31 +192,35 @@ public class JcrACLManagement implements ACLManagement {
this.policiesPerPrincipal = new HashMap<>();
}
- public Map<String, List<AccessControlPolicy>> getPoliciesPerPrincipal() {
+ public Map<String, List<? extends AccessControlPolicy>> getPoliciesPerPrincipal() {
return policiesPerPrincipal;
}
@Override
public void accept(Principal principal) {
try {
- policiesPerPrincipal.put(principal.getName(), Arrays.asList(jrAcMgr.getPolicies(principal)));
+ List<JackrabbitAccessControlPolicy> policies = Arrays.asList(jrAcMgr.getPolicies(principal));
+ if (!policies.isEmpty()) {
+ policiesPerPrincipal.put(principal.getName(), policies);
+ }
} catch (RepositoryException e) {
- //throw new UncheckedRepositoryException()
+ throw new UncheckedRepositoryException(e);
}
}
}
private void findPrincipalsRecursively(UserManager userMgr, Node node, Consumer<Principal> principalConsumer) throws RepositoryException {
+ // TODO: check if the additional check with UserManagement isAuthorizableNodeType really speeds things up...
if (userManagement.isAuthorizableNodeType(node.getPrimaryNodeType().getName())) {
- // what is this authorizable's name?
Authorizable authorizable = userMgr.getAuthorizableByPath(node.getPath());
if (authorizable != null) {
principalConsumer.accept(authorizable.getPrincipal());
}
} else {
- for (Node child : JcrUtils.in(node.getNodes())) {
+ for (Node child : JcrUtils.in(((Iterator<Node>)node.getNodes()))) {
findPrincipalsRecursively(userMgr, child, principalConsumer);
}
}
}
+
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
index 568b8f80..5eafbf13 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
@@ -88,7 +88,7 @@ public class JackrabbitServiceProvider implements ServiceProvider {
*/
public ACLManagement getACLManagement() {
if (aclManagement == null) {
- aclManagement = new JcrACLManagement();
+ aclManagement = new JackrabbitACLManagement();
}
return aclManagement;
}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
index 04c313c9..ba96e82d 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
@@ -19,10 +19,12 @@ package org.apache.jackrabbit.vault.fs.spi.impl.jcr20;
import java.util.UUID;
+import javax.jcr.AccessDeniedException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.Authorizable;
@@ -62,16 +64,9 @@ public class JackrabbitUserManagement implements UserManagement {
* {@inheritDoc}
*/
public String getAuthorizablePath(Session session, String id) {
- UserManager uMgr;
- try {
- uMgr = ((JackrabbitSession) session).getUserManager();
- } catch (RepositoryException e) {
- log.warn("Unable to get authorizable path of {}. Error while retrieving user manager.", id, e);
- return null;
- }
Authorizable authorizable;
try {
- authorizable = uMgr.getAuthorizable(id);
+ authorizable = getAuthorizable(session, id);
if (authorizable == null) {
log.debug("No existing authorizable with id {} found", id);
return null;
@@ -116,19 +111,10 @@ public class JackrabbitUserManagement implements UserManagement {
* {@inheritDoc}
*/
public void addMembers(Session session, String id, String[] membersUUID) {
- if (!(session instanceof JackrabbitSession)) {
- log.warn("Unable to update membership. no jackrabbit session.");
- return;
- }
- UserManager uMgr;
- try {
- uMgr = ((JackrabbitSession) session).getUserManager();
- } catch (RepositoryException e) {
- log.warn("Unable to update membership of {}. Error while retrieving user manager.", id, e);
- return;
- }
Authorizable auth;
+ UserManager uMgr;
try {
+ uMgr = getUserManager(session);
auth = uMgr.getAuthorizable(id);
} catch (RepositoryException e) {
log.warn("Unable to update membership of {}. Error while retrieving authorizable.", id, e);
@@ -170,4 +156,29 @@ public class JackrabbitUserManagement implements UserManagement {
}
}
}
+
+ private UserManager getUserManager(Session session) throws RepositoryException {
+ if (!(session instanceof JackrabbitSession)) {
+ throw new RepositoryException("no jackrabbit session.");
+ }
+ return ((JackrabbitSession) session).getUserManager();
+ }
+
+ private Authorizable getAuthorizable(Session session, String id) throws RepositoryException {
+ return getUserManager(session).getAuthorizable(id);
+ }
+
+ @Override
+ public String getPrincipalName(Session session, String id) {
+ try {
+ Authorizable auth = getAuthorizable(session, id);
+ if (auth != null) {
+ return auth.getPrincipal().getName();
+ }
+ } catch (RepositoryException e) {
+ log.warn("Unable to get principal name of {}. Error while retrieving user manager or authorizable.", id, e);
+ return null;
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java
new file mode 100644
index 00000000..ec71e1e6
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java
@@ -0,0 +1,32 @@
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20;
+
+import java.security.Principal;
+
+/** Helper class to create users/groups with intermediate path */
+public final class SimplePrincipal implements Principal {
+ private final String name;
+
+ public SimplePrincipal(String name) {
+ if(name == null) {
+ throw new IllegalArgumentException("Name cannot be null");
+ }
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (
+ other instanceof SimplePrincipal)
+ && (this.name.equals(((SimplePrincipal)other).name));
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java
new file mode 100644
index 00000000..7afeccf5
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java
@@ -0,0 +1,96 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+
+public class AbstractAccessControlEntry {
+ final Collection<String> privileges;
+ final Map<String, Value[]> restrictions;
+
+ protected abstract static class Builder<T extends AbstractAccessControlEntry> implements JackrabbitAccessControlEntryBuilder<T> {
+ final Collection<String> privileges;
+ final Map<String, Value[]> restrictions;
+
+ Builder(Collection<String> privileges) {
+ this.privileges = new HashSet<>(privileges);
+ this.restrictions = new HashMap<>();
+ }
+
+ @Override
+ public void addRestriction(String restrictionName, Value[] values) {
+ restrictions.put(restrictionName, values);
+ }
+ }
+
+ protected AbstractAccessControlEntry(JackrabbitAccessControlEntry entry) throws RepositoryException {
+ this(Arrays.stream(entry.getPrivileges()).map(Privilege::getName).collect(Collectors.toList()),
+ Arrays.stream(entry.getRestrictionNames())
+ .collect(Collectors.<String, String, Value[]>toMap(rn -> rn, rn -> {
+ try {
+ return entry.getRestrictions(rn);
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ })));
+ }
+
+ Map.Entry<Map<String, Value>, Map<String, Value[]>> separateRestrictions(JackrabbitAccessControlList list) throws RepositoryException {
+ Map<String, Value> svRestrictions = new HashMap<>();
+ Map<String, Value[]> mvRestrictions = new HashMap<>();
+ try {
+ Map<Boolean, List<String>> restrictionNamesMap = restrictions.keySet().stream().collect(Collectors.partitioningBy(r -> {
+ try {
+ return list.isMultiValueRestriction(r);
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ }));
+ restrictionNamesMap.get(Boolean.TRUE).stream().forEach(restrictionName -> mvRestrictions.put(restrictionName, restrictions.get(restrictionName)));
+ restrictionNamesMap.get(Boolean.FALSE).stream().forEach(restrictionName -> svRestrictions.put(restrictionName, restrictions.get(restrictionName)[0]));
+ } catch (UncheckedRepositoryException e) {
+ throw e.getCause();
+ }
+ return new AbstractMap.SimpleEntry<>(svRestrictions, mvRestrictions);
+ }
+
+ protected AbstractAccessControlEntry(Collection<String> privileges, Map<String, Value[]> restrictions) {
+ this.privileges = new HashSet<>(privileges);
+ this.restrictions = new HashMap<>(restrictions);
+ }
+
+ Privilege[] getPrivileges(AccessControlManager acMgr) throws RepositoryException {
+ return AccessControlUtils.privilegesFromNames(acMgr, privileges.toArray(new String[0]));
+ }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
similarity index 57%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
index 9b735fd4..9c2f8cb0 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
@@ -1,4 +1,4 @@
-/*
+/*************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -13,9 +13,20 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import javax.jcr.Value;
+
+/**
+ * Builder for an entry to be used with {@link PrincipalBasedAccessControlList} or {@link ResourceBasedAccessControlList}.
+ *
+ * @param <T> the type of the entry
*/
+public interface JackrabbitAccessControlEntryBuilder<T extends AbstractAccessControlEntry> {
+
+ T build();
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+ void addRestriction(String restrictionName, Value[] values);
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java
new file mode 100644
index 00000000..1ace04fb
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java
@@ -0,0 +1,193 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.SimplePrincipal;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstraction on top of a JCR Access control policies
+ */
+public abstract class JackrabbitAccessControlPolicy {
+
+ protected final String accessControlledPath;
+ /** default logger */
+ protected static final Logger log = LoggerFactory.getLogger(JackrabbitAccessControlPolicy.class);
+
+ public static JackrabbitAccessControlPolicy fromAccessControlPolicy(AccessControlPolicy policy) throws RepositoryException {
+ final JackrabbitAccessControlPolicyBuilder<? extends JackrabbitAccessControlPolicy> builder;
+ if (policy instanceof PrincipalAccessControlList) {
+ PrincipalAccessControlList principalAcl = (PrincipalAccessControlList)policy;
+ builder = new PrincipalBasedAccessControlList.Builder(principalAcl.getPrincipal().getName());
+ Arrays.stream(principalAcl.getAccessControlEntries()).map(PrincipalAccessControlList.Entry.class::cast).map(t -> {
+ try {
+ return new PrincipalBasedAccessControlEntry(t);
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ }).forEach(builder::addEntry);
+ } else if (policy instanceof PrincipalSetPolicy) {
+ PrincipalSetPolicy principalSetPolicy = (PrincipalSetPolicy)policy;
+ builder = new PrincipalSetAccessControlPolicy.Builder(principalSetPolicy.getPrincipals().stream().map(Principal::getName).collect(Collectors.toList()));
+ } else if (policy instanceof JackrabbitAccessControlList) {
+ JackrabbitAccessControlList acl = (JackrabbitAccessControlList)policy;
+ builder = new ResourceBasedAccessControlList.Builder();
+ Arrays.stream(acl.getAccessControlEntries()).map(JackrabbitAccessControlEntry.class::cast).map(t -> {
+ try {
+ return new ResourceBasedAccessControlEntry(t);
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ }).forEach(builder::addEntry);
+ } else {
+ throw new RepositoryException("Unsupported policy type " + policy);
+ }
+ String accessControlledPath = ((org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy)policy).getPath();
+ return builder.build(accessControlledPath);
+ }
+
+ JackrabbitAccessControlPolicy(String accessControlledPath) {
+ this.accessControlledPath = accessControlledPath;
+ }
+
+ Principal getPrincipal(Session session, final String principalName) throws RepositoryException {
+ PrincipalManager pMgr = getPrincipalManager(session);
+ Principal p = pMgr.getPrincipal(principalName);
+ if (p == null) {
+ try {
+ Authorizable a = getUserManager(session).getAuthorizableByPath(accessControlledPath);
+ if (a != null) {
+ p = a.getPrincipal();
+ }
+ } catch (RepositoryException e) {
+ log.debug("Error while trying to retrieve user/group from access controlled path {}, {}", accessControlledPath, e.getMessage());
+ }
+ if (p == null) {
+ p = getPrincipal(principalName);
+ }
+ }
+ return p;
+ }
+
+ Principal getPrincipal(final String principalName) {
+ return new SimplePrincipal(principalName);
+ }
+
+ protected static final JackrabbitAccessControlManager getAccessControlManager(Session session) throws RepositoryException {
+ AccessControlManager acMgr = session.getAccessControlManager();
+ if (!(acMgr instanceof JackrabbitAccessControlManager)) {
+ throw new IllegalStateException("The access control manager exposed by the given session is no JackrabbitAccessControlManager");
+ }
+ return (JackrabbitAccessControlManager)acMgr;
+ }
+
+ protected static final PrincipalManager getPrincipalManager(Session session) throws RepositoryException {
+ if(!(session instanceof JackrabbitSession)) {
+ throw new IllegalStateException("This session is not a JackrabbitSession");
+ }
+ return ((JackrabbitSession)session).getPrincipalManager();
+ }
+
+ protected static final UserManager getUserManager(Session session) throws RepositoryException {
+ if(!(session instanceof JackrabbitSession)) {
+ throw new IllegalStateException("This session is not a JackrabbitSession");
+ }
+ return ((JackrabbitSession)session).getUserManager();
+ }
+
+ <T> T getPolicy(JackrabbitAccessControlManager acMgr, Class<T> clz) throws RepositoryException {
+ for (AccessControlPolicy p : acMgr.getPolicies(accessControlledPath)) {
+ if (clz.isAssignableFrom(p.getClass())) {
+ return clz.cast(p);
+ }
+ }
+ return null;
+ }
+
+ <T> T getPolicy(JackrabbitAccessControlManager acMgr, Class<T> clz, Principal principal) throws RepositoryException {
+ for (AccessControlPolicy p : acMgr.getPolicies(principal)) {
+ if (clz.isAssignableFrom(p.getClass())) {
+ return clz.cast(p);
+ }
+ }
+ return null;
+ }
+
+ <T> T getApplicablePolicy(JackrabbitAccessControlManager acMgr, Class<T> clz) throws RepositoryException {
+ AccessControlPolicyIterator iter = acMgr.getApplicablePolicies(accessControlledPath);
+ while (iter.hasNext()) {
+ AccessControlPolicy p = iter.nextAccessControlPolicy();
+ if (clz.isAssignableFrom(p.getClass())) {
+ return clz.cast(p);
+ }
+ }
+
+ // no applicable policy
+ throw new RepositoryException("no applicable AccessControlPolicy of type " + clz + " on " +
+ (accessControlledPath == null ? "'root'" : accessControlledPath));
+ }
+
+ <T> T getApplicablePolicy(JackrabbitAccessControlManager acMgr, Class<T> clz, Principal principal) throws RepositoryException {
+ for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getApplicablePolicies(principal)) {
+ if (clz.isAssignableFrom(p.getClass())) {
+ return clz.cast(p);
+ }
+ }
+
+ // no applicable policy
+ throw new AccessControlException("no applicable AccessControlPolicy of type " + clz + " for " + principal.getName());
+ }
+
+ protected void addPathIfExists(Session session, List<String> paths, String path) throws RepositoryException {
+ if (session.nodeExists(path)) {
+ paths.add(path);
+ }
+ }
+
+ /**
+ * Imports the policy into the repository according to the rules from {@code aclHandling}.
+ * @param session
+ * @param aclHandling
+ * @return the paths which have been modified or added
+ * @throws RepositoryException
+ */
+ public abstract List<String> apply(Session session, AccessControlHandling aclHandling) throws RepositoryException;
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
similarity index 65%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
index 9b735fd4..789304d7 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
@@ -1,4 +1,4 @@
-/*
+/*************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -13,9 +13,13 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+public interface JackrabbitAccessControlPolicyBuilder<T extends JackrabbitAccessControlPolicy> {
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+ T build(String accessControlledPath);
+
+ void addEntry(AbstractAccessControlEntry entry);
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java
new file mode 100644
index 00000000..7abf2f72
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java
@@ -0,0 +1,54 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+
+public class PrincipalBasedAccessControlEntry extends AbstractAccessControlEntry {
+
+ public static class Builder extends AbstractAccessControlEntry.Builder<PrincipalBasedAccessControlEntry> {
+ final String effectivePath;
+
+ public Builder(Collection<String> privileges, String effectivePath) {
+ super(privileges);
+ this.effectivePath = effectivePath;
+ }
+
+ @Override
+ public PrincipalBasedAccessControlEntry build() {
+ return new PrincipalBasedAccessControlEntry(effectivePath, privileges, restrictions);
+ }
+ }
+
+ final String effectivePath;
+
+ public PrincipalBasedAccessControlEntry(PrincipalAccessControlList.Entry entry) throws RepositoryException {
+ super(entry);
+ this.effectivePath = entry.getEffectivePath();
+ }
+
+ public PrincipalBasedAccessControlEntry(String effectivePath, Collection<String> privileges, Map<String, Value[]> restrictions) {
+ super(privileges, restrictions);
+ this.effectivePath = effectivePath;
+ }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java
new file mode 100644
index 00000000..4709e73e
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java
@@ -0,0 +1,108 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class PrincipalBasedAccessControlList extends JackrabbitAccessControlPolicy {
+
+ public static final class Builder implements JackrabbitAccessControlPolicyBuilder<PrincipalBasedAccessControlList> {
+ private final List<PrincipalBasedAccessControlEntry> entries = new ArrayList<>();
+ private final String principalName;
+
+ public Builder(String principalName) {
+ this.principalName = principalName;
+ }
+
+ @Override
+ public void addEntry(AbstractAccessControlEntry entry) {
+ if (!(entry instanceof PrincipalBasedAccessControlEntry)) {
+ throw new IllegalStateException("Only entries of type PrincipalBasedAccessControlEntry are supported");
+ }
+ entries.add((PrincipalBasedAccessControlEntry)entry);
+ }
+
+ @Override
+ public PrincipalBasedAccessControlList build(String accessControlledPath) {
+ return new PrincipalBasedAccessControlList(accessControlledPath, principalName, entries);
+ }
+ }
+
+ private final String principalName;
+ private final List<PrincipalBasedAccessControlEntry> entries = new ArrayList<>();
+
+ private PrincipalBasedAccessControlList(String accessControlledPath, String principalName, List<PrincipalBasedAccessControlEntry> entries) {
+ super(accessControlledPath);
+ this.entries.addAll(entries);
+ this.principalName = principalName;
+ }
+
+ @Override
+ public List<String> apply(Session session, final AccessControlHandling aclHandling) throws RepositoryException {
+ if (aclHandling == AccessControlHandling.IGNORE) {
+ return Collections.emptyList();
+ }
+ if (aclHandling == AccessControlHandling.MERGE_PRESERVE) {
+ log.debug("MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE.");
+ return Collections.emptyList();
+ }
+
+ JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+ Principal principal = getPrincipal(session, principalName);
+ PrincipalAccessControlList acl = getPolicy(acMgr, PrincipalAccessControlList.class, principal);
+ if (acl != null && aclHandling == AccessControlHandling.OVERWRITE) {
+ // remove existing policy for 'OVERWRITE'
+ acMgr.removePolicy(acl.getPath(), acl);
+ acl = null;
+ }
+
+ if (acl == null) {
+ acl = getApplicablePolicy(acMgr, PrincipalAccessControlList.class, principal);
+ }
+
+ // apply ACEs of package for MERGE and OVERWRITE
+ for (PrincipalBasedAccessControlEntry entry : entries) {
+ Entry<Map<String, Value>, Map<String, Value[]>> restrictions = entry.separateRestrictions(acl);
+ acl.addEntry(entry.effectivePath, entry.getPrivileges(acMgr), restrictions.getKey(), restrictions.getValue());
+ }
+ acMgr.setPolicy(acl.getPath(), acl);
+
+ List<String> paths = new LinkedList<>();
+ if (accessControlledPath == null) {
+ addPathIfExists(session, paths, "/rep:repoPolicy");
+ } else if ("/".equals(accessControlledPath)) {
+ addPathIfExists(session, paths, "/rep:policy");
+ } else {
+ addPathIfExists(session, paths, accessControlledPath + "/rep:policy");
+ }
+ return paths;
+ }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java
new file mode 100644
index 00000000..b728e0fc
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java
@@ -0,0 +1,91 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class PrincipalSetAccessControlPolicy extends JackrabbitAccessControlPolicy {
+
+ public static final class Builder implements JackrabbitAccessControlPolicyBuilder<JackrabbitAccessControlPolicy> {
+ private final Collection<String> principalNames;
+
+ public Builder(Collection<String> principalNames) {
+ this.principalNames = new HashSet<>(principalNames);
+ }
+
+ @Override
+ public void addEntry(AbstractAccessControlEntry entry) {
+ throw new UnsupportedOperationException("This policy type does not support entries");
+ }
+
+ @Override
+ public PrincipalSetAccessControlPolicy build(String accessControlledPath) {
+ return new PrincipalSetAccessControlPolicy(accessControlledPath, principalNames);
+ }
+
+ }
+
+ private final Collection<String> principalNames;
+
+ public PrincipalSetAccessControlPolicy(String accessControlledPath, Collection<String> principalNames) {
+ super(accessControlledPath);
+ this.principalNames = new HashSet<>(principalNames);
+ }
+
+ @Override
+ public List<String> apply(Session session, AccessControlHandling aclHandling) throws RepositoryException {
+ JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+ PrincipalSetPolicy psPolicy = getPolicy(acMgr, PrincipalSetPolicy.class);
+ if (psPolicy != null) {
+ Set<Principal> existingPrincipals = psPolicy.getPrincipals();
+ // remove existing policy for 'overwrite'
+ if (aclHandling == AccessControlHandling.OVERWRITE) {
+ psPolicy.removePrincipals(existingPrincipals.toArray(new Principal[existingPrincipals.size()]));
+ }
+ } else {
+ psPolicy = getApplicablePolicy(acMgr, PrincipalSetPolicy.class);
+ }
+
+ // TODO: correct behavior for MERGE and MERGE_PRESERVE?
+ Principal[] principals = principalNames.stream().map(name -> getPrincipal(name)).toArray(Principal[]::new);
+
+ psPolicy.addPrincipals(principals);
+ acMgr.setPolicy(accessControlledPath, psPolicy);
+
+ List<String> paths = new LinkedList<>();
+ if ("/".equals(accessControlledPath)) {
+ addPathIfExists(session, paths, "/rep:cugPolicy");
+ } else {
+ addPathIfExists(session, paths, accessControlledPath + "/rep:cugPolicy");
+ }
+ return paths;
+ }
+
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java
new file mode 100644
index 00000000..1a5efead
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java
@@ -0,0 +1,59 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+
+public class ResourceBasedAccessControlEntry extends AbstractAccessControlEntry {
+
+ public static class Builder extends AbstractAccessControlEntry.Builder<ResourceBasedAccessControlEntry> {
+ final boolean allow;
+ final String principalName;
+
+ public Builder(Collection<String> privileges, boolean allow, String principalName) {
+ super(privileges);
+ this.allow = allow;
+ this.principalName = principalName;
+ }
+
+ @Override
+ public ResourceBasedAccessControlEntry build() {
+ return new ResourceBasedAccessControlEntry(allow, principalName, privileges, restrictions);
+ }
+ }
+
+ final boolean allow;
+ final String principalName;
+
+ public ResourceBasedAccessControlEntry(JackrabbitAccessControlEntry entry) throws RepositoryException {
+ super(entry);
+ this.allow = entry.isAllow();
+ this.principalName = entry.getPrincipal().getName();
+ }
+
+ protected ResourceBasedAccessControlEntry(boolean allow, String principalName, Collection<String> privileges, Map<String, Value[]> restrictions) {
+ super(privileges, restrictions);
+ this.allow = allow;
+ this.principalName = principalName;
+ }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java
new file mode 100644
index 00000000..5be53f21
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java
@@ -0,0 +1,123 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class ResourceBasedAccessControlList extends JackrabbitAccessControlPolicy {
+
+ public static final class Builder implements JackrabbitAccessControlPolicyBuilder<ResourceBasedAccessControlList> {
+ private final List<ResourceBasedAccessControlEntry> entries;
+
+ public Builder() {
+ entries = new ArrayList<>();
+ }
+
+ @Override
+ public void addEntry(AbstractAccessControlEntry entry) {
+ if (!(entry instanceof ResourceBasedAccessControlEntry)) {
+ throw new IllegalStateException("Only entries of type ResourceBasedAccessControlEntry are supported");
+ }
+ entries.add((ResourceBasedAccessControlEntry)entry);
+ }
+
+ @Override
+ public ResourceBasedAccessControlList build(String accessControlledPath) {
+ return new ResourceBasedAccessControlList(accessControlledPath, entries);
+ }
+ }
+
+ private final List<ResourceBasedAccessControlEntry> entries = new ArrayList<>();
+
+ ResourceBasedAccessControlList(String accessControlledPath, List<ResourceBasedAccessControlEntry> aceList) {
+ super(accessControlledPath);
+ this.entries.addAll(aceList);
+ }
+
+ @Override
+ public List<String> apply(Session session, final AccessControlHandling aclHandling) throws RepositoryException {
+ JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+ // find principals of existing ACL
+ JackrabbitAccessControlList acl = getPolicy(acMgr, JackrabbitAccessControlList.class);
+ Set<String> existingPrincipals = new HashSet<>();
+ if (acl != null) {
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ existingPrincipals.add(ace.getPrincipal().getName());
+ }
+
+ // remove existing policy for 'overwrite'
+ if (aclHandling == AccessControlHandling.OVERWRITE) {
+ acMgr.removePolicy(accessControlledPath, acl);
+ acl = null;
+ }
+ }
+
+ if (acl == null) {
+ acl = getApplicablePolicy(acMgr, JackrabbitAccessControlList.class);
+ }
+
+ // clear all ACEs of the package principals for merge (VLT-94), otherwise the `acl.addEntry()` below
+ // might just combine the privileges.
+ if (aclHandling == AccessControlHandling.MERGE) {
+ for (ResourceBasedAccessControlEntry entry : entries) {
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ if (ace.getPrincipal().getName().equals(entry.principalName)) {
+ acl.removeAccessControlEntry(ace);
+ }
+ }
+ }
+ }
+
+ // apply ACEs of package
+ for (ResourceBasedAccessControlEntry ace : entries) {
+ final String principalName = ace.principalName;
+ if (aclHandling == AccessControlHandling.MERGE_PRESERVE && existingPrincipals.contains(principalName)) {
+ // skip principal if it already has an ACL
+ continue;
+ }
+ Principal principal = getPrincipal(principalName);
+ Entry<Map<String, Value>, Map<String, Value[]>> restrictions = ace.separateRestrictions(acl);
+ acl.addEntry(principal, ace.getPrivileges(acMgr), ace.allow, restrictions.getKey(), restrictions.getValue());
+ }
+ acMgr.setPolicy(accessControlledPath, acl);
+ List<String> paths = new ArrayList<>();
+ if (accessControlledPath == null) {
+ addPathIfExists(session, paths, "/rep:repoPolicy");
+ } else if ("/".equals(accessControlledPath)) {
+ addPathIfExists(session, paths, "/rep:policy");
+ } else {
+ addPathIfExists(session, paths, accessControlledPath + "/rep:policy");
+ }
+ return paths;
+ }
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
index 9b735fd4..6fae8abe 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-@Version("2.5.0")
+@Version("2.6.0")
package org.apache.jackrabbit.vault.fs.spi;
import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
index 98ab931e..c5060ff2 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
@@ -25,6 +25,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.jcr.Binary;
import javax.jcr.InvalidSerializedDataException;
@@ -35,6 +36,7 @@ import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
+import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
@@ -49,13 +51,14 @@ import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.util.XMLChar;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
import org.apache.jackrabbit.value.ValueHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
- * Helper class that represents a JCR property in the FileVault (enhanced) document view format.
- * It contains formatting and parsing methods for writing/reading enhanced
+ * Immutable helper class that represents a JCR property in the FileVault (enhanced) document view format.
+ * It contains formatting and parsing methods for serializing/deserializing enhanced
* docview properties.
* <br>
* The string representation adheres to the following grammar:
@@ -703,6 +706,10 @@ public class DocViewProperty2 {
return type;
}
+ private int getSafeType() {
+ return type == PropertyType.UNDEFINED ? PropertyType.STRING : type;
+ }
+
public @NotNull Optional<String> getStringValue() {
if (!values.isEmpty()) {
return Optional.of(values.get(0));
@@ -713,4 +720,37 @@ public class DocViewProperty2 {
public @NotNull List<String> getStringValues() {
return values;
}
+
+ /**
+ * @param valueFactory the value factory to use for converting the underlying string to the JCR value
+ * @return the value or empty if no value set. For multi value only the first item is returned
+ * @throws ValueFormatException
+ * @since 3.6.10
+ */
+ public @NotNull Optional<Value> getValue(@NotNull ValueFactory valueFactory) throws ValueFormatException {
+ if (!values.isEmpty()) {
+ return Optional.of(valueFactory.createValue(values.get(0), getSafeType()));
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * @param valueFactory the value factory to use for converting the underlying string to the JCR value
+ * @return the list of values, may be empty. In case of single value entry just a single value list.
+ * @throws ValueFormatException
+ * @since 3.6.10
+ */
+ public @NotNull List<Value> getValues(@NotNull ValueFactory valueFactory) throws ValueFormatException {
+ try {
+ return values.stream().map(v -> {
+ try {
+ return valueFactory.createValue(v, getSafeType());
+ } catch (ValueFormatException e) {
+ throw new UncheckedValueFormatException(e);
+ }
+ }).collect(Collectors.toList());
+ } catch (UncheckedValueFormatException e) {
+ throw e.getCause();
+ }
+ }
}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
similarity index 55%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
index 9b735fd4..2a49e107 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
@@ -14,8 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.jackrabbit.vault.util;
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+import javax.jcr.ValueFormatException;
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+/**
+ * Wraps a {@link ValueFormatException} with an unchecked exception.
+ * Useful in {@link FunctionalInterface} methods/lambda expressions which must not throw checked exceptions.
+ * @since 3.6.10
+ *
+ */
+public class UncheckedValueFormatException extends RuntimeException {
+
+ private static final long serialVersionUID = 7179774059211440453L;
+
+ public UncheckedValueFormatException(ValueFormatException e) {
+ super(e);
+ }
+
+ @Override
+ public synchronized ValueFormatException getCause() {
+ return (ValueFormatException) super.getCause();
+ }
+}
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
index 2431e5f8..7f22485e 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
@@ -16,14 +16,38 @@
*/
package org.apache.jackrabbit.vault.packaging.integration;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList.Entry;
import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
@@ -46,34 +70,24 @@ import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.vault.fs.api.ImportMode;
import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.hamcrest.Description;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Test;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.ValueFormatException;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.Privilege;
-import java.security.Principal;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
public class PrincipalBasedIT extends IntegrationTestBase {
@@ -113,8 +127,8 @@ public class PrincipalBasedIT extends IntegrationTestBase {
Map<String, Value[]> mvRestrictions = ImmutableMap.of(AccessControlConstants.REP_ITEM_NAMES, new Value[]{vf.createValue(JcrConstants.JCR_CONTENT, PropertyType.NAME)});
pacl.addEntry(EFFECTIVE_PATH, AccessControlUtils.privilegesFromNames(acMgr, Privilege.JCR_READ), ImmutableMap.<String, Value>of(), mvRestrictions);
pacl.addEntry(null, AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.JCR_NAMESPACE_MANAGEMENT));
- existingEntries = pacl.getAccessControlEntries();
acMgr.setPolicy(pacl.getPath(), pacl);
+ existingEntries = pacl.getAccessControlEntries();
break;
}
}
@@ -176,34 +190,81 @@ public class PrincipalBasedIT extends IntegrationTestBase {
private void assertPolicy(@NotNull Principal principal, @NotNull AccessControlEntry... expectedEntries) throws RepositoryException {
for (AccessControlPolicy policy : acMgr.getPolicies(principal)) {
+ // disregard the order
if (policy instanceof PrincipalAccessControlList) {
PrincipalAccessControlList pacl = (PrincipalAccessControlList) policy;
AccessControlEntry[] aces = pacl.getAccessControlEntries();
- assertEquals(expectedEntries.length, aces.length);
+ MatcherAssert.assertThat(Arrays.asList(aces), Matchers.containsInAnyOrder(Arrays.stream(aces).map(e -> new PrincipalAccessControlEntryMatcher(e, pacl)).collect(Collectors.toList())));
+ return;
+ }
+ }
+ fail("expected PrincipalAccessControlList for principal " + principal.getName());
+ }
- for (int i = 0; i < expectedEntries.length; i++) {
- assertTrue(expectedEntries[i] instanceof PrincipalAccessControlList.Entry);
- assertTrue(aces[i] instanceof PrincipalAccessControlList.Entry);
+ static String toString(PrincipalAccessControlList.Entry entry) {
+ try {
+ return "PrincipalAccessControlList.Entry[effectivePath="+entry.getEffectivePath() +", privileges=" + Arrays.toString(entry.getPrivileges()) + ", restrictionNames = " + Arrays.toString(entry.getRestrictionNames()) + "]";
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
+ }
+ }
+ private static final class PrincipalAccessControlEntryMatcher extends TypeSafeMatcher<AccessControlEntry> {
- PrincipalAccessControlList.Entry entry = (PrincipalAccessControlList.Entry) aces[i];
- PrincipalAccessControlList.Entry expected = (PrincipalAccessControlList.Entry) expectedEntries[i];
+ private final PrincipalAccessControlList.Entry expectedEntry;
+ private final JackrabbitAccessControlList containerACL;
+ public PrincipalAccessControlEntryMatcher(AccessControlEntry accessControlEntry, JackrabbitAccessControlList containerACL) {
+ this.expectedEntry = PrincipalAccessControlList.Entry.class.cast(accessControlEntry);
+ this.containerACL = containerACL;
+ }
- assertEquals(expected.getEffectivePath(), entry.getEffectivePath());
- assertEquals(ImmutableSet.copyOf(expected.getPrivileges()), ImmutableSet.copyOf(entry.getPrivileges()));
- assertEquals(ImmutableSet.copyOf(expected.getRestrictionNames()), ImmutableSet.copyOf(entry.getRestrictionNames()));
- for (String rName : expected.getRestrictionNames()) {
- if (pacl.isMultiValueRestriction(rName)) {
- assertArrayEquals(expected.getRestrictions(rName), entry.getRestrictions(rName));
- } else {
- assertEquals(expected.getRestriction(rName), entry.getRestriction(rName));
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(PrincipalBasedIT.toString(expectedEntry));
+ }
+
+ @Override
+ protected void describeMismatchSafely(AccessControlEntry item, Description mismatchDescription) {
+ mismatchDescription.appendText(PrincipalBasedIT.toString(PrincipalAccessControlList.Entry.class.cast(item)));
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessControlEntry item) {
+ if (!(item instanceof PrincipalAccessControlList.Entry)) {
+ return false;
+ }
+ Entry actualEntry = PrincipalAccessControlList.Entry.class.cast(item);
+ if (!Objects.equals(expectedEntry.getEffectivePath(), actualEntry.getEffectivePath())) {
+ return false;
+ }
+ if (!ImmutableSet.copyOf(expectedEntry.getPrivileges()).equals(ImmutableSet.copyOf(actualEntry.getPrivileges()))) {
+ return false;
+ }
+ try {
+ if (!ImmutableSet.copyOf(expectedEntry.getRestrictionNames()).equals(ImmutableSet.copyOf(actualEntry.getRestrictionNames()))) {
+ return false;
+ }
+ for (String rName : expectedEntry.getRestrictionNames()) {
+ if (containerACL.isMultiValueRestriction(rName)) {
+ if (!Arrays.equals(expectedEntry.getRestrictions(rName), actualEntry.getRestrictions(rName))) {
+ return false;
+ }
+ } else {
+ if (!Objects.equals(expectedEntry.getRestriction(rName), actualEntry.getRestriction(rName))) {
+ return false;
}
}
}
- return;
+ } catch (RepositoryException e) {
+ throw new UncheckedRepositoryException(e);
}
+ return true;
}
- fail("expected PrincipalAccessControlList for principal " + principal.getName());
+
+ }
+
+ private void assertNoPolicy(@NotNull Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
+ assertEquals("Expected no policy for principal " + principal.getName(), 0, acMgr.getPolicies(principal).length);
}
@Test
@@ -305,7 +366,8 @@ public class PrincipalBasedIT extends IntegrationTestBase {
admin.save();
extractVaultPackage("/test-packages/principalbased.zip");
- assertPolicy(testUser.getPrincipal(), packageEntries);
+ Authorizable newUser = userManager.getAuthorizable(SYSTEM_USER_ID);
+ assertPolicy(newUser.getPrincipal(), packageEntries);
}
@Test
@@ -436,15 +498,17 @@ public class PrincipalBasedIT extends IntegrationTestBase {
public void testNewUserHandlingMergePreserveModeReplace() throws Exception {
assumeTrue(isOak());
ImportOptions opts = getDefaultOptions();
+ // MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE (compare with comment
opts.setAccessControlHandling(AccessControlHandling.MERGE_PRESERVE);
opts.setImportMode(ImportMode.REPLACE);
+ // This removes the test user's ACEs being added in setup
admin.getNode(testUser.getPath()).remove();
admin.save();
extractVaultPackage("/test-packages/principalbased.zip", opts);
Principal p = userManager.getAuthorizable(SYSTEM_USER_ID).getPrincipal();
- assertPolicy(p, packageEntries);
+ assertNoPolicy(p);
}
@Test
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
index 9b93d6db..a70b9134 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
@@ -21,7 +21,7 @@ import java.util.Collections;
import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
-import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JcrACLManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JackrabbitACLManagement;
import org.apache.jackrabbit.vault.util.DocViewNode2;
import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
import org.apache.jackrabbit.vault.validation.spi.NodeContext;
@@ -35,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
*/
public class AccessControlValidator implements DocumentViewXmlValidator {
- protected static final ACLManagement ACL_MANAGEMENT = new JcrACLManagement();
+ protected static final ACLManagement ACL_MANAGEMENT = new JackrabbitACLManagement();
protected static final String MESSAGE_IGNORED_ACCESS_CONTROL_LIST = "Found an access control list, but it is never considered during installation as the property 'acHandling' is set to '%s'!";
protected static final String MESSAGE_INEFFECTIVE_ACCESS_CONTROL_LIST = "Found no access control list, but there is supposed to be one contained as the property 'acHandling' is set to '%s'!";
private final ValidationMessageSeverity severity;