From c63f4420ca526019e473917ddc8f60b780d3f1d7 Mon Sep 17 00:00:00 2001 From: Nitin Nizhawan Date: Tue, 16 May 2017 16:31:56 +0530 Subject: [PATCH] SLING-6422, Allow for specifying oak restrictions with repoinit - Interpret parsed restriction clauses from repoinit --- bundles/jcr/repoinit/pom.xml | 2 +- .../sling/jcr/repoinit/impl/AclUtil.java | 139 +++++++++++- .../sling/jcr/repoinit/impl/AclVisitor.java | 4 +- .../sling/jcr/repoinit/GeneralAclTest.java | 206 ++++++++++++++++-- 4 files changed, 327 insertions(+), 24 deletions(-) diff --git a/bundles/jcr/repoinit/pom.xml b/bundles/jcr/repoinit/pom.xml index fd248172ad..b614f1a6d9 100644 --- a/bundles/jcr/repoinit/pom.xml +++ b/bundles/jcr/repoinit/pom.xml @@ -152,7 +152,7 @@ org.apache.sling org.apache.sling.repoinit.parser - 1.1.0 + 1.1.1-SNAPSHOT provided diff --git a/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java b/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java index 7cd5a5154a..79e4f0a5e9 100644 --- a/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java +++ b/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclUtil.java @@ -18,14 +18,18 @@ import java.security.Principal; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.Value; +import javax.jcr.ValueFactory; import javax.jcr.security.AccessControlEntry; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; @@ -35,7 +39,11 @@ import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; + import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; + +import org.apache.sling.repoinit.parser.operations.RestrictionClause; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,8 +51,7 @@ public class AclUtil { private static final Logger LOG = LoggerFactory.getLogger(AclUtil.class); - - public static JackrabbitAccessControlManager getJACM(Session s) throws UnsupportedRepositoryOperationException, RepositoryException { + public static JackrabbitAccessControlManager getJACM(Session s) throws RepositoryException { final AccessControlManager acm = s.getAccessControlManager(); if(!(acm instanceof JackrabbitAccessControlManager)) { throw new IllegalStateException( @@ -54,8 +61,49 @@ public static JackrabbitAccessControlManager getJACM(Session s) throws Unsupport return (JackrabbitAccessControlManager) acm; } + /** + * Converts RestrictionClauses to structure consumable by + * jackrabbit + * @param list + * @param jacl + * @param s + * @return + * @throws RepositoryException + */ + private static LocalRestrictions createLocalRestrictions(List list, JackrabbitAccessControlList jacl, Session s) throws RepositoryException { + Map restrictions = new HashMap(); + Map mvrestrictions = new HashMap(); + + if(list != null && !list.isEmpty()){ + ValueFactory vf = s.getValueFactory(); + + for(RestrictionClause rc : list){ + String restrictionName = rc.getName(); + int type = jacl.getRestrictionType(restrictionName); + Value[] values = new Value[rc.getValues().size()]; + for(int i=0;i principals, List paths, List privileges, boolean isAllow) - throws UnsupportedRepositoryOperationException, RepositoryException { + throws RepositoryException { + setAcl(session,principals,paths,privileges,isAllow,Arrays.asList(new RestrictionClause[]{})); + } + + public static void setAcl(Session session, List principals, List paths, List privileges, boolean isAllow, List restrictionClauses) + throws RepositoryException { final String [] privArray = privileges.toArray(new String[privileges.size()]); final Privilege[] jcrPriv = AccessControlUtils.privilegesFromNames(session, privArray); @@ -64,8 +112,13 @@ public static void setAcl(Session session, List principals, List if(!session.nodeExists(path)) { throw new PathNotFoundException("Cannot set ACL on non-existent path " + path); } + JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(session, path); + + LocalRestrictions localRestrictions = createLocalRestrictions(restrictionClauses, acl, session); + AccessControlEntry[] existingAces = acl.getAccessControlEntries(); + boolean changed = false; for (String name : principals) { final Principal principal; @@ -81,12 +134,13 @@ public static void setAcl(Session session, List principals, List if (principal == null) { throw new IllegalStateException("Principal not found: " + name); } - LocalAccessControlEntry newAce = new LocalAccessControlEntry(principal, jcrPriv, isAllow); + 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); continue; } - acl.addEntry(newAce.principal, newAce.privileges, newAce.isAllow); + acl.addEntry(newAce.principal, newAce.privileges, newAce.isAllow, + newAce.restrictions.getRestrictions(), newAce.restrictions.getMVRestrictions()); changed = true; } if ( changed ) { @@ -122,21 +176,59 @@ static class LocalAccessControlEntry { private final Principal principal; private final Privilege[] privileges; private final boolean isAllow; - + private final LocalRestrictions restrictions; LocalAccessControlEntry(Principal principal, Privilege[] privileges, boolean isAllow) { + this(principal, privileges, isAllow, null); + } + + LocalAccessControlEntry(Principal principal, Privilege[] privileges, + boolean isAllow, LocalRestrictions restrictions) { this.principal = principal; this.privileges = privileges; this.isAllow = isAllow; + this.restrictions = restrictions != null ? restrictions : new LocalRestrictions(); } public boolean isContainedIn(JackrabbitAccessControlEntry other) throws RepositoryException { return other.getPrincipal().equals(principal) && contains(other.getPrivileges(), privileges) && other.isAllow() == isAllow && - ( other.getRestrictionNames() == null || - other.getRestrictionNames().length == 0 ); + sameRestrictions(other); } - + + /** + * compares if restrictions present in jackrabbit access control entry + * is same as specified restrictions in repo init + * @param jace + * @return + * @throws RepositoryException + */ + private boolean sameRestrictions(JackrabbitAccessControlEntry jace) throws RepositoryException { + // total (multivalue and simple) number of restrictions should be same + if(jace.getRestrictionNames().length == (restrictions.size())){ + for(String rn : jace.getRestrictionNames()){ + Value[] oldValues = jace.getRestrictions(rn); + Value[] newValues = restrictions.getRestrictions().get(rn) != null + ? new Value[]{restrictions.getRestrictions().get(rn)} + : restrictions.getMVRestrictions().get(rn); + if((newValues == null || newValues.length == 0) && (oldValues == null || oldValues.length == 0)){ + continue; + } + + if(newValues.length != oldValues.length){ + return false; + } + for(int i=0;i set1 = new HashSet(); @@ -153,4 +245,33 @@ public String toString() { return "[" + getClass().getSimpleName() + "# principal " + principal+ ", privileges: " + Arrays.toString(privileges) + ", isAllow : " + isAllow + "]"; } } + + /** + * Helper class to store both restrictions and multi value restrictions + * in ready to consume structure expected by jackrabbit + */ + private static class LocalRestrictions { + private Map restrictions; + private Map mvRestrictions; + public LocalRestrictions(){ + restrictions = new HashMap<>(); + mvRestrictions = new HashMap<>(); + } + public LocalRestrictions(Map restrictions,Map mvRestrictions){ + this.restrictions = restrictions != null ? restrictions : new HashMap(); + this.mvRestrictions = mvRestrictions != null ? mvRestrictions : new HashMap(); + } + + public Map getRestrictions(){ + return this.restrictions; + } + + public Map getMVRestrictions(){ + return this.mvRestrictions; + } + + public int size(){ + return this.restrictions.size() + this.mvRestrictions.size(); + } + } } diff --git a/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclVisitor.java b/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclVisitor.java index c18499512e..dbbf52ce1c 100644 --- a/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclVisitor.java +++ b/bundles/jcr/repoinit/src/main/java/org/apache/sling/jcr/repoinit/impl/AclVisitor.java @@ -28,6 +28,7 @@ import org.apache.sling.repoinit.parser.operations.AclLine; import org.apache.sling.repoinit.parser.operations.CreatePath; import org.apache.sling.repoinit.parser.operations.PathSegmentDefinition; +import org.apache.sling.repoinit.parser.operations.RestrictionClause; import org.apache.sling.repoinit.parser.operations.SetAclPaths; import org.apache.sling.repoinit.parser.operations.SetAclPrincipals; @@ -56,7 +57,8 @@ private List require(AclLine line, String propertyName) { private void setAcl(AclLine line, Session s, List principals, List paths, List privileges, boolean isAllow) { try { log.info("Adding ACL '{}' entry '{}' for {} on {}", isAllow ? "allow" : "deny", privileges, principals, paths); - AclUtil.setAcl(s, principals, paths, privileges, isAllow); + List restrictions = line.getRestrictions(); + AclUtil.setAcl(s, principals, paths, privileges, isAllow, restrictions); } catch(Exception e) { throw new RuntimeException("Failed to set ACL (" + e.toString() + ") " + line, e); } diff --git a/bundles/jcr/repoinit/src/test/java/org/apache/sling/jcr/repoinit/GeneralAclTest.java b/bundles/jcr/repoinit/src/test/java/org/apache/sling/jcr/repoinit/GeneralAclTest.java index e9da0293bb..5888b038b1 100644 --- a/bundles/jcr/repoinit/src/test/java/org/apache/sling/jcr/repoinit/GeneralAclTest.java +++ b/bundles/jcr/repoinit/src/test/java/org/apache/sling/jcr/repoinit/GeneralAclTest.java @@ -25,7 +25,14 @@ import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; - +import javax.jcr.Value; +import javax.jcr.ValueFormatException; +import javax.jcr.security.AccessControlEntry; + +import org.apache.jackrabbit.api.JackrabbitSession; +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.sling.jcr.repoinit.impl.TestUtil; import org.apache.sling.repoinit.parser.RepoInitParsingException; import org.apache.sling.testing.mock.sling.ResourceResolverType; @@ -36,6 +43,8 @@ import org.junit.Rule; import org.junit.Test; +import java.util.Arrays; + /** Various ACL-related tests */ public class GeneralAclTest { @@ -157,12 +166,7 @@ public void addChildAtRoot() throws Exception { private void verifyAddChildNode(String username,String nodeName, boolean successExpected) throws RepositoryException { Session userSession = U.loginService(username); try { - Node rootNode = userSession.getRootNode(); - rootNode.addNode(nodeName); - userSession.save(); - assertTrue(successExpected); - } catch(Exception e) { - assertTrue(!successExpected); + verifyAddChildNode(userSession,nodeName,successExpected); } finally { if(userSession != null) { userSession.logout(); @@ -170,6 +174,28 @@ private void verifyAddChildNode(String username,String nodeName, boolean success } } + /** + * Verifies success/failure of adding a child + */ + private void verifyAddChildNode(Session userSession, String nodeName, boolean successExpected) throws RepositoryException{ + Node rootNode = userSession.getRootNode(); + verifyAddChildNode(rootNode, nodeName, successExpected); + } + + /** + * Verifies success/failure of adding a child + */ + private void verifyAddChildNode(Node node, String nodeName, boolean successExpected) throws RepositoryException{ + try { + node.addNode(nodeName); + node.getSession().save(); + assertTrue("Added child node succeeded "+nodeName,successExpected); + } catch(Exception e) { + node.getSession().refresh(false); + assertTrue("Error adding " + nodeName + " to " + node + e.getMessage(), !successExpected); + } + } + /** * Verifies success/failure of adding a properties * @param username @@ -180,6 +206,24 @@ private void verifyAddChildNode(String username,String nodeName, boolean success */ private void verifyAddProperty(String username,String nodeName,String propertyName, boolean successExpected) throws RepositoryException { Session userSession = U.loginService(username); + try { + verifyAddProperty(userSession, nodeName, propertyName, successExpected); + } finally { + if(userSession != null) { + userSession.logout(); + } + } + } + + /** + * Verifies success/failure of adding a properties + * @param userSession + * @param nodeName + * @param propertyName + * @param successExpected + * @throws RepositoryException + */ + private void verifyAddProperty(Session userSession,String nodeName,String propertyName,boolean successExpected) throws RepositoryException{ try { Node rootNode = userSession.getRootNode(); Node node = rootNode.getNode(nodeName); @@ -187,16 +231,12 @@ private void verifyAddProperty(String username,String nodeName,String propertyNa userSession.save(); assertTrue(successExpected); } catch(Exception e) { - assertTrue(!successExpected); - } finally { - if(userSession != null) { - userSession.logout(); - } + userSession.refresh(false); // remove changes causing failure + assertTrue("Error " + e.getMessage(), !successExpected); } } - /** * Verifies that ACEs for existing principal are replaced */ @@ -400,4 +440,144 @@ public void mergePreserveMode_PreserveAceTest() throws Exception { verifyAddChildNode(userA, "A2_" + U.id, true); verifyAddChildNode(userB, "B2_" + U.id, true); } + + /** + * Tests use of glob restriction in set ACL + * @throws Exception + */ + @Test + public void globRestrictionTest() throws Exception { + + final String allowedNodeName = "testxyz_" + U.id; + final String notAllowedNodeName = "testabc_" + U.id; + + U.adminSession.getRootNode().addNode(allowedNodeName); + U.adminSession.getRootNode().addNode(notAllowedNodeName); + U.adminSession.save(); + + + final String nodeName = "test_" + U.id; + + final String aclSetup = + "set ACL for " + U.username + "\n" + + "allow jcr:all on / \n" + + "deny jcr:addChildNodes on / restriction(rep:glob,*abc*)\n" + + "end" + ; + + U.parseAndExecute(aclSetup); + + try { + verifyAddChildNode(s.getRootNode().getNode(allowedNodeName), nodeName, true); + verifyAddChildNode(s.getRootNode().getNode(notAllowedNodeName), nodeName, false); + + } finally { + s.logout(); + } + } + + + /** + * Tests use of rep:itemNames restriction in set ACL + * @throws Exception + */ + @Test + public void itemNamesRestrictionTest() throws Exception { + final String nodename = "test_" + U.id; + final String propName = "test2_" + U.id; + + final String aclSetup = + "set ACL for " + U.username + "\n" + + "allow jcr:all on / \n" + + "deny jcr:modifyProperties on / restriction(rep:itemNames,"+propName+")\n" + + "end" + ; + + U.parseAndExecute(aclSetup); + + try { + + verifyAddChildNode(s, nodename, true); + verifyAddProperty(s, nodename, "someotherproperty", true); + verifyAddProperty(s, nodename, propName, false); // adding property propName should fail + + } finally { + s.logout(); + } + } + + /** + * Tests default merging of acls with restrictions + * @throws Exception + */ + @Test + public void multiRestrictionMergeTest() throws Exception { + final String nodeName = "test_" + U.id; + final String nodeName2 = "test2_" + U.id; + final String nodeName3 = "test3_" + U.id; + final String propName = "propName" + U.id; + + + U.adminSession.getRootNode().addNode(nodeName); + U.adminSession.getRootNode().addNode(nodeName2); + U.adminSession.getRootNode().addNode(nodeName3); + U.adminSession.save(); + + + final String aclSetup = + "set ACL for " + U.username + "\n" + + "allow jcr:all on / \n" + + "deny jcr:addChildNodes on / restriction(rep:glob,"+nodeName2+")\n" + + "end" + ; + + U.parseAndExecute(aclSetup); + + final String aclSetup2 = + "set ACL for " + U.username + "\n" + + "deny jcr:addChildNodes on / restriction(rep:glob,"+nodeName3+")\n" + + "end" + ; + + U.parseAndExecute(aclSetup2); + + final String aclSetup3 = + "set ACL for " + U.username + "\n" + + "deny jcr:modifyProperties on / restriction(rep:itemNames,"+propName+")\n" + + "end" + ; + + U.parseAndExecute(aclSetup3); + + try { + + // now verify add child nodes perm + + verifyAddChildNode(s.getRootNode().getNode(nodeName), nodeName, true); + + verifyAddChildNode(s.getRootNode().getNode(nodeName2), nodeName, false); + verifyAddChildNode(s.getRootNode().getNode(nodeName3), nodeName, false); + + // verify property restriction + verifyAddProperty(s, nodeName, "someotherproperty", true); + verifyAddProperty(s, nodeName, propName, false); // adding property propName should fail + + } finally { + s.logout(); + } + } + + + @Test + public void emptyRestrictionTest() throws Exception { + final String aclSetup = + "set ACL for " + U.username + "\n" + + "allow jcr:all on / \n" + + "deny jcr:modifyProperties on / restriction(rep:itemNames)\n" + + "end" + ; + U.parseAndExecute(aclSetup); + } + + }