Skip to content
Permalink
Browse files
OAK-9701 : Additional restrictions to simplify permission setup
  • Loading branch information
anchela committed Feb 22, 2022
1 parent 4b285d2 commit b3685246f0f54a7668e5c463d9f1c40f373b3c25
Showing 16 changed files with 954 additions and 124 deletions.
@@ -37,6 +37,7 @@
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 static void main(String[] args) throws Exception {
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),
@@ -94,12 +94,17 @@ private void createForEachPrincipal(@NotNull Principal pGrantedRead, @NotNull Ja
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 {
@@ -24,6 +24,7 @@
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 @@ private enum EvaluationType {

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
@@ -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;
}

}
@@ -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;
}
}
@@ -63,21 +63,18 @@
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,53 +85,57 @@ public RestrictionPattern getPattern(@Nullable String oakPath, @NotNull Tree tre
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
public RestrictionPattern getPattern(@Nullable String oakPath, @NotNull Set<Restriction> restrictions) {
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 void validateRestrictions(@Nullable String oakPath, @NotNull Tree aceTree
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);
}
}
}
}

0 comments on commit b368524

Please sign in to comment.