SLING-8757 Add option to set/edit access control policies at user homes (add extraction of home-paths using constants added to sling-repoinit-parser)
diff --git a/pom.xml b/pom.xml
index 7120259..4f26c54 100644
--- a/pom.xml
+++ b/pom.xml
@@ -229,7 +229,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.repoinit.parser</artifactId>
- <version>1.3.1-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java
index da409d1..4150302 100644
--- a/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java
+++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java
@@ -17,6 +17,7 @@
package org.apache.sling.jcr.repoinit.impl;
import java.security.Principal;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -42,13 +43,20 @@
import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.util.Text;
import org.apache.sling.repoinit.parser.operations.AclLine;
import org.apache.sling.repoinit.parser.operations.RestrictionClause;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.apache.sling.repoinit.parser.operations.AclLine.ID_DELIMINATOR;
+import static org.apache.sling.repoinit.parser.operations.AclLine.PATH_HOME;
+import static org.apache.sling.repoinit.parser.operations.AclLine.PATH_REPOSITORY;
import static org.apache.sling.repoinit.parser.operations.AclLine.PROP_PATHS;
import static org.apache.sling.repoinit.parser.operations.AclLine.PROP_PRIVILEGES;
+import static org.apache.sling.repoinit.parser.operations.AclLine.SUBTREE_DELIMINATOR;
/** Utilities for ACL management */
public class AclUtil {
@@ -108,26 +116,22 @@
public static void setAcl(Session session, List<String> principals, List<String> paths, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
throws RepositoryException {
- for (String path : paths) {
- if (AclLine.PATH_REPOSITORY.equals(path)) {
- setRepositoryAcl(session, principals, privileges, isAllow, restrictionClauses);
- } else {
- if (!session.nodeExists(path)) {
- throw new PathNotFoundException("Cannot set ACL on non-existent path " + path);
- }
- setAcl(session, principals, path, privileges, isAllow, restrictionClauses);
+ for (String jcrPath : getJcrPaths(session, paths)) {
+ if (jcrPath != null && !session.nodeExists(jcrPath)) {
+ throw new PathNotFoundException("Cannot set ACL on non-existent path " + jcrPath);
}
+ setAcl(session, principals, jcrPath, privileges, isAllow, restrictionClauses);
}
}
- private static void setAcl(Session session, List<String> principals, String path, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
+ private static void setAcl(Session session, List<String> principals, String jcrPath, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
throws RepositoryException {
final String [] privArray = privileges.toArray(new String[privileges.size()]);
final Privilege[] jcrPriv = AccessControlUtils.privilegesFromNames(session, privArray);
- JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(session, path);
- checkState(acl != null, "No JackrabbitAccessControlList available for path " + path);
+ JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(session, jcrPath);
+ checkState(acl != null, "No JackrabbitAccessControlList available for path " + jcrPath);
LocalRestrictions localRestrictions = createLocalRestrictions(restrictionClauses, acl, session);
@@ -145,7 +149,7 @@
checkState(principal != null, "Principal not found: " + name);
LocalAccessControlEntry newAce = new LocalAccessControlEntry(principal, jcrPriv, isAllow, localRestrictions);
if (contains(existingAces, newAce)) {
- LOG.info("Not adding {} to path {} since an equivalent access control entry already exists", newAce, path);
+ LOG.info("Not adding {} to path {} since an equivalent access control entry already exists", newAce, jcrPath);
continue;
}
acl.addEntry(newAce.principal, newAce.privileges, newAce.isAllow,
@@ -153,7 +157,7 @@
changed = true;
}
if ( changed ) {
- session.getAccessControlManager().setPolicy(path, acl);
+ session.getAccessControlManager().setPolicy(jcrPath, acl);
}
}
@@ -176,11 +180,10 @@
LocalRestrictions restrictions = createLocalRestrictions(line.getRestrictions(), acl, session);
Privilege[] privileges = AccessControlUtils.privilegesFromNames(session, line.getProperty(PROP_PRIVILEGES).toArray(new String[0]));
- for (String path : line.getProperty(PROP_PATHS)) {
- String effectivePath = (path == null || path.isEmpty() || AclLine.PATH_REPOSITORY.equals(path)) ? null : path;
+ for (String effectivePath : getJcrPaths(session, line.getProperty(PROP_PATHS))) {
boolean added = acl.addEntry(effectivePath, privileges, restrictions.getRestrictions(), restrictions.getMVRestrictions());
if (!added) {
- LOG.info("Equivalent principal-based entry already exists for principal {} and effective path {} ", principalName, path);
+ LOG.info("Equivalent principal-based entry already exists for principal {} and effective path {} ", principalName, effectivePath);
} else {
modified = true;
}
@@ -211,6 +214,39 @@
return acl;
}
+ @NotNull
+ private static List<String> getJcrPaths(@NotNull Session session, @NotNull List<String> paths) throws RepositoryException {
+ List<String> jcrPaths = new ArrayList<>(paths.size());
+ for (String path : paths) {
+ if (PATH_REPOSITORY.equals(path) || path == null || path.isEmpty()) {
+ jcrPaths.add(null);
+ } else if (path.startsWith(PATH_HOME)) {
+ int lastHashIndex = path.lastIndexOf(SUBTREE_DELIMINATOR);
+ checkState(lastHashIndex > -1, "Invalid format of home path: # deliminator expected.");
+ String subTreePath = path.substring(lastHashIndex+1);
+ for (String aPath : getAuthorizablePaths(session, path.substring(PATH_HOME.length(), lastHashIndex))) {
+ jcrPaths.add(aPath + subTreePath);
+ }
+ } else {
+ jcrPaths.add(path);
+ }
+ }
+ return jcrPaths;
+ }
+
+ @NotNull
+ private static Iterable<String> getAuthorizablePaths(@NotNull Session session, @NotNull String ids) throws RepositoryException {
+ List<String> paths = new ArrayList<>();
+ for (String id : Text.explode(ids, ID_DELIMINATOR)) {
+ Authorizable a = UserUtil.getAuthorizable(session, id);
+ if (a == null) {
+ throw new PathNotFoundException("Cannot resolve path of user/group with id '" + id + "'.");
+ }
+ paths.add(a.getPath());
+ }
+ return paths;
+ }
+
// visible for testing
static boolean contains(AccessControlEntry[] existingAces, LocalAccessControlEntry newAce) throws RepositoryException {
for (int i = 0 ; i < existingAces.length; i++) {
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/PrincipalBasedAclTest.java b/src/test/java/org/apache/sling/jcr/repoinit/PrincipalBasedAclTest.java
index ad42344..9d14301 100644
--- a/src/test/java/org/apache/sling/jcr/repoinit/PrincipalBasedAclTest.java
+++ b/src/test/java/org/apache/sling/jcr/repoinit/PrincipalBasedAclTest.java
@@ -17,6 +17,11 @@
package org.apache.sling.jcr.repoinit;
import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.Jcr;
@@ -32,9 +37,13 @@
import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.sling.jcr.repoinit.impl.AclUtil;
import org.apache.sling.jcr.repoinit.impl.TestUtil;
import org.apache.sling.repoinit.parser.RepoInitParsingException;
+import org.apache.sling.repoinit.parser.operations.AclLine;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -47,11 +56,18 @@
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
import javax.security.auth.Subject;
+import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
+import java.util.List;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PrincipalBasedAclTest {
@@ -390,4 +406,38 @@
U.parseAndExecute(setup);
}
+
+ @Test
+ public void testHomePath() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ Authorizable a = uMgr.getAuthorizable(U.username);
+ Principal principal = a.getPrincipal();
+ String userHomePath = a.getPath();
+
+ JackrabbitAccessControlManager accessControlManager = AclUtil.getJACM(U.adminSession);
+ assertNull(getAcl(principal, accessControlManager));
+
+ AclLine line = new AclLine(AclLine.Action.ALLOW);
+ line.setProperty(AclLine.PROP_PRINCIPALS, Collections.singletonList(principal.getName()));
+ line.setProperty(AclLine.PROP_PRIVILEGES, Collections.singletonList(Privilege.JCR_READ));
+ line.setProperty(AclLine.PROP_PATHS, Collections.singletonList(":home:"+U.username+"#"));
+ AclUtil.setPrincipalAcl(U.adminSession, U.username, Collections.singletonList(line));
+
+ PrincipalAccessControlList acl = getAcl(principal, accessControlManager);
+ assertNotNull(acl);
+ assertEquals(1, acl.size());
+ PrincipalAccessControlList.Entry entry = (PrincipalAccessControlList.Entry) acl.getAccessControlEntries()[0];
+ assertArrayEquals(AccessControlUtils.privilegesFromNames(accessControlManager, Privilege.JCR_READ), entry.getPrivileges());
+ assertEquals(a.getPath(), entry.getEffectivePath());
+ }
+
+ @Nullable
+ private static PrincipalAccessControlList getAcl(@NotNull Principal principal, @NotNull JackrabbitAccessControlManager jacm) throws RepositoryException {
+ for (AccessControlPolicy policy : jacm.getPolicies(principal)) {
+ if (policy instanceof PrincipalAccessControlList) {
+ return (PrincipalAccessControlList) policy;
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/jcr/repoinit/impl/AclUtilTest.java b/src/test/java/org/apache/sling/jcr/repoinit/impl/AclUtilTest.java
index 8ea5f38..7a434f7 100644
--- a/src/test/java/org/apache/sling/jcr/repoinit/impl/AclUtilTest.java
+++ b/src/test/java/org/apache/sling/jcr/repoinit/impl/AclUtilTest.java
@@ -21,14 +21,18 @@
import static org.junit.Assert.assertTrue;
import java.security.Principal;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
+import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.Privilege;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.commons.PathUtils;
@@ -40,6 +44,7 @@
import org.junit.After;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -70,6 +75,9 @@
@After
public void cleanup() throws RepositoryException, RepoInitParsingException {
+ if (U.adminSession != null) {
+ U.adminSession.refresh(false);
+ }
U.cleanupUser();
}
@@ -246,6 +254,135 @@
}
}
+ @Test
+ public void testSetAclWithHomePath() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ String userHomePath = uMgr.getAuthorizable(U.username).getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+U.username+"#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Test
+ public void testSetAclWithHomePathMultipleIds() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ String userHomePath = uMgr.getAuthorizable(U.username).getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ Group gr = uMgr.createGroup("groupId");
+ String groupHomePath = gr.getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+U.username+","+gr.getID()+"#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Test
+ public void testSetAclWithHomePathMultiplePath() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ String userHomePath = uMgr.getAuthorizable(U.username).getPath();
+
+ List<String> paths = Arrays.asList(":home:" + U.username + "#", ":repository", PathUtils.ROOT_PATH);
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_ALL), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_ALL}, true);
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, null), U.username, new String[] {Privilege.JCR_ALL}, true);
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, PathUtils.ROOT_PATH), U.username, new String[] {Privilege.JCR_ALL}, true);
+ }
+
+ @Test
+ public void testSetAclWithHomePathAndSubtree() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ String userHomePath = U.adminSession.getNode(uMgr.getAuthorizable(U.username).getPath()).addNode("profiles").addNode("private").getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ Group gr = uMgr.createGroup("groupId");
+ String groupHomePath = U.adminSession.getNode(gr.getPath()).addNode("profiles").addNode("private").getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+U.username+","+gr.getID()+"#/profiles/private");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Test(expected = PathNotFoundException.class)
+ public void testSetAclWithHomePathAndMissingSubtree() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+ String userHomePath = uMgr.getAuthorizable(U.username).getPath() + "/profiles/private";
+ assertFalse(U.adminSession.nodeExists(userHomePath));
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, userHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ Group gr = uMgr.createGroup("groupId");
+ String groupHomePath = U.adminSession.getNode(gr.getPath()).addNode("profiles").addNode("private").getPath();
+
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+U.username+","+gr.getID()+"#/profiles/private");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+ }
+
+ @Test
+ public void testSetAclWithHomePathIdWithHash() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+
+ Group gr = uMgr.createGroup("g#roupId#");
+ String groupHomePath = gr.getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+gr.getID()+","+U.username+"#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Test
+ public void testSetAclWithHomePathIdWithColon() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+
+ Group gr = uMgr.createGroup(":group:Id");
+ String groupHomePath = gr.getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+gr.getID()+","+U.username+"#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Ignore("TODO: user/groupId containing , will fail ac setup")
+ @Test
+ public void testSetAclWithHomePathIdWithComma() throws Exception {
+ UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
+
+ Group gr = uMgr.createGroup(",group,Id,");
+ String groupHomePath = gr.getPath();
+ assertIsNotContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+
+ List<String> paths = Collections.singletonList(":home:"+gr.getID()+","+U.username+"#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+
+ assertIsContained(AccessControlUtils.getAccessControlList(U.adminSession, groupHomePath), U.username, new String[] {Privilege.JCR_READ}, true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSetAclWithHomePathMissingTrailingHash() throws Exception {
+ List<String> paths = Collections.singletonList(":home:"+U.username);
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+ }
+
+ @Test(expected = PathNotFoundException.class)
+ public void testSetAclWithHomePathUnknownUser() throws Exception {
+ List<String> paths = Collections.singletonList(":home:alice#");
+ AclUtil.setAcl(U.adminSession, Collections.singletonList(U.username), paths, Collections.singletonList(Privilege.JCR_READ), true);
+ }
+
private void assertIsContained(JackrabbitAccessControlList acl, String username, String[] privilegeNames, boolean isAllow) throws RepositoryException {
assertIsContained0(acl, username, privilegeNames, isAllow, true);
}