You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2022/02/22 20:15:00 UTC

[jackrabbit-oak] branch trunk updated: OAK-9701 : Additional restrictions to simplify permission setup

This is an automated email from the ASF dual-hosted git repository.

angela pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b368524  OAK-9701 : Additional restrictions to simplify permission setup
     new 41fbff6  Merge branch 'trunk' of https://github.com/apache/jackrabbit-oak into trunk
b368524 is described below

commit b3685246f0f54a7668e5c463d9f1c40f373b3c25
Author: angela <an...@adobe.com>
AuthorDate: Tue Feb 22 21:05:53 2022 +0100

    OAK-9701 : Additional restrictions to simplify permission setup
---
 .../jackrabbit/oak/benchmark/BenchmarkRunner.java  |   6 +
 .../authorization/AbstractHasItemGetItemTest.java  |   7 +-
 .../GetPrivilegeCollectionIncludeNamesTest.java    |  15 +-
 .../authorization/MvGlobsAndSubtreesTest.java      | 103 ++++++++++
 .../authorization/restriction/GlobsPattern.java    |  91 +++++++++
 .../restriction/RestrictionProviderImpl.java       |  85 ++++----
 .../authorization/restriction/SubtreePattern.java  | 106 ++++++++++
 .../authorization/accesscontrol/ACLTest.java       |   3 +-
 .../AccessControlManagerImplTest.java              |   2 +-
 .../authorization/restriction/GlobPatternTest.java | 128 ++++++------
 .../restriction/GlobsPatternTest.java              | 219 +++++++++++++++++++++
 .../restriction/RestrictionProviderImplTest.java   |  79 ++++++--
 .../restriction/SubtreePatternTest.java            | 160 +++++++++++++++
 .../markdown/security/authorization/restriction.md |  37 ++++
 .../accesscontrol/AccessControlConstants.java      |  35 ++++
 .../authorization/accesscontrol/package-info.java  |   2 +-
 16 files changed, 954 insertions(+), 124 deletions(-)

diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
index 5b0584e..5edd058 100644
--- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.oak.benchmark.authentication.external.SyncExternalU
 import org.apache.jackrabbit.oak.benchmark.authorization.AceCreationTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.CanReadNonExisting;
 import org.apache.jackrabbit.oak.benchmark.authorization.GetPrivilegeCollectionIncludeNamesTest;
+import org.apache.jackrabbit.oak.benchmark.authorization.MvGlobsAndSubtreesTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.SaveHasItemGetItemTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.HasPermissionHasItemGetItemTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.HasPrivilegesHasItemGetItemTest;
@@ -296,6 +297,11 @@ public class BenchmarkRunner {
                                 benchmarkOptions.getNumberOfGroups().value(options),
                                 benchmarkOptions.getReport().value(options), 
                                 benchmarkOptions.getEvalutionType().value(options)),
+                        new MvGlobsAndSubtreesTest(benchmarkOptions.getItemsToRead().value(options),
+                                benchmarkOptions.getNumberOfInitialAce().value(options),
+                                benchmarkOptions.getNumberOfGroups().value(options),
+                                benchmarkOptions.getReport().value(options),
+                                benchmarkOptions.getEvalutionType().value(options)),
                         new ConcurrentReadDeepTreeTest(
                                 benchmarkOptions.getRunAsAdmin().value(options),
                                 benchmarkOptions.getItemsToRead().value(options),
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/AbstractHasItemGetItemTest.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/AbstractHasItemGetItemTest.java
index ea7ee2e..448866e 100644
--- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/AbstractHasItemGetItemTest.java
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/AbstractHasItemGetItemTest.java
@@ -94,12 +94,17 @@ abstract class AbstractHasItemGetItemTest extends ReadDeepTreeTest {
             int cnt = 0;
             int targetCnt = (principal.getName().equals(pGrantedRead.getName())) ? numberOfACEs-1 : numberOfACEs;
             while (cnt < targetCnt) {
-                if (Utils.addEntry(acMgr, principal, getRandom(nodePaths), (Privilege[]) Utils.getRandom(allPrivileges, 3).toArray(new Privilege[0]))) {
+                if (createEntry(acMgr, principal, getRandom(nodePaths), (Privilege[]) Utils.getRandom(allPrivileges, 3).toArray(new Privilege[0]))) {
                     cnt++;
                 }
             }
         }
     }
+    
+    boolean createEntry(@NotNull JackrabbitAccessControlManager acMgr, @NotNull Principal principal, @NotNull String path, 
+                        @NotNull Privilege[] privileges) throws RepositoryException {
+        return Utils.addEntry(acMgr, principal, path, privileges);
+    }
 
     @Override
     protected void afterSuite() throws Exception {
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java
index ebbfb43..70b774a 100644
--- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -49,7 +50,19 @@ public class GetPrivilegeCollectionIncludeNamesTest extends AbstractHasItemGetIt
     
     public GetPrivilegeCollectionIncludeNamesTest(int itemsToRead, int numberOfACEs, int numberOfGroups, boolean doReport, String evalType) {
         super(itemsToRead, numberOfACEs, numberOfGroups, doReport);
-        this.evalType = (Strings.isNullOrEmpty(evalType)) ? EvaluationType.ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION : EvaluationType.valueOf(evalType);
+        this.evalType = getEvalType(evalType);
+    }
+    
+    @NotNull
+    private static EvaluationType getEvalType(@Nullable String type) {
+        if (Strings.isNullOrEmpty(type)) {
+            return EvaluationType.ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION;
+        }
+        try {
+            return EvaluationType.valueOf(type);
+        } catch (IllegalArgumentException e) {
+            return EvaluationType.ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION;
+        }
     }
 
     @Override
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/MvGlobsAndSubtreesTest.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/MvGlobsAndSubtreesTest.java
new file mode 100644
index 0000000..41be9a9
--- /dev/null
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/MvGlobsAndSubtreesTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.oak.benchmark.authorization;
+
+import joptsimple.internal.Strings;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.security.Privilege;
+import java.security.Principal;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.REP_GLOB;
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.REP_GLOBS;
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.REP_SUBTREES;
+
+public class MvGlobsAndSubtreesTest extends HasPermissionHasItemGetItemTest {
+
+    private enum RestrictionType {
+        REP_GLOB,
+        REP_GLOBS,
+        REP_SUBTREES
+    }
+    
+    private static final String GLOB1 = "*/jcr:content";
+    private static final String GLOB2 = "*/jcr:content/*";
+    private static final String SUBTREE = "/jcr:content";
+    
+    private final RestrictionType restrictionType;
+
+    public MvGlobsAndSubtreesTest(int itemsToRead, int numberOfACEs, int numberOfGroups, boolean doReport, String restrictionType) {
+        super(itemsToRead, numberOfACEs, numberOfGroups, doReport);
+
+        this.restrictionType = getRestrictionType(restrictionType);
+    }
+
+    @NotNull
+    private static RestrictionType getRestrictionType(@Nullable String type) {
+        if (Strings.isNullOrEmpty(type)) {
+            return RestrictionType.REP_SUBTREES;
+        }
+        try {
+            return RestrictionType.valueOf(type);
+        } catch (IllegalArgumentException e) {
+            return RestrictionType.REP_SUBTREES;
+        }
+    }
+
+    @Override
+    @NotNull String additionalMethodName() {
+        return super.additionalMethodName() + " with ac setup including restriction '"+restrictionType+"')";
+    }
+
+    @Override
+    boolean createEntry(@NotNull JackrabbitAccessControlManager acMgr, @NotNull Principal principal, @NotNull String path,
+                        @NotNull Privilege[] privileges) throws RepositoryException {
+        JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, path);
+        if (acl == null) {
+            throw new IllegalStateException("No policy to setup ACE.");
+        }
+        
+        ValueFactory vf = adminSession.getValueFactory();
+        
+        boolean added;
+        if (restrictionType == RestrictionType.REP_GLOB) {
+            added = (acl.addEntry(principal, privileges, true, singletonMap(REP_GLOB, vf.createValue(GLOB1)), emptyMap()) ||
+                     acl.addEntry(principal, privileges, true, singletonMap(REP_GLOB, vf.createValue(GLOB2)), emptyMap()));
+        } else if (restrictionType == RestrictionType.REP_GLOBS) {
+            added = acl.addEntry(principal, privileges, true, emptyMap(), 
+                    singletonMap(REP_GLOBS, new Value[] {vf.createValue(GLOB1), vf.createValue(GLOB2)}));
+        } else {
+            // rep:subtrees
+            added = acl.addEntry(principal, privileges, true, emptyMap(),
+                    singletonMap(REP_SUBTREES, new Value[] {vf.createValue(SUBTREE)}));
+        }
+        if (added) {
+            acMgr.setPolicy(acl.getPath(), acl);
+        }
+        return added;
+    }
+
+}
\ No newline at end of file
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPattern.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPattern.java
new file mode 100644
index 0000000..e4e0519
--- /dev/null
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPattern.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.oak.security.authorization.restriction;
+
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * <p>Multi-valued variant of the {@link GlobPattern} that will match a given path (or tree/property) if any of the 
+ * contained patterns matches. This is equivalent to creating multiple access control entries with a single rep:glob
+ * restrictions each.</p>
+ * 
+ * <p>NOTE: An empty value array will not match any path/item</p>
+ * <p>NOTE: Currently the pattern keeps a list of {@link GlobPattern} and doesn't attempt to optimize the evaluation.</p>
+ * 
+ * @see GlobPattern GlobPattern for details
+ */
+class GlobsPattern implements RestrictionPattern {
+    
+    private final GlobPattern[] patterns;
+
+    GlobsPattern(@NotNull String path, @NotNull Iterable<String> restrictions)  {
+        ArrayList<GlobPattern> l = new ArrayList<>(Iterables.size(restrictions));
+        restrictions.forEach(restriction -> {
+            if (restriction != null) {
+                l.add(GlobPattern.create(path, restriction));
+            }
+        });
+        patterns = l.toArray(new GlobPattern[0]);
+    }
+
+    @Override
+    public boolean matches(@NotNull Tree tree, @Nullable PropertyState property) {
+        String itemPath = (property == null) ? tree.getPath() : PathUtils.concat(tree.getPath(), property.getName());
+        return matches(itemPath);
+    }
+
+    @Override
+    public boolean matches(@NotNull String path) {
+        for (GlobPattern gp : patterns) {
+            if (gp.matches(path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean matches() {
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(patterns);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof GlobsPattern) {
+            GlobsPattern other = (GlobsPattern) obj;
+            return Arrays.equals(patterns, other.patterns);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java
index 1147f80..29c125a 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java
@@ -63,21 +63,18 @@ import org.slf4j.LoggerFactory;
 public class RestrictionProviderImpl extends AbstractRestrictionProvider {
 
     private static final Logger log = LoggerFactory.getLogger(RestrictionProviderImpl.class);
-
-    private static final int NUMBER_OF_DEFINITIONS = 5;
+    
+    private static final Map<String, RestrictionDefinition> DEFINITIONS = ImmutableMap.<String, RestrictionDefinition>builder()
+            .put(REP_GLOB, new RestrictionDefinitionImpl(REP_GLOB, Type.STRING, false))
+            .put(REP_NT_NAMES, new RestrictionDefinitionImpl(REP_NT_NAMES, Type.NAMES, false))
+            .put(REP_PREFIXES, new RestrictionDefinitionImpl(REP_PREFIXES, Type.STRINGS, false))
+            .put(REP_ITEM_NAMES, new RestrictionDefinitionImpl(REP_ITEM_NAMES, Type.NAMES, false))
+            .put(REP_CURRENT, new RestrictionDefinitionImpl(REP_CURRENT, Type.STRINGS, false))
+            .put(REP_GLOBS, new RestrictionDefinitionImpl(REP_GLOBS, Type.STRINGS, false))
+            .put(REP_SUBTREES, new RestrictionDefinitionImpl(REP_SUBTREES, Type.STRINGS, false)).build();
 
     public RestrictionProviderImpl() {
-        super(supportedRestrictions());
-    }
-
-    @NotNull
-    private static Map<String, RestrictionDefinition> supportedRestrictions() {
-        RestrictionDefinition glob = new RestrictionDefinitionImpl(REP_GLOB, Type.STRING, false);
-        RestrictionDefinition nts = new RestrictionDefinitionImpl(REP_NT_NAMES, Type.NAMES, false);
-        RestrictionDefinition pfxs = new RestrictionDefinitionImpl(REP_PREFIXES, Type.STRINGS, false);
-        RestrictionDefinition names = new RestrictionDefinitionImpl(REP_ITEM_NAMES, Type.NAMES, false);
-        RestrictionDefinition current = new RestrictionDefinitionImpl(REP_CURRENT, Type.STRINGS, false);
-        return ImmutableMap.of(glob.getName(), glob, nts.getName(), nts, pfxs.getName(), pfxs, names.getName(), names, current.getName(), current);
+        super(DEFINITIONS);
     }
 
     //------------------------------------------------< RestrictionProvider >---
@@ -88,31 +85,41 @@ public class RestrictionProviderImpl extends AbstractRestrictionProvider {
         if (oakPath == null) {
             return RestrictionPattern.EMPTY;
         } else {
-            List<RestrictionPattern> patterns = new ArrayList<>(NUMBER_OF_DEFINITIONS);
+            List<RestrictionPattern> patterns = new ArrayList<>(DEFINITIONS.size());
             PropertyState glob = tree.getProperty(REP_GLOB);
             if (glob != null) {
                 patterns.add(GlobPattern.create(oakPath, glob.getValue(Type.STRING)));
             }
-            PropertyState ntNames = tree.getProperty(REP_NT_NAMES);
-            if (ntNames != null) {
-                patterns.add(new NodeTypePattern(ntNames.getValue(Type.NAMES)));
-            }
-            PropertyState prefixes = tree.getProperty(REP_PREFIXES);
-            if (prefixes != null) {
-                patterns.add(new PrefixPattern(prefixes.getValue(Type.STRINGS)));
-            }
-            PropertyState itemNames = tree.getProperty(REP_ITEM_NAMES);
-            if (itemNames != null) {
-                patterns.add(new ItemNamePattern(itemNames.getValue(Type.NAMES)));
+            for (String name : new String[] {REP_NT_NAMES, REP_ITEM_NAMES}) {
+                PropertyState ps = tree.getProperty(name);
+                if (ps != null) {
+                    patterns.add(createPattern(oakPath, name, ps.getValue(Type.NAMES)));
+                }
             }
-            PropertyState current = tree.getProperty(REP_CURRENT);
-            if (current != null) {
-                patterns.add(new CurrentPattern(oakPath, current.getValue(Type.STRINGS)));
+            for (String name : new String[] {REP_PREFIXES, REP_CURRENT, REP_GLOBS, REP_SUBTREES}) {
+                PropertyState ps = tree.getProperty(name);
+                if (ps != null) {
+                    patterns.add(createPattern(oakPath, name, ps.getValue(Type.STRINGS)));
+                }
             }
-
             return CompositePattern.create(patterns);
         }
     }
+    
+    private static RestrictionPattern createPattern(@NotNull String path, @NotNull String name, @NotNull Iterable<String> values) {
+        switch (name) {
+            case REP_ITEM_NAMES: return new ItemNamePattern(values);
+            case REP_NT_NAMES: return new NodeTypePattern(values);
+            case REP_PREFIXES: return new PrefixPattern(values);
+            case REP_CURRENT:  return new CurrentPattern(path, values);
+            case REP_GLOBS:    return new GlobsPattern(path, values);
+            case REP_SUBTREES: return new SubtreePattern(path, values);
+            default: {
+                log.debug("Ignoring unsupported restriction {}", name);
+                return RestrictionPattern.EMPTY;
+            }
+        }
+    }
 
     @NotNull
     @Override
@@ -120,21 +127,15 @@ public class RestrictionProviderImpl extends AbstractRestrictionProvider {
         if (oakPath == null || restrictions.isEmpty()) {
             return RestrictionPattern.EMPTY;
         } else {
-            List<RestrictionPattern> patterns = new ArrayList<>(NUMBER_OF_DEFINITIONS);
+            List<RestrictionPattern> patterns = new ArrayList<>(DEFINITIONS.size());
             for (Restriction r : restrictions) {
                 String name = r.getDefinition().getName();
                 if (REP_GLOB.equals(name)) {
                     patterns.add(GlobPattern.create(oakPath, r.getProperty().getValue(Type.STRING)));
-                } else if (REP_NT_NAMES.equals(name)) {
-                    patterns.add(new NodeTypePattern(r.getProperty().getValue(Type.NAMES)));
-                } else if (REP_PREFIXES.equals(name)) {
-                    patterns.add(new PrefixPattern(r.getProperty().getValue(Type.STRINGS)));
-                } else if (REP_ITEM_NAMES.equals(name)) {
-                    patterns.add(new ItemNamePattern(r.getProperty().getValue(Type.NAMES)));
-                } else if (REP_CURRENT.equals(name)) {
-                    patterns.add(new CurrentPattern(oakPath, r.getProperty().getValue(Type.STRINGS)));
+                } else if (REP_NT_NAMES.equals(name) || REP_ITEM_NAMES.equals(name)) {
+                    patterns.add(createPattern(oakPath, name, r.getProperty().getValue(Type.NAMES)));
                 } else {
-                    log.debug("Ignoring unsupported restriction {}", name);
+                    patterns.add(createPattern(oakPath, name, r.getProperty().getValue(Type.STRINGS)));
                 }
             }
             return CompositePattern.create(patterns);
@@ -150,5 +151,11 @@ public class RestrictionProviderImpl extends AbstractRestrictionProvider {
         if (glob != null) {
             GlobPattern.validate(glob.getValue(Type.STRING));
         }
+        PropertyState globs = restrictionsTree.getProperty(REP_GLOBS);
+        if (globs != null) {
+            for (String v : globs.getValue(Type.STRINGS)) {
+                GlobPattern.validate(v);
+            }
+        }
     }
 }
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePattern.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePattern.java
new file mode 100644
index 0000000..06d7cfd
--- /dev/null
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePattern.java
@@ -0,0 +1,106 @@
+/*
+ * 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.oak.security.authorization.restriction;
+
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+class SubtreePattern implements RestrictionPattern {
+    
+    private static final String SEGMENT_DELIM = "/";
+    
+    private final String oakPath;
+    private final int oakPathLength;
+    private final String[] targets;
+    private final String[] subtrees;
+    
+    SubtreePattern(@NotNull String oakPath, @NotNull Iterable<String> subtrees) {
+        this.oakPath = oakPath;
+        this.oakPathLength = oakPath.length();
+        int size = Iterables.size(subtrees);
+        List<String> tl = new ArrayList<>(size);
+        List<String> sl = new ArrayList<>(size);
+        subtrees.forEach(s -> {
+            if (s != null && !s.isEmpty()) {
+                if (s.endsWith(SEGMENT_DELIM)) {
+                    sl.add(s);
+                } else {
+                    tl.add(s);
+                    sl.add((s + SEGMENT_DELIM));
+                }
+            }
+        });
+        this.targets = tl.toArray(new String[0]);        
+        this.subtrees = sl.toArray(new String[0]);
+    }
+    
+    @Override
+    public boolean matches(@NotNull Tree tree, @Nullable PropertyState property) {
+        String path = (property == null) ? tree.getPath() : PathUtils.concat(tree.getPath(), property.getName());
+        return matches(path);
+    }
+
+    @Override
+    public boolean matches(@NotNull String path) {
+        if (path.indexOf(oakPath) != 0) {
+            return false;
+        }
+        for (String subtree : subtrees) {
+            if (path.indexOf(subtree, oakPathLength) > -1) {
+                return true;
+            }
+        }
+        for (String target : targets) {
+            if (path.endsWith(target)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean matches() {
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(oakPath, Arrays.hashCode(targets), Arrays.hashCode(subtrees));
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof SubtreePattern) {
+            SubtreePattern other = (SubtreePattern) obj;
+            return oakPath.equals(other.oakPath) && Arrays.equals(targets, other.targets) && Arrays.equals(subtrees, other.subtrees);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
index cd1d9de..3a1f5fc 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
@@ -679,8 +679,7 @@ public class ACLTest extends AbstractAccessControlTest implements PrivilegeConst
     public void testRestrictions() throws Exception {
         String[] names = acl.getRestrictionNames();
         assertNotNull(names);
-        assertEquals(5, names.length);
-        assertArrayEquals(new String[] {REP_GLOB, REP_NT_NAMES, REP_PREFIXES, REP_ITEM_NAMES, REP_CURRENT}, names);
+        assertArrayEquals(new String[] {REP_GLOB, REP_NT_NAMES, REP_PREFIXES, REP_ITEM_NAMES, REP_CURRENT, REP_GLOBS, REP_SUBTREES}, names);
         assertEquals(PropertyType.STRING, acl.getRestrictionType(names[0]));
         assertEquals(PropertyType.NAME, acl.getRestrictionType(names[1]));
         assertEquals(PropertyType.STRING, acl.getRestrictionType(names[2]));
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
index ac796c6..26e2ed2 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
@@ -1333,7 +1333,7 @@ public class AccessControlManagerImplTest extends AbstractAccessControlTest impl
     public void testSetPolicyWithRestrictions() throws Exception {
         ACL acl = TestUtility.getApplicablePolicy(acMgr, testPath);
         acl.addEntry(testPrincipal, testPrivileges, true, TestUtility.getGlobRestriction("/a/b", valueFactory));
-        acl.addEntry(testPrincipal, testPrivileges, true, TestUtility.getGlobRestriction("/c/d", valueFactory));
+        acl.addEntry(testPrincipal, testPrivileges, true, null, Collections.singletonMap(REP_GLOBS, new Value[] {valueFactory.createValue("/c/d")}));
         acMgr.setPolicy(testPath, acl);
         root.commit();
 
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPatternTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPatternTest.java
index 7b089e2..3981602 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPatternTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobPatternTest.java
@@ -22,6 +22,7 @@ import java.util.Map;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
@@ -30,16 +31,25 @@ import static org.junit.Assert.assertNotEquals;
 
 public class GlobPatternTest {
 
-    private static void assertMatch(GlobPattern gp, String testPath, Boolean expectedResult) {
+    private static void assertMatch(@NotNull GlobPattern gp, @NotNull String testPath, @NotNull Boolean expectedResult) {
         Boolean match = gp.matches(testPath);
         assertEquals("Pattern : " + gp + "; TestPath : " + testPath, expectedResult, match);
     }
-
-    @Test
-    public void testMatchesWildcardAll() {
+    
+    private static void runTests(@NotNull Map<GlobPattern, Map<String, Boolean>> m) {
+        for (Map.Entry<GlobPattern, Map<String, Boolean>> entry : m.entrySet()) {
+            GlobPattern gp = entry.getKey();
+            Map<String, Boolean> tests = entry.getValue();
+            for (String testPath : tests.keySet()) {
+                assertMatch(gp, testPath, tests.get(testPath));
+            }
+        }
+    }
+    
+    static @NotNull Map<GlobPattern, Map<String, Boolean>> createWildcardTests() {
+        Map<GlobPattern, Map<String, Boolean>> m = new HashMap<>();
 
         Map<String,Boolean> tests = new HashMap<>();
-
         // restriction "*" matches /foo, all siblings of foo and foo's and the siblings' descendants
         GlobPattern gp = GlobPattern.create("/a/b/c", "*");
         // matching
@@ -54,9 +64,7 @@ public class GlobPatternTest {
         tests.put("/a", false);
         tests.put("/b/c", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // restriction "*cat" matches all siblings and descendants of /foo that have a name ending with cat
         gp = GlobPattern.create("/a/b/c", "*e");
@@ -86,9 +94,7 @@ public class GlobPatternTest {
         tests.put("/a/b/ce/", false);
         tests.put("/a/b/ceg", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // restriction "*/cat" matches all descendants of /foo and foo's siblings that have a name segment "cat"
         gp = GlobPattern.create("/a/b/c", "*/e");
@@ -111,9 +117,7 @@ public class GlobPatternTest {
         tests.put("/a/b/c/e/f", false);
         tests.put("/a/b/ce/", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // matches target path '/a/b/c/e', all siblings whose name starts with e
         // and child nodes of either.
@@ -137,9 +141,7 @@ public class GlobPatternTest {
         tests.put("/a/b/c/d/f/e/f", false);
         tests.put("/a/b/cee/d/e/f", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // all descendants of '/a/b/c/e'
         gp = GlobPattern.create("/a/b/c/e", "/*");
@@ -161,9 +163,7 @@ public class GlobPatternTest {
         tests.put("/a/b/c/d/f/e/f", false);
         tests.put("/a/b/cee/d/e/f", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // all descendants of '/a/b/ce'
         gp = GlobPattern.create("/a/b/c", "e/*");
@@ -182,9 +182,7 @@ public class GlobPatternTest {
         tests.put("/a/b/cee/d/e/f", false);
         tests.put("/a/b/ce/", false);       // missing * after ce/
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // all descendants of '/'
         gp = GlobPattern.create("/", "*");
@@ -199,9 +197,7 @@ public class GlobPatternTest {
         // not-matching
         tests.put("/", false);
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         // restriction "*cat/*" matches all siblings and descendants of /foo that have an intermediate segment ending with 'cat'
         gp = GlobPattern.create("/a/b/c", "*e/*");
@@ -220,9 +216,7 @@ public class GlobPatternTest {
         tests.put("/a/b/c/d/f/f", false);  // missing *e
         tests.put("/a/b/c/ed/f/f", false); // missing e/
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         //  restriction /*cat/*  matches all descendants of /foo that have an intermediate segment ending with 'cat'
         gp = GlobPattern.create("/a/b/c", "/*e/*");
@@ -242,9 +236,7 @@ public class GlobPatternTest {
         tests.put("/a/b/c/d/f/f", false);  // missing *e
         tests.put("/a/b/c/ed/f/f", false); // missing e/
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         //  restriction /*cat  matches all children of /a/b/c whose path ends with "cat"
         gp = GlobPattern.create("/a/b/c", "/*cat");
@@ -264,9 +256,7 @@ public class GlobPatternTest {
         tests.put("/a/b/cat/ed/f/f", false); // ... nor do siblings' children
         tests.put("/a/b/ced/cat", false);    // ... nor do siblings' children
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         //  restriction /*/cat  matches all non-direct descendants of /foo named "cat"
         gp = GlobPattern.create("/a/b/c", "/*/cat");
@@ -289,9 +279,7 @@ public class GlobPatternTest {
         tests.put("/a/b/ced/cat", false);    // ... nor do siblings' children
         tests.put("/a/b/ced/f/cat", false);  // ... nor do siblings' children
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
 
         //  restriction /cat* matches all descendant paths of /foo that have the
         //  direct foo-descendant segment starting with "cat"
@@ -317,13 +305,20 @@ public class GlobPatternTest {
         tests.put("/a/b/ced/cat", false);    // ... nor do siblings' children
         tests.put("/a/b/ced/f/cat", false);  // ... nor do siblings' children
 
-        for (String testPath : tests.keySet()) {
-            assertMatch(gp, testPath, tests.get(testPath));
-        }
+        m.put(gp, tests);
+
+        return m;
     }
 
     @Test
-    public void testEmptyRestriction() {
+    public void testMatchesWildcardAll() {
+        Map<GlobPattern, Map<String, Boolean>> m = createWildcardTests();
+        runTests(m);
+    }
+
+    static @NotNull Map<GlobPattern, Map<String, Boolean>> createEmptyTests() {
+        Map<GlobPattern, Map<String, Boolean>> m = new HashMap<>();
+
         GlobPattern gp = GlobPattern.create("/", "");
         Map<String,Boolean> tests = new HashMap<>();
         tests.put("/", true);
@@ -335,9 +330,7 @@ public class GlobPatternTest {
         tests.put("/a", false);
         tests.put("/a/b/cde", false);
 
-        for (String toTest : tests.keySet()) {
-            assertEquals(gp + " : " + toTest, tests.get(toTest), gp.matches(toTest));
-        }
+        m.put(gp, tests);
 
         gp = GlobPattern.create("/a/b/c", "");
 
@@ -351,13 +344,18 @@ public class GlobPatternTest {
         tests.put("/a", false);
         tests.put("/a/b/cde", false);
 
-        for (String toTest : tests.keySet()) {
-            assertEquals(gp + " : " + toTest, tests.get(toTest), gp.matches(toTest));
-        }
+        m.put(gp, tests);
+        return m;
     }
 
     @Test
-    public void testPathRestriction() {
+    public void testEmptyRestriction() {
+        runTests(createEmptyTests());
+    }
+
+    static @NotNull Map<GlobPattern, Map<String, Boolean>> createPathTests() {
+        Map<GlobPattern, Map<String, Boolean>> m = new HashMap<>();
+
         GlobPattern gp = GlobPattern.create("/a/b/c", "d");
         Map<String,Boolean> tests = new HashMap<>();
         tests.put("/", false);
@@ -370,9 +368,7 @@ public class GlobPatternTest {
         tests.put("/a/b/cd/e/f", true);
         tests.put("/a/b/cde", false);
 
-        for (String toTest : tests.keySet()) {
-            assertEquals(gp + " : " + toTest, tests.get(toTest), gp.matches(toTest));
-        }
+        m.put(gp, tests);
 
         gp = GlobPattern.create("/a/b/c", "/d");
 
@@ -387,9 +383,7 @@ public class GlobPatternTest {
         tests.put("/a/b/cd/e/f", false);
         tests.put("/a/b/cde", false);
 
-        for (String toTest : tests.keySet()) {
-            assertEquals(gp + " : " + toTest, tests.get(toTest), gp.matches(toTest));
-        }
+        m.put(gp, tests);
 
         gp = GlobPattern.create("/a/b/c", "/d/");
 
@@ -405,21 +399,31 @@ public class GlobPatternTest {
         tests.put("/a/b/cd/e/f", false);
         tests.put("/a/b/cde", false);
 
-        for (String toTest : tests.keySet()) {
-            assertEquals(gp + " : " + toTest, tests.get(toTest), gp.matches(toTest));
-        }
+        m.put(gp, tests);
+        return m;
+    }
+
+    @Test
+    public void testPathRestriction() {
+        runTests(createPathTests());
+    }
+    
+    static @NotNull String restrictionExcessiveWildcard() {
+        return "1*/2*/3*/4*/5*/6*/7*/8*/9*/10*/11*/12*/13*/14*/15*/16*/17*/18*/19*/20*/21*";
+    }
+    
+    private static @NotNull GlobPattern createGlobPatternExcessiveWildcard() {
+        return GlobPattern.create("/", restrictionExcessiveWildcard());
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testMaxOccurrences() {
-        GlobPattern gp = GlobPattern.create("/", "1*/2*/3*/4*/5*/6*/7*/8*/9*/10*/11*/12*/13*/14*/15*/16*/17*/18*/19*/20*/21*");
-        gp.matches("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21");
+        createGlobPatternExcessiveWildcard().matches("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21");
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testMaxOccurrences2() {
-        GlobPattern gp = GlobPattern.create("/", "1*/2*/3*/4*/5*/6*/7*/8*/9*/10*/11*/12*/13*/14*/15*/16*/17*/18*/19*/20*/21*");
-        gp.matches("/11/22/33/44/55/66/77/88/99/100/111/122/133/144/155/166/177/188/199/200/211");
+        createGlobPatternExcessiveWildcard().matches("/11/22/33/44/55/66/77/88/99/100/111/122/133/144/155/166/177/188/199/200/211");
     }
 
     @Test
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPatternTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPatternTest.java
new file mode 100644
index 0000000..9b7619e
--- /dev/null
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/GlobsPatternTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.oak.security.authorization.restriction;
+
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class GlobsPatternTest {
+    
+    private static final String PATH = "/a/b/c";
+    
+    private static final Map<String, List<String>> MATCH = new HashMap<>();
+    private static final Map<String, List<String>> NO_MATCH = new HashMap<>();
+    static {
+        GlobPatternTest.createWildcardTests().forEach((globPattern, tests) -> {
+            String path = extractPath(globPattern);
+            if (PATH.equals(path)) {
+                String restriction = extractRestriction(globPattern);
+                List<String> matching = new ArrayList<>();
+                List<String> notMatching = new ArrayList<>();
+                tests.forEach((testPath, expectedMatch) -> {
+                    if (expectedMatch) {
+                        matching.add(testPath);
+                    } else {
+                        notMatching.add(testPath);
+                    }
+                });
+                MATCH.put(restriction, matching);
+                NO_MATCH.put(restriction, notMatching);
+            }
+        });
+    }
+    
+    private static String extractPath(@NotNull GlobPattern globPattern) {
+        String s = globPattern.toString();
+        return s.substring(0, s.indexOf(":")-1);
+    }
+
+    private static String extractRestriction(@NotNull GlobPattern globPattern) {
+        String s = globPattern.toString();
+        return s.substring(s.indexOf(":")+2);
+    }
+    
+    private static String getAnyRestriction(@NotNull Map<String, List<String>> m) {
+        return Iterables.get(m.keySet(), new Random().nextInt(m.size()));
+    }
+    
+    @Test
+    public void testAllMatching() {
+        GlobsPattern gp = new GlobsPattern(PATH, MATCH.keySet());
+        MATCH.values().forEach(testPaths -> testPaths.forEach(testPath -> assertTrue(gp.matches(testPath))));
+    }
+
+    @Test
+    public void testNoMatching() {
+        String rest1 = getAnyRestriction(NO_MATCH);
+        String rest2 = getAnyRestriction(NO_MATCH);
+        
+        GlobsPattern gp = new GlobsPattern(PATH, Arrays.asList(rest1, rest2));
+
+        GlobPattern p1 = GlobPattern.create(PATH, rest1);
+        NO_MATCH.get(rest2).forEach(testPath -> {
+            boolean expectedMatch = p1.matches(testPath);
+            assertEquals(expectedMatch, gp.matches(testPath));
+        });    
+    }
+
+    @Test
+    public void testMixedMatching() {
+        String rest1 = getAnyRestriction(MATCH);
+        String rest2 = getAnyRestriction(NO_MATCH);
+        GlobsPattern gp = new GlobsPattern(PATH, Arrays.asList(rest1, rest2));
+        
+        GlobPattern p1 = GlobPattern.create(PATH, rest1);
+        // test-paths for the rest1 will always match
+        MATCH.get(rest1).forEach(testPath -> assertTrue(gp.matches(testPath)));
+        // test-pathss for the non-matching restriction will match if they match rest1
+        NO_MATCH.get(rest2).forEach(testPath -> {
+            boolean expectedMatch = p1.matches(testPath);
+            assertEquals(expectedMatch, gp.matches(testPath));
+        });
+    }
+
+    @Test
+    public void testMatchTreeProperty() {
+        String rest1 = getAnyRestriction(MATCH);
+        String rest2 = getAnyRestriction(MATCH);
+        GlobsPattern gp = new GlobsPattern(PATH, Arrays.asList(rest1, rest2));
+
+        MATCH.get(rest1).forEach(testPath -> {
+            Tree t = when(mock(Tree.class).getPath()).thenReturn(testPath).getMock();
+            assertTrue(gp.matches(t, null));
+            verify(t).getPath();
+            verifyNoMoreInteractions(t);
+        });
+
+        MATCH.get(rest2).forEach(testPath -> {
+            String parentPath = PathUtils.getParentPath(testPath);
+            Tree t = when(mock(Tree.class).getPath()).thenReturn(parentPath).getMock();
+            PropertyState ps = when(mock(PropertyState.class).getName()).thenReturn(PathUtils.getName(testPath)).getMock();
+            assertTrue(gp.matches(t, ps));
+            verify(t).getPath();
+            verify(ps).getName();
+            verifyNoMoreInteractions(t, ps);
+        });
+    }
+    
+    @Test
+    public void testEmptyValues() {
+        GlobsPattern gp = new GlobsPattern(PATH, Collections.emptyList());
+        assertNoMatch(gp);
+    }
+
+    @Test
+    public void testNullValues() {
+        GlobsPattern gp = new GlobsPattern(PATH, Collections.singletonList(null));
+        assertNoMatch(gp);
+    }
+    
+    private static void assertNoMatch(@NotNull GlobsPattern gp) {
+        assertFalse(gp.matches("/"));
+        assertFalse(gp.matches(PATH));
+        assertFalse(gp.matches(PATH + "-sibling"));
+        assertFalse(gp.matches(PATH + "/sub/tree"));
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testMaxOccurrences() {
+        GlobsPattern gp = new GlobsPattern("/", 
+                Arrays.asList("simple/restriction", GlobPatternTest.restrictionExcessiveWildcard()));
+        gp.matches("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21");
+    }
+    
+    @Test
+    public void testMatches() {
+        GlobsPattern gp = new GlobsPattern("/path", Collections.emptyList());
+        assertFalse(gp.matches());
+    }
+
+    @Test
+    public void testHashCode() {
+        String rest1 = getAnyRestriction(MATCH);
+        String rest2 = getAnyRestriction(NO_MATCH);
+        List<String> rests = Arrays.asList(rest1, rest2);
+        
+        GlobsPattern pattern = new GlobsPattern(PATH, rests);
+        
+        assertEquals(pattern.hashCode(), new GlobsPattern(PATH, rests).hashCode());
+        assertNotEquals(pattern.hashCode(), new GlobsPattern(PATH, Collections.emptyList()).hashCode());
+        assertNotEquals(pattern.hashCode(), new GlobsPattern(PATH, Collections.singletonList(rest2)).hashCode());
+        assertNotEquals(pattern.hashCode(), new GlobsPattern("/different/path", rests).hashCode());
+    }
+
+    @Test
+    public void testEquals() {
+        String rest1 = getAnyRestriction(MATCH);
+        String rest2 = getAnyRestriction(NO_MATCH);
+        List<String> rests = Arrays.asList(rest1, rest2);
+
+        GlobsPattern pattern = new GlobsPattern(PATH, rests);
+        
+        assertEquals(pattern, pattern);
+        assertEquals(pattern, new GlobsPattern(PATH, rests));
+    }
+
+    @Test
+    public void testNotEquals() {
+        String rest1 = getAnyRestriction(MATCH);
+        String rest2 = getAnyRestriction(NO_MATCH);
+        List<String> rests = Arrays.asList(rest1, rest2);
+
+        GlobsPattern pattern = new GlobsPattern(PATH, rests);
+        
+        // different tree path
+        assertNotEquals(pattern, new GlobsPattern("/another/path", rests));
+        // different set of subtrees
+        assertNotEquals(pattern, new GlobsPattern(PATH, Collections.emptyList()));
+        assertNotEquals(pattern, new GlobsPattern(PATH, Collections.singletonList(rest1)));
+        assertNotEquals(pattern, new GlobsPattern(PATH, Arrays.asList(rest1, rest2, getAnyRestriction(NO_MATCH))));
+        // different restriction
+        assertNotEquals(new GlobsPattern(PATH, Collections.singletonList(rest1)), GlobPattern.create(PATH, rest1));
+    }
+}
\ No newline at end of file
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImplTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImplTest.java
index 4dc3c3b..1150c88 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImplTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImplTest.java
@@ -16,13 +16,6 @@
  */
 package org.apache.jackrabbit.oak.security.authorization.restriction;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlManager;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.apache.jackrabbit.JcrConstants;
@@ -38,11 +31,21 @@ import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.Access
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.CompositePattern;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionImpl;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import static com.google.common.collect.Maps.newHashMap;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -72,8 +75,9 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
 
         Set<RestrictionDefinition> defs = provider.getSupportedRestrictions("/testPath");
         assertNotNull(defs);
-        assertEquals(5, defs.size());
+        assertEquals(7, defs.size());
 
+        Set<String> stringsPropNames = ImmutableSet.of(REP_PREFIXES, REP_CURRENT, REP_GLOBS, REP_SUBTREES);
         for (RestrictionDefinition def : defs) {
             if (REP_GLOB.equals(def.getName())) {
                 assertEquals(Type.STRING, def.getRequiredType());
@@ -81,13 +85,10 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
             } else if (REP_NT_NAMES.equals(def.getName())) {
                 assertEquals(Type.NAMES, def.getRequiredType());
                 assertFalse(def.isMandatory());
-            } else if (REP_PREFIXES.equals(def.getName())) {
-                assertEquals(Type.STRINGS, def.getRequiredType());
-                assertFalse(def.isMandatory());
             } else if (REP_ITEM_NAMES.equals(def.getName())) {
                 assertEquals(Type.NAMES, def.getRequiredType());
                 assertFalse(def.isMandatory());
-            } else if (REP_CURRENT.equals(def.getName())) {
+            } else if (stringsPropNames.contains(def.getName())) {
                 assertEquals(Type.STRINGS, def.getRequiredType());
                 assertFalse(def.isMandatory());
             } else {
@@ -127,7 +128,8 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
     @Test
     public void testGetPatternForAllSupported() throws Exception {
         Map<PropertyState, RestrictionPattern> map = newHashMap();
-        map.put(PropertyStates.createProperty(REP_GLOB, "/*/jcr:content"), GlobPattern.create("/testPath", "/*/jcr:content"));
+        String globRestriction = "/*/jcr:content";
+        map.put(PropertyStates.createProperty(REP_GLOB, globRestriction), GlobPattern.create("/testPath", globRestriction));
         List<String> ntNames = ImmutableList.of(JcrConstants.NT_FOLDER, JcrConstants.NT_LINKEDFILE);
         map.put(PropertyStates.createProperty(REP_NT_NAMES, ntNames, Type.NAMES), new NodeTypePattern(ntNames));
         List<String> prefixes = ImmutableList.of("rep", "jcr");
@@ -135,7 +137,11 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
         List<String> itemNames = ImmutableList.of("abc", "jcr:primaryType");
         map.put(PropertyStates.createProperty(REP_ITEM_NAMES, prefixes, Type.NAMES), new ItemNamePattern(itemNames));
         List<String> propNames = ImmutableList.of("jcr:mixinTypes", "jcr:primaryType");
-        map.put(PropertyStates.createProperty(REP_CURRENT, prefixes, Type.STRINGS), new CurrentPattern("/testPatg", propNames));
+        map.put(PropertyStates.createProperty(REP_CURRENT, propNames, Type.STRINGS), new CurrentPattern("/testPath", propNames));
+        List<String> globs = Collections.singletonList(globRestriction);
+        map.put(PropertyStates.createProperty(REP_GLOBS, globs, Type.STRINGS), new GlobsPattern("/testPath", globs));
+        List<String> subtrees = ImmutableList.of("/sub/tree", "/a/b/c/");
+        map.put(PropertyStates.createProperty(REP_SUBTREES, subtrees, Type.STRINGS), new SubtreePattern("/testPath", subtrees));
 
         Tree tree = TreeUtil.getOrAddChild(root.getTree("/"), "testPath", JcrConstants.NT_UNSTRUCTURED);
         Tree restrictions = TreeUtil.addChild(tree, REP_RESTRICTIONS, NT_REP_RESTRICTIONS);
@@ -149,8 +155,10 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
 
     @Test
     public void testGetPatternFromRestrictions() throws Exception {
+        String globRestriction = "/*/jcr:content";
+
         Map<PropertyState, RestrictionPattern> map = newHashMap();
-        map.put(PropertyStates.createProperty(REP_GLOB, "/*/jcr:content"), GlobPattern.create("/testPath", "/*/jcr:content"));
+        map.put(PropertyStates.createProperty(REP_GLOB, globRestriction), GlobPattern.create("/testPath", globRestriction));
 
         List<String> ntNames = ImmutableList.of(JcrConstants.NT_FOLDER, JcrConstants.NT_LINKEDFILE);
         map.put(PropertyStates.createProperty(REP_NT_NAMES, ntNames, Type.NAMES), new NodeTypePattern(ntNames));
@@ -163,10 +171,16 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
 
         List<String> propNames = ImmutableList.of("*");
         map.put(PropertyStates.createProperty(REP_CURRENT, propNames, Type.STRINGS), new CurrentPattern("/testPath", propNames));
-        
+
+        List<String> globs = Collections.singletonList(globRestriction);
+        map.put(PropertyStates.createProperty(REP_GLOBS, globs, Type.STRINGS), new GlobsPattern("/testPath", globs));
+
+        List<String> subtrees = ImmutableList.of("/sub/tree", "/a/b/c/");
+        map.put(PropertyStates.createProperty(REP_SUBTREES, subtrees, Type.STRINGS), new SubtreePattern("/testPath", subtrees));
+
         Tree tree = TreeUtil.getOrAddChild(root.getTree("/"), "testPath", JcrConstants.NT_UNSTRUCTURED);
         Tree restrictions = TreeUtil.addChild(tree, REP_RESTRICTIONS, NT_REP_RESTRICTIONS);
-        
+
         // test restrictions individually
         for (Map.Entry<PropertyState, RestrictionPattern> entry : map.entrySet()) {
             restrictions.setProperty(entry.getKey());
@@ -185,6 +199,14 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
     }
 
     @Test
+    public void testGetPatternFromInvalidRestrictionSet() {
+        PropertyState ps = PropertyStates.createProperty("invalid", Collections.singleton("value"), Type.STRINGS);
+        Set<Restriction> restrictions = Collections.singleton(new RestrictionImpl(ps, false));
+        RestrictionPattern pattern = provider.getPattern("/testPath", restrictions);
+        assertSame(RestrictionPattern.EMPTY, pattern);
+    }
+
+    @Test
     public void testGetPatternFromTreeNullPath() {
         assertSame(RestrictionPattern.EMPTY, provider.getPattern(null, mock(Tree.class)));
     }
@@ -223,4 +245,27 @@ public class RestrictionProviderImplTest extends AbstractSecurityTest implements
             }
         }
     }
+
+    @Test(expected = AccessControlException.class)
+    public void testValidateMvGlobRestriction() throws Exception {
+        Tree t = TreeUtil.getOrAddChild(root.getTree("/"), "testTree", JcrConstants.NT_UNSTRUCTURED);
+        String path = t.getPath();
+
+        AccessControlManager acMgr = getAccessControlManager(root);
+
+        ValueFactory vf = getValueFactory(root);
+        JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, path);
+        acl.addEntry(getTestUser().getPrincipal(),
+                AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.JCR_READ),
+                true, Collections.emptyMap(), Collections.singletonMap(REP_GLOBS, new Value[] {
+                        vf.createValue("/1*/2*/3*/4*/5*/6*/7*/8*/9*/10*/11*/12*/13*/14*/15*/16*/17*/18*/19*/20*/21*"),
+                        vf.createValue("*********************")}));
+        acMgr.setPolicy(path, acl);
+
+        try {
+            provider.validateRestrictions(path, t.getChild(REP_POLICY).getChild("allow"));
+        } finally {
+            acMgr.removePolicy(path, acl);
+        }
+    }
 }
\ No newline at end of file
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePatternTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePatternTest.java
new file mode 100644
index 0000000..510041f
--- /dev/null
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/restriction/SubtreePatternTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.oak.security.authorization.restriction;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class SubtreePatternTest {
+    
+    private static final String PATH = "/var/foo";
+    private static final List<String> SUBTREES = Arrays.asList("/a/path", "/only/the/subtree/", ".ext", "rel/path/");
+    private final SubtreePattern pattern = new SubtreePattern(PATH, SUBTREES);
+    
+    @Test
+    public void testMatches() {
+        assertFalse(pattern.matches());
+    }
+    
+    @Test
+    public void testMatchesTree() {
+        Tree tree = when(mock(Tree.class).getPath()).thenReturn("/var").getMock();
+        assertFalse(pattern.matches(tree, null));
+        verify(tree).getPath();
+        verifyNoMoreInteractions(tree);
+
+        tree = when(mock(Tree.class).getPath()).thenReturn("/var/foo/a").getMock();
+        PropertyState prop = when(mock(PropertyState.class).getName()).thenReturn("path").getMock();
+        assertTrue(pattern.matches(tree, prop));
+        verify(tree).getPath();
+        verify(prop).getName();
+        verifyNoMoreInteractions(tree, prop);
+    }
+    
+    @Test
+    public void testTargetMatch() {
+        assertTrue(pattern.matches("/var/foo/a/path"));
+        assertTrue(pattern.matches("/var/foo/any/a/path"));
+        assertTrue(pattern.matches("/var/foo.ext"));
+        assertTrue(pattern.matches("/var/foo/any.ext"));
+        assertTrue(pattern.matches("/var/foo/any/something.ext"));
+        assertTrue(pattern.matches("/var/foo/any.ext/something"));
+        assertTrue(pattern.matches("/var/foo/.ext"));
+        assertTrue(pattern.matches("/var/foo/.ext/something"));
+        assertTrue(pattern.matches("/var/foo/rel/path/something"));
+        assertTrue(pattern.matches("/var/foorel/path/something"));
+        assertTrue(pattern.matches("/var/foo/any/rel/path/something"));
+        assertTrue(pattern.matches("/var/foo/anyrel/path/something"));
+        
+        assertFalse(pattern.matches("/vara/path"));
+        assertFalse(pattern.matches("/var/foo/only/the/subtree"));
+        assertFalse(pattern.matches("/var/foorel/path"));
+        assertFalse(pattern.matches("/var/foo/rel/path"));
+        assertFalse(pattern.matches("/var/foo/anyrel/path"));
+        assertFalse(pattern.matches("/var/foo/any/rel/path"));
+
+
+        assertFalse(pattern.matches("/"));
+        assertFalse(pattern.matches("/etc/a/path"));
+        assertFalse(pattern.matches("/var"));
+        assertFalse(pattern.matches("/content.ext", false));
+    }
+    
+    @Test
+    public void testSubtreeMatch() {
+        assertTrue(pattern.matches("/var/foo/a/path/child"));
+        assertTrue(pattern.matches("/var/foo/any.ext/child"));
+        assertTrue(pattern.matches("/var/foo/rel/path/child"));
+        assertTrue(pattern.matches("/var/foorel/path/child"));
+        assertTrue(pattern.matches("/var/foo/only/the/subtree/child"));
+
+        assertFalse(pattern.matches("/var/a/path/child"));
+        assertFalse(pattern.matches("/vara/path/child"));
+    }
+    
+    @Test
+    public void testEmptyValues() {
+        SubtreePattern sp = new SubtreePattern(PATH, Collections.emptyList());
+        assertNoMatch(sp);
+    }
+
+    @Test
+    public void testEmptyString() {
+        SubtreePattern sp = new SubtreePattern(PATH, Collections.singletonList(""));
+        assertNoMatch(sp);
+    }
+
+    @Test
+    public void testNullString() {
+        SubtreePattern sp = new SubtreePattern(PATH, Collections.singletonList(null));
+        assertNoMatch(sp);
+    }
+    
+    private static void assertNoMatch(@NotNull SubtreePattern sp) {
+        assertFalse(sp.matches("/"));
+        assertFalse(sp.matches("/etc"));
+        assertFalse(sp.matches("/var"));
+        assertFalse(sp.matches("/var/foo"));
+        assertFalse(sp.matches("/var/foo/"));
+        assertFalse(sp.matches("/var/foo/some/path"));
+    }
+
+    @Test
+    public void testHashCode() {
+        assertEquals(pattern.hashCode(), new SubtreePattern(PATH, SUBTREES).hashCode());
+        assertNotEquals(pattern.hashCode(), new SubtreePattern("/var", SUBTREES).hashCode());
+        assertNotEquals(pattern.hashCode(), new SubtreePattern(PATH, SUBTREES.subList(0, 2)).hashCode());
+        assertNotEquals(pattern.hashCode(), new SubtreePattern("/var", Collections.emptyList()).hashCode());
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(pattern, pattern);
+        assertEquals(pattern, new SubtreePattern(PATH, SUBTREES));
+    }
+
+    @Test
+    public void testNotEquals() {
+        // different tree path
+        assertNotEquals(pattern, new SubtreePattern("/another/path", SUBTREES));
+        // different set of subtrees
+        assertNotEquals(pattern, new SubtreePattern(PATH, Collections.emptyList()));
+        assertNotEquals(pattern, new SubtreePattern(PATH, Collections.singleton("/some/other/subtree")));
+        assertNotEquals(pattern, new SubtreePattern(PATH, SUBTREES.subList(0, 2)));
+        assertNotEquals(pattern, new SubtreePattern(PATH, Lists.asList("/subtree/", SUBTREES.toArray(new String[0]))));
+        // different restrictions
+        assertNotEquals(pattern, new ItemNamePattern(ImmutableSet.of("a", "b")));
+        assertNotEquals(pattern, new PrefixPattern(ImmutableSet.of("a", "b", "c")));
+    }
+}
\ No newline at end of file
diff --git a/oak-doc/src/site/markdown/security/authorization/restriction.md b/oak-doc/src/site/markdown/security/authorization/restriction.md
index bc8db56..cf95961 100644
--- a/oak-doc/src/site/markdown/security/authorization/restriction.md
+++ b/oak-doc/src/site/markdown/security/authorization/restriction.md
@@ -117,8 +117,11 @@ The following `Restriction` implementations are supported with the default Oak a
 | `rep:prefixes`   | String | true         | false     | Multivalued restriction to limit the effect to item names that match the specified namespace prefixes (session level remapping not respected) | Oak 1.0 |
 | `rep:itemNames`  | Name   | true         | false     | Multivalued restriction for property or node names | Oak 1.3.8 |
 | `rep:current`    | String | true         | false     | Multivalued restriction that limits the effect to a single level i.e. the target node where the access control entry takes effect and optionally all or a subset of it's properties. There is no inheritance of the ACE effect to nodes in the subtree or their properties. Expanded JCR property names and namespace remapping not supported (see below for details) | Oak 1.42.0 |
+| `rep:globs`      | String | true         | false     | Multivalued variant of the `rep:glob` restriction | Oak 1.44.0 |
+| `rep:subtrees`   | String | true         | false     | Multivalued restriction that limits effect to one or multiple subtrees (see below) | Oak 1.44.0 |
 
 
+<a name="rep_glob"></a>
 ##### rep:glob - Details and Examples
 
 For a nodePath `/foo` the following results can be expected for the different
@@ -157,6 +160,7 @@ Examples without wildcard char:
 
 See also [GlobPattern] for implementation details and the [GlobRestrictionTest] in the _oak-exercise_ module for training material.
 
+<a name="rep_current"></a>
 ##### rep:current - Details and Examples
 
 The restriction limits the effect to the target node. The value of the restriction property defines which properties of 
@@ -186,6 +190,39 @@ point to non-existing nodes. Example: if `rep:current` is present with an ACE ta
 `Session.hasPermission("/foo/non-existing",Session.ACTION_READ)` will always return `false` because the restriction
 will interpret `/foo/non-existing` as a path pointing to a node.
 
+<a name="rep_subtrees"></a>
+##### rep:subtrees - Details and Examples
+
+The `rep:subtrees` restriction allows to limit the effect to one or multiple subtrees below the access-controlled 
+node. It is a simplified variant of the common pattern using 2 `rep:glob` (wildcard) patterns to grant or 
+deny access on a particular node in the subtree and all its descendent items.
+
+A common pattern involving `rep:glob` may look as follows:
+- entry 1: `rep:glob` = "/\*/path/to/subtree"
+- entry 2: `rep:glob`= "/\*/path/to/subtree/\*"
+
+The `rep:subtrees` restriction neither requires multiple entries nor wildcard characters 
+that have shown to be prone to mistakes. The corresponding setup with `rep:subtrees` would be
+- entry: `rep:subtrees` = "/path/to/subtree"
+
+and matches both the tree defined at /foo/path/to/subtree as well as any subtree at /foo/*/path/to/subtree.
+
+For a node path `/foo` the following results can be expected for different values of `rep:subtrees`:
+
+| `rep:subtrees`| Result |
+|---------------|-------------------------------------------------------------|
+| [/cat]        |   all descendants of `/foo` whose path ends with `/cat` or that have an intermediate segment `/cat/` |
+| [/cat/]       |   all descendants of `/foo` that have an intermediate segment `/cat/` |
+| [cat]         |   all siblings or descendants of `/foo` whose path ends with `cat` or that have an intermediate segment ending with `cat` |
+| [cat/]        |   all siblings or descendants of `/foo` that have an intermediate segment ending with `cat` |
+
+Note:
+- variants of 'cat'-paths could also consist of multiple segments like e.g. `/cat/dog` or `/cat/dog`
+- different subtrees can be matched by defining multiple values in the restriction
+- in contrast to `rep:glob` no wildcard characters are required to make sure the restriction is evaluated against the whole tree below the access controlled node.
+- an empty value array will never match any path/item
+- null values and empty string values will be ignored
+
 
 <a name="representation"></a>
 ### Representation in the Repository
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AccessControlConstants.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AccessControlConstants.java
index 6d4b351..28e421a 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AccessControlConstants.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AccessControlConstants.java
@@ -93,6 +93,41 @@ public interface AccessControlConstants {
     String REP_CURRENT = "rep:current";
 
     /**
+     * <p>Name of the optional multi-valued access control restriction that allows to combine more than one 
+     * {@link #REP_GLOB} restriction. The effect is equivalent to defining multiple access control entries with a 
+     * single {@link #REP_GLOB} restriction each and will match a given path or item if any of the specified glob-values 
+     * matches.</p>
+     * <p>
+     * Note, that an empty value array will never match any path/item.</p>
+     * <p>
+     * The corresponding restriction type is {@link org.apache.jackrabbit.oak.api.Type#STRINGS}
+     * </p>
+     */
+    String REP_GLOBS = "rep:globs";
+
+    /**
+     * <p>Name of the optional multi-valued access control restriction that allows to limit the effect to one or multiple  
+     * subtrees. It is a simplified variant of the common pattern using 2 {@link #REP_GLOB} wildcard patterns to grant or 
+     * deny access on a particular node in the subtree and all its descendent items.</p>
+     *
+     * <pre>
+     * NodePath = "/foo"
+     * Restriction   |   Matches
+     * -----------------------------------------------------------------------------
+     * /cat          |   all descendants of /foo whose path ends with "/cat" or that have an intermediate segment /cat/
+     * /cat/         |   all descendants of /foo that have an intermediate segment /cat/
+     * cat           |   all siblings or descendants of /foo whose path ends with "cat" or that have an intermediate segment ending with "cat"
+     * cat/          |   all siblings or descendants of /foo that have an intermediate segment ending with "cat"
+     * </pre>
+     *  
+     * <p>Note, that variants of 'cat'-paths could also consist of multiple segments like e.g. '/cat/dog' or '/cat/dog'</p> 
+     * <p>Note, that in contrast to {@link #REP_GLOB}</p> no wildcard characters are used to specify the restriction.
+     * <p>Note, that an empty value array will never match any path/item.</p>
+     * <p>Note, that null values and empty string values will be omitted.</p>
+     */
+    String REP_SUBTREES = "rep:subtrees";
+
+    /**
      * @since OAK 1.0
      */
     String REP_RESTRICTIONS = "rep:restrictions";
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
index a4f2ad2..fdd635b 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.9.0")
+@Version("1.10.0")
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
 import org.osgi.annotation.versioning.Version;