blob: baa339f4774f4738eb77cb89db5f415f5b662662 [file] [log] [blame]
/*
* 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.spi.security.authorization.cug.impl;
import java.security.Principal;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.jcr.AccessDeniedException;
import javax.jcr.RepositoryException;
import javax.jcr.SimpleCredentials;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.tree.RootProvider;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude;
import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugPolicy;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Base class for CUG related test that setup the authorization configuration
* to expose the CUG specific implementations of {@code AccessControlManager}
* and {@code PermissionProvider}.
*/
public abstract class AbstractCugTest extends AbstractSecurityTest implements CugConstants, NodeTypeConstants {
static final String SUPPORTED_PATH = "/content";
static final String SUPPORTED_PATH2 = "/content2";
static final String SUPPORTED_PATH3 = "/some/content/tree";
static final String UNSUPPORTED_PATH = "/testNode";
static final String INVALID_PATH = "/path/to/non/existing/tree";
static final String[] SUPPORTED_PATHS = {SUPPORTED_PATH, SUPPORTED_PATH2, SUPPORTED_PATH3};
static final ConfigurationParameters CUG_CONFIG = ConfigurationParameters.of(
CugConstants.PARAM_CUG_SUPPORTED_PATHS, SUPPORTED_PATHS,
CugConstants.PARAM_CUG_ENABLED, true);
static final String TEST_GROUP_ID = "testGroup" + UUID.randomUUID();
static final String TEST_USER2_ID = "testUser2" + UUID.randomUUID();
@Override
public void before() throws Exception {
super.before();
/*
* Create tree structure:
*
* + root
* + content
* + subtree
* + content2
* + some
* + content
* + tree
* + testNode
* + child
*/
Tree rootNode = root.getTree("/");
createTrees(rootNode, "content", "subtree");
createTrees(rootNode, "content2");
createTrees(rootNode, "some", "content", "tree");
createTrees(rootNode, "testNode", "child");
root.commit();
}
@Override
public void after() throws Exception {
try {
// revert transient pending changes (that might be invalid)
root.refresh();
// remove the test group and second test user
Authorizable testGroup = getUserManager(root).getAuthorizable(TEST_GROUP_ID);
if (testGroup != null) {
testGroup.remove();
}
Authorizable testUser2 = getUserManager(root).getAuthorizable(TEST_USER2_ID);
if (testUser2 != null) {
testUser2.remove();
}
for (String p : new String[] {SUPPORTED_PATH, SUPPORTED_PATH2, Text.getAbsoluteParent(SUPPORTED_PATH3, 0), UNSUPPORTED_PATH}) {
Tree t = root.getTree(p);
if (t.exists()) {
t.remove();
}
}
root.commit();
} finally {
super.after();
}
}
@Override
protected SecurityProvider getSecurityProvider() {
if (securityProvider == null) {
securityProvider = CugSecurityProvider.newTestSecurityProvider(getSecurityConfigParameters());
}
return securityProvider;
}
@Override
protected ConfigurationParameters getSecurityConfigParameters() {
return ConfigurationParameters.of(AuthorizationConfiguration.NAME, CUG_CONFIG);
}
CugPermissionProvider createCugPermissionProvider(@NotNull Set<String> supportedPaths, @NotNull Principal... principals) {
return new CugPermissionProvider(root, root.getContentSession().getWorkspaceName(), ImmutableSet.copyOf(principals), supportedPaths, getConfig(AuthorizationConfiguration.class).getContext(), getRootProvider(), getTreeProvider());
}
void createTrees(@NotNull Tree tree, @NotNull String... names) throws AccessDeniedException {
Tree parent = tree;
for (String n : names) {
parent = TreeUtil.addChild(parent, n, NodeTypeConstants.NT_OAK_UNSTRUCTURED);
}
}
void setupCugsAndAcls(@NotNull String... paths) throws Exception {
UserManager uMgr = getUserManager(root);
Principal testGroupPrincipal = getTestGroupPrincipal();
User testUser2 = uMgr.createUser(TEST_USER2_ID, TEST_USER2_ID);
((Group) uMgr.getAuthorizable(testGroupPrincipal)).addMember(testUser2);
root.commit();
User testUser = getTestUser();
// add more child nodes
Tree n = root.getTree(SUPPORTED_PATH);
createTrees(n, "a", "b", "c");
createTrees(n, "aa", "bb", "cc");
// create cugs
// - /content/a : allow testGroup, deny everyone
// - /content/aa/bb : allow testGroup, deny everyone
// - /content/a/b/c : allow everyone, deny testGroup (isolated)
// - /content2 : allow everyone, deny testGroup (isolated)
Map<String, Principal> m = ImmutableMap.of(
"/content/a", testGroupPrincipal,
"/content/aa/bb", testGroupPrincipal,
"/content/a/b/c", EveryonePrincipal.getInstance(),
"/content2", EveryonePrincipal.getInstance());
String[] cugPaths = (paths.length == 0) ? m.keySet().toArray(new String[0]) : paths;
for (String cugPath : cugPaths) {
createCug(cugPath, m.get(cugPath));
}
// setup regular acl at /content:
// - testUser ; allow ; jcr:read
// - testGroup ; allow ; jcr:read, jcr:write, jcr:readAccessControl
AccessControlManager acMgr = getAccessControlManager(root);
AccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, "/content");
acl.addAccessControlEntry(testUser.getPrincipal(), privilegesFromNames(
PrivilegeConstants.JCR_READ));
acl.addAccessControlEntry(testGroupPrincipal, privilegesFromNames(
PrivilegeConstants.JCR_READ, PrivilegeConstants.REP_WRITE, PrivilegeConstants.JCR_READ_ACCESS_CONTROL)
);
acMgr.setPolicy("/content", acl);
root.commit();
}
CugExclude getExclude() {
return new CugExclude.Default();
}
void createCug(@NotNull String absPath, @NotNull Principal principal) throws RepositoryException {
AccessControlManager acMgr = getAccessControlManager(root);
AccessControlPolicyIterator it = acMgr.getApplicablePolicies(absPath);
while (it.hasNext()) {
AccessControlPolicy policy = it.nextAccessControlPolicy();
if (policy instanceof CugPolicy) {
((CugPolicy) policy).addPrincipals(principal);
acMgr.setPolicy(absPath, policy);
return;
}
}
throw new IllegalStateException("Unable to create CUG at " + absPath);
}
static void createCug(@NotNull Root root, @NotNull String path, @NotNull String principalName) throws RepositoryException {
Tree tree = root.getTree(path);
Preconditions.checkState(tree.exists());
TreeUtil.addMixin(tree, MIX_REP_CUG_MIXIN, root.getTree(NODE_TYPES_PATH), null);
TreeUtil.addChild(tree, REP_CUG_POLICY, NT_REP_CUG_POLICY).setProperty(REP_PRINCIPAL_NAMES, ImmutableSet.of(principalName), Type.STRINGS);
}
Principal getTestGroupPrincipal() throws Exception {
UserManager uMgr = getUserManager(root);
Group g = uMgr.getAuthorizable(TEST_GROUP_ID, Group.class);
if (g == null) {
g = uMgr.createGroup(TEST_GROUP_ID);
root.commit();
}
return g.getPrincipal();
}
ContentSession createTestSession2() throws Exception {
return login(new SimpleCredentials(TEST_USER2_ID, TEST_USER2_ID.toCharArray()));
}
static void assertCugPermission(@NotNull TreePermission tp, boolean isSupportedPath) {
if (isSupportedPath) {
assertTrue(tp instanceof CugTreePermission);
} else {
assertTrue(tp instanceof EmptyCugTreePermission);
}
}
static void assertNestedCugs(@NotNull Root root, @NotNull RootProvider rootProvider,
@NotNull String cugHoldingPath, boolean hasCugPolicy, @NotNull String... expectedNestedPaths) {
Root immutableRoot = rootProvider.createReadOnlyRoot(root);
Tree tree = immutableRoot.getTree(cugHoldingPath);
if (hasCugPolicy) {
assertFalse(tree.hasProperty(HIDDEN_NESTED_CUGS));
tree = tree.getChild(REP_CUG_POLICY);
}
assertTrue(tree.exists());
if (tree.isRoot()) {
if (expectedNestedPaths.length == 0) {
assertFalse(tree.hasProperty(HIDDEN_TOP_CUG_CNT));
assertFalse(tree.hasProperty(HIDDEN_NESTED_CUGS));
} else {
assertTrue(tree.hasProperty(HIDDEN_NESTED_CUGS));
assertEquals(ImmutableSet.copyOf(expectedNestedPaths), ImmutableSet.copyOf(tree.getProperty(HIDDEN_NESTED_CUGS).getValue(Type.PATHS)));
assertTrue(tree.hasProperty(HIDDEN_TOP_CUG_CNT));
assertEquals(Long.valueOf(expectedNestedPaths.length), tree.getProperty(HIDDEN_TOP_CUG_CNT).getValue(Type.LONG));
}
} else {
assertFalse(tree.hasProperty(HIDDEN_TOP_CUG_CNT));
}
if (expectedNestedPaths.length == 0) {
assertFalse(tree.hasProperty(HIDDEN_NESTED_CUGS));
} else {
assertTrue(tree.hasProperty(HIDDEN_NESTED_CUGS));
assertEquals(ImmutableSet.copyOf(expectedNestedPaths), ImmutableSet.copyOf(tree.getProperty(HIDDEN_NESTED_CUGS).getValue(Type.PATHS)));
}
}
static TreePermission getTreePermission(@NotNull Root root,
@NotNull String path,
@NotNull PermissionProvider pp) {
Tree t = root.getTree("/");
TreePermission tp = pp.getTreePermission(t, TreePermission.EMPTY);
for (String segm : PathUtils.elements(path)) {
t = t.getChild(segm);
tp = pp.getTreePermission(t, tp);
}
return tp;
}
}