Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPattern.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPattern.java (revision 1880831) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPattern.java (date 1597842395000) @@ -42,8 +42,8 @@ *
  * NodePath     |   Restriction   |   Matches
  * -----------------------------------------------------------------------------
- * /foo         |   null          |   matches /foo and all children of /foo
- * /foo         |   ""            |   matches /foo only
+ * /foo         |   null          |   matches /foo and all descendants of /foo
+ * /foo         |   ""            |   matches /foo only (no descendants, not even properties)
  * 
*

* @@ -53,10 +53,10 @@ * NodePath = "/foo" * Restriction | Matches * ----------------------------------------------------------------------------- - * /cat | the node /foo/cat and all it's children - * /cat/ | the descendants of the node /foo/cat - * cat | the node /foocat and all it's children - * cat/ | all descendants of the node /foocat + * /cat | '/foo/cat' and all it's descendants + * /cat/ | all descendants of '/foo/cat' + * cat | '/foocat' and all it's descendants + * cat/ | all descendants of '/foocat' * *

* @@ -67,7 +67,7 @@ * Restriction | Matches * ----------------------------------------------------------------------------- * * | foo, all siblings of foo and their descendants - * /*cat | all children of /foo whose path ends with "cat" + * /*cat | all descendants of /foo whose path ends with "cat" * /*/cat | all non-direct descendants of /foo named "cat" * /cat* | all descendant path of /foo that have the direct foo-descendant segment starting with "cat" * *cat | all siblings and descendants of foo that have a name ending with cat Index: oak-doc/src/site/markdown/security/authorization/restriction.md IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-doc/src/site/markdown/security/authorization/restriction.md (revision 1880831) +++ oak-doc/src/site/markdown/security/authorization/restriction.md (date 1597842485000) @@ -130,15 +130,15 @@ | rep:glob | Result | |---------------|----------------------------------------------------------| -| null | i.e. no restriction: matches /foo and all children | -| "" | matches node /foo only | +| null | i.e. no restriction: matches /foo and all descendants | +| "" | matches node /foo only (no descendants, not even properties) | Examples including wildcard char: | rep:glob | Result | |---------------|----------------------------------------------------------| | \* | foo, siblings of foo and their descendants | -| /\*cat | all child items of /foo whose paths end with 'cat' | +| /\*cat | all descendants of /foo whose paths end with 'cat' | | \*cat | all siblings and descendants of foo that have a name ending with 'cat' | | /\*/cat | all non-direct descendants of /foo named 'cat' | | /cat\* | all descendant of /foo that have the direct foo-descendant segment starting with 'cat' | @@ -151,10 +151,10 @@ | rep:glob | Result | |---------------|----------------------------------------------------------| -| /cat | the node /foo/cat and all it's child items | -| /cat/ | the descendants of the node /foo/cat | -| cat | the node /foocat and all it's child items | -| cat/ | all descendants of the node /foocat | +| /cat | '/foo/cat' and all it's descendants | +| /cat/ | all descendants of '/foo/cat' | +| cat | '/foocat' and all it's descendants | +| cat/ | all descendants of '/foocat' | See also [GlobPattern] for implementation details. @@ -194,6 +194,7 @@ - implement `RestrictionProvider` interface exposing your custom restriction(s). - make the provider implementation an OSGi service and make it available to the Oak repository. +- make sure the `RestrictionProvider` is listed as required service with the `SecurityProvider` (see also [Introduction](../introduction.html#configuration])) Please make sure to consider the following recommendations when implementing a custom `RestrictionProvider`: - restrictions are part of the overall permission evaluation and thus may heavily impact overall read/write performance Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/ReadWithGlobRestrictionTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/ReadWithGlobRestrictionTest.java (revision 1880831) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/ReadWithGlobRestrictionTest.java (date 1597828465000) @@ -20,16 +20,29 @@ import java.util.HashSet; import java.util.Set; import javax.jcr.Node; +import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.security.Privilege; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.oak.commons.PathUtils; import org.junit.Test; public class ReadWithGlobRestrictionTest extends AbstractEvaluationTest { + private String ccPath; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Node grandchild = superuser.getNode(childNPath).addNode("child"); + ccPath = grandchild.getPath(); + superuser.save(); + } + @Test public void testGlobRestriction() throws Exception { deny(path, readPrivileges, createGlobRestriction("*/" + jcrPrimaryType)); @@ -64,7 +77,7 @@ allow(path, group2.getPrincipal(), readPrivs); deny(path, group3.getPrincipal(), readPrivs); - Set principals = new HashSet(); + Set principals = new HashSet<>(); principals.add(getTestGroup().getPrincipal()); principals.add(group2.getPrincipal()); principals.add(group3.getPrincipal()); @@ -155,11 +168,7 @@ * @see OAK-2412 */ @Test - public void testEmptyGlobRestriction() throws Exception{ - Node grandchild = superuser.getNode(childNPath).addNode("child"); - String ccPath = grandchild.getPath(); - superuser.save(); - + public void testEmptyGlobRestriction() throws Exception { // first deny access to 'path' (read-access is granted in the test setup) deny(path, readPrivileges); assertFalse(canReadNode(testSession, path)); @@ -184,10 +193,6 @@ */ @Test public void testEmptyGlobRestriction2() throws Exception{ - Node grandchild = superuser.getNode(childNPath).addNode("child"); - String ccPath = grandchild.getPath(); - superuser.save(); - // first deny access to 'path' (read-access is granted in the test setup) deny(path, readPrivileges); assertFalse(canReadNode(testSession, path)); @@ -268,4 +273,34 @@ assertTrue(canReadNode(testSession, n121.getPath())); assertTrue(canReadNode(testSession, n122.getPath())); } + + /** + * OAK-9179 + */ + @Test + public void testGlobTrailingSlash() throws Exception { + // first deny access to 'path' (read-access is granted in the test setup) + deny(path, readPrivileges); + allow(path, readPrivileges, createGlobRestriction("/"+PathUtils.getName(childNPath) + "/")); + assertGlobTrailingSlashEffect(); + } + + /** + * OAK-9179 + */ + @Test + public void testGlobTrailingSlashWildcard() throws Exception { + // first deny access to 'path' (read-access is granted in the test setup) + deny(path, readPrivileges); + allow(path, readPrivileges, createGlobRestriction("/"+PathUtils.getName(childNPath) + "/*")); + assertGlobTrailingSlashEffect(); + } + + private void assertGlobTrailingSlashEffect() throws RepositoryException { + assertFalse(canReadNode(testSession, path)); + assertFalse(canReadNode(testSession, path+"/")); + assertFalse(canReadNode(testSession, childNPath)); + assertTrue(canReadNode(testSession, ccPath)); + assertTrue(testSession.propertyExists(childchildPPath)); + } } \ No newline at end of file Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/WriteRestrictionTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/WriteRestrictionTest.java (revision 1880831) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/WriteRestrictionTest.java (date 1597830389000) @@ -18,14 +18,19 @@ import javax.jcr.AccessDeniedException; import javax.jcr.Node; +import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.security.Privilege; -import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants; +import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.test.api.util.Text; import org.junit.Before; import org.junit.Test; +import static javax.jcr.security.Privilege.JCR_ADD_CHILD_NODES; +import static javax.jcr.security.Privilege.JCR_REMOVE_CHILD_NODES; +import static javax.jcr.security.Privilege.JCR_REMOVE_NODE; + /** * WriteRestrictionTest: tests add and remove node in combination with glob restrictions. */ @@ -68,8 +73,8 @@ @Test public void testGlobRestriction2() throws Exception { - Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES); - Privilege[] rmNode = privilegesFromName(Privilege.JCR_REMOVE_NODE); + Privilege[] addNode = privilegesFromName(JCR_ADD_CHILD_NODES); + Privilege[] rmNode = privilegesFromName(JCR_REMOVE_NODE); // permissions defined @ path // restriction: grants write-priv to nodeName3 grand-children but not direct nodeName3 children. @@ -84,7 +89,7 @@ @Test public void testGlobRestriction3() throws Exception { - Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES); + Privilege[] addNode = privilegesFromName(JCR_ADD_CHILD_NODES); // permissions defined @ path // restriction: allows write to nodeName3 children @@ -104,7 +109,7 @@ @Test public void testGlobRestriction4() throws Exception { - Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES); + Privilege[] addNode = privilegesFromName(JCR_ADD_CHILD_NODES); allow(path, repWritePrivileges, createGlobRestriction("/*"+nodeName3)); deny(childNPath2, addNode); @@ -122,7 +127,7 @@ /* allow READ/WRITE privilege for testUser at 'path' */ allow(path, testUser.getPrincipal(), readWritePrivileges); /* deny REMOVE_NODE privileges at subtree. */ - deny(path, privilegesFromName(PrivilegeConstants.JCR_REMOVE_NODE), createGlobRestriction("*/" + nodeName3)); + deny(path, privilegesFromName(JCR_REMOVE_NODE), createGlobRestriction("*/" + nodeName3)); testSession.getNode(childNPath).getNode(nodeName3).remove(); try { @@ -138,7 +143,7 @@ /* allow READ/WRITE privilege for testUser at 'path' */ allow(path, testUser.getPrincipal(), readWritePrivileges); /* deny REMOVE_NODE privileges at subtree. */ - deny(path, privilegesFromName(PrivilegeConstants.JCR_REMOVE_CHILD_NODES), createGlobRestriction("*/" + Text.getName(childNPath))); + deny(path, privilegesFromName(JCR_REMOVE_CHILD_NODES), createGlobRestriction("*/" + Text.getName(childNPath))); testSession.getNode(childNPath).getNode(nodeName3).remove(); try { @@ -154,7 +159,7 @@ /* allow READ/WRITE privilege for testUser at 'path' */ allow(path, testUser.getPrincipal(), readWritePrivileges); /* deny ADD_CHILD_NODES privileges at subtree. */ - deny(path, privilegesFromName(PrivilegeConstants.JCR_ADD_CHILD_NODES), createGlobRestriction("*/"+nodeName3)); + deny(path, privilegesFromName(JCR_ADD_CHILD_NODES), createGlobRestriction("*/"+nodeName3)); Node node4 = testSession.getNode(nodePath3).addNode(nodeName4); try { @@ -164,4 +169,43 @@ // success } } + + /** + * OAK-9179 + */ + @Test + public void testGlobTrailingSlash() throws Exception { + Node grandchild = superuser.getNode(childNPath).addNode("child"); + String ccPath = grandchild.getPath(); + + // allow ADD_CHILD_NODES privileges at '/cat/' -> matches descendants of childNPath + allow(path, privilegesFromName(JCR_ADD_CHILD_NODES), createGlobRestriction("/"+PathUtils.getName(childNPath) + "/")); + assertGlobTrailingSlashEffect(ccPath); + } + + /** + * OAK-9179 + */ + @Test + public void testGlobTrailingSlashWildcard() throws Exception { + Node grandchild = superuser.getNode(childNPath).addNode("grandchild"); + String ccPath = grandchild.getPath(); + + // allow ADD_CHILD_NODES privileges at '/cat/*' -> matches descendants of childNPath + allow(path, privilegesFromName(JCR_ADD_CHILD_NODES), createGlobRestriction("/"+PathUtils.getName(childNPath) + "/*")); + assertGlobTrailingSlashEffect(ccPath); + } + + private void assertGlobTrailingSlashEffect(String ccPath) throws RepositoryException { + assertFalse(testSession.hasPermission(path, Session.ACTION_ADD_NODE)); + assertFalse(testSession.hasPermission(childNPath, Session.ACTION_ADD_NODE)); + assertFalse(testSession.hasPermission(childNPath+"/", Session.ACTION_ADD_NODE)); + assertFalse(testSession.hasPermission(ccPath, Session.ACTION_ADD_NODE)); + assertFalse(testSession.hasPermission(ccPath+"/", Session.ACTION_ADD_NODE)); + assertTrue(testSession.hasPermission(ccPath+"/greatgrandchild", Session.ACTION_ADD_NODE)); + assertTrue(testSession.hasPermission(ccPath+"/greatgrandchild/", Session.ACTION_ADD_NODE)); + assertTrue(testSession.hasPermission(ccPath+"/greatgrandchild/descendant", Session.ACTION_ADD_NODE)); + testSession.getNode(ccPath).addNode("greatgrandchild").addNode("descendant"); + testSession.save(); + } } \ No newline at end of file