blob: ef9d60dd747d892f3c3040e88364ff665447b815 [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.security.authorization.accesscontrol;
import java.security.Principal;
import javax.jcr.AccessDeniedException;
import javax.jcr.PropertyType;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.security.AccessControlManager;
import com.google.common.collect.ImmutableMap;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.jackrabbit.oak.security.authorization.AuthorizationConfigurationImpl;
import org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.Validator;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.util.NodeUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class AccessControlValidatorTest extends AbstractSecurityTest implements AccessControlConstants {
private final String testName = "testRoot";
private final String testPath = '/' + testName;
private final String aceName = "validAce";
private Principal testPrincipal;
@Before
public void before() throws Exception {
super.before();
NodeUtil rootNode = new NodeUtil(root.getTree("/"), getNamePathMapper());
rootNode.addChild(testName, JcrConstants.NT_UNSTRUCTURED);
root.commit();
testPrincipal = getTestUser().getPrincipal();
}
@After
public void after() throws Exception {
try {
Tree testRoot = root.getTree(testPath);
if (testRoot.exists()) {
testRoot.remove();
root.commit();
}
} finally {
super.after();
}
}
private NodeUtil getTestRoot() {
return new NodeUtil(root.getTree(testPath));
}
private AccessControlValidatorProvider createValidatorProvider() {
CompositeAuthorizationConfiguration cac = (CompositeAuthorizationConfiguration) getConfig(AuthorizationConfiguration.class);
return new AccessControlValidatorProvider((AuthorizationConfigurationImpl) cac.getDefaultConfig());
}
private NodeUtil createAcl() throws AccessDeniedException {
NodeUtil testRoot = getTestRoot();
testRoot.setNames(JcrConstants.JCR_MIXINTYPES, MIX_REP_ACCESS_CONTROLLABLE);
NodeUtil acl = testRoot.addChild(REP_POLICY, NT_REP_ACL);
NodeUtil ace = createACE(acl, aceName, NT_REP_GRANT_ACE, testPrincipal.getName(), PrivilegeConstants.JCR_READ);
ace.addChild(REP_RESTRICTIONS, NT_REP_RESTRICTIONS);
return acl;
}
private static NodeUtil createACE(NodeUtil acl, String aceName, String ntName, String principalName, String... privilegeNames) throws AccessDeniedException {
NodeUtil ace = acl.addChild(aceName, ntName);
ace.setString(REP_PRINCIPAL_NAME, principalName);
ace.setNames(REP_PRIVILEGES, privilegeNames);
return ace;
}
@Test
public void testPolicyWithOutChildOrder() throws AccessDeniedException {
NodeUtil testRoot = getTestRoot();
testRoot.setNames(JcrConstants.JCR_MIXINTYPES, MIX_REP_ACCESS_CONTROLLABLE);
testRoot.addChild(REP_POLICY, NT_REP_ACL);
try {
root.commit();
fail("Policy node with child node ordering");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("OakAccessControl0004")); // Order of children is not stable
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
}
}
@Test
public void testOnlyRootIsRepoAccessControllable() {
NodeUtil testRoot = getTestRoot();
testRoot.setNames(JcrConstants.JCR_MIXINTYPES, MIX_REP_REPO_ACCESS_CONTROLLABLE);
try {
root.commit();
fail("Only the root node can be made RepoAccessControllable.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot"));
}
}
@Test
public void testAddInvalidRepoPolicy() throws Exception {
NodeUtil testRoot = getTestRoot();
testRoot.setNames(JcrConstants.JCR_MIXINTYPES, MIX_REP_ACCESS_CONTROLLABLE);
NodeUtil policy = getTestRoot().addChild(REP_REPO_POLICY, NT_REP_ACL);
try {
root.commit();
fail("Attempt to add repo-policy with rep:AccessControllable node.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot"));
} finally {
policy.getTree().remove();
}
}
@Test
public void testAddPolicyWithAcContent() throws Exception {
NodeUtil acl = createAcl();
NodeUtil ace = acl.getChild(aceName);
NodeUtil[] acContent = new NodeUtil[]{acl, ace, ace.getChild(REP_RESTRICTIONS)};
for (NodeUtil node : acContent) {
NodeUtil policy = node.addChild(REP_POLICY, NT_REP_ACL);
try {
root.commit();
fail("Adding an ACL below access control content should fail");
} catch (CommitFailedException e) {
// success
assertTrue(e.isConstraintViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
} finally {
policy.getTree().remove();
}
}
}
@Test
public void testAddRepoPolicyWithAcContent() throws Exception {
NodeUtil acl = createAcl();
NodeUtil ace = acl.getChild(aceName);
NodeUtil[] acContent = new NodeUtil[]{acl, ace, ace.getChild(REP_RESTRICTIONS)};
for (NodeUtil node : acContent) {
NodeUtil policy = node.addChild(REP_REPO_POLICY, NT_REP_ACL);
try {
root.commit();
fail("Adding an ACL below access control content should fail");
} catch (CommitFailedException e) {
// success
assertTrue(e.isConstraintViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
} finally {
policy.getTree().remove();
}
}
}
@Test
public void testAddAceWithAcContent() throws Exception {
NodeUtil acl = createAcl();
NodeUtil ace = acl.getChild(aceName);
NodeUtil[] acContent = new NodeUtil[]{ace, ace.getChild(REP_RESTRICTIONS)};
for (NodeUtil node : acContent) {
NodeUtil entry = node.addChild("invalidACE", NT_REP_DENY_ACE);
try {
root.commit();
fail("Adding an ACE below an ACE or restriction should fail");
} catch (CommitFailedException e) {
// success
assertTrue(e.isConstraintViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy/validAce"));
} finally {
entry.getTree().remove();
}
}
}
@Test
public void testAddRestrictionWithAcContent() throws Exception {
NodeUtil acl = createAcl();
NodeUtil ace = acl.getChild(aceName);
NodeUtil[] acContent = new NodeUtil[]{acl, ace.getChild(REP_RESTRICTIONS)};
for (NodeUtil node : acContent) {
NodeUtil entry = node.addChild("invalidRestriction", NT_REP_RESTRICTIONS);
try {
root.commit();
fail("Adding an ACE below an ACE or restriction should fail");
} catch (CommitFailedException e) {
// success
assertTrue(e.isConstraintViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
} finally {
entry.getTree().remove();
}
}
}
@Test
public void testAddIsolatedPolicy() throws Exception {
String[] policyNames = new String[]{"isolatedACL", REP_POLICY, REP_REPO_POLICY};
NodeUtil node = getTestRoot();
for (String policyName : policyNames) {
NodeUtil policy = node.addChild(policyName, NT_REP_ACL);
try {
root.commit();
fail("Writing an isolated ACL without the parent being rep:AccessControllable should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot"));
} finally {
// revert pending changes that cannot be saved.
policy.getTree().remove();
}
}
}
@Test
public void testAddIsolatedAce() throws Exception {
String[] ntNames = new String[]{NT_REP_DENY_ACE, NT_REP_GRANT_ACE};
NodeUtil node = getTestRoot();
for (String aceNtName : ntNames) {
NodeUtil ace = createACE(node, "isolatedACE", aceNtName, testPrincipal.getName(), PrivilegeConstants.JCR_READ);
try {
root.commit();
fail("Writing an isolated ACE should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/isolatedACE"));
} finally {
// revert pending changes that cannot be saved.
ace.getTree().remove();
}
}
}
@Test
public void testAddIsolatedRestriction() throws Exception {
NodeUtil node = getTestRoot();
NodeUtil restriction = node.addChild("isolatedRestriction", NT_REP_RESTRICTIONS);
try {
root.commit();
fail("Writing an isolated Restriction should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot"));
} finally {
// revert pending changes that cannot be saved.
restriction.getTree().remove();
}
}
@Test
public void testInvalidPrivilege() throws Exception {
NodeUtil acl = createAcl();
String privName = "invalidPrivilegeName";
createACE(acl, "invalid", NT_REP_GRANT_ACE, testPrincipal.getName(), privName);
try {
root.commit();
fail("Creating an ACE with invalid privilege should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
}
}
@Test
public void testAbstractPrivilege() throws Exception {
PrivilegeManager pMgr = getPrivilegeManager(root);
pMgr.registerPrivilege("abstractPrivilege", true, new String[0]);
NodeUtil acl = createAcl();
createACE(acl, "invalid", NT_REP_GRANT_ACE, testPrincipal.getName(), "abstractPrivilege");
try {
root.commit();
fail("Creating an ACE with an abstract privilege should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
}
}
@Test
public void testInvalidRestriction() throws Exception {
NodeUtil restriction = createAcl().getChild(aceName).getChild(REP_RESTRICTIONS);
restriction.setString("invalid", "value");
try {
root.commit();
fail("Creating an unsupported restriction should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
}
}
@Test
public void testRestrictionWithInvalidType() throws Exception {
NodeUtil restriction = createAcl().getChild(aceName).getChild(REP_RESTRICTIONS);
restriction.setName(REP_GLOB, "rep:glob");
try {
root.commit();
fail("Creating restriction with invalid type should fail.");
} catch (CommitFailedException e) {
// success
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy"));
}
}
@Test
public void testDuplicateAce() throws Exception {
AccessControlManager acMgr = getAccessControlManager(root);
JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, testPath);
acl.addAccessControlEntry(testPrincipal, privilegesFromNames(PrivilegeConstants.JCR_ADD_CHILD_NODES));
acMgr.setPolicy(testPath, acl);
// add duplicate ac-entry on OAK-API
NodeUtil policy = new NodeUtil(root.getTree(testPath + "/rep:policy"));
NodeUtil ace = policy.addChild("duplicateAce", NT_REP_GRANT_ACE);
ace.setString(REP_PRINCIPAL_NAME, testPrincipal.getName());
ace.setNames(AccessControlConstants.REP_PRIVILEGES, PrivilegeConstants.JCR_ADD_CHILD_NODES);
try {
root.commit();
fail("Creating duplicate ACE must be detected");
} catch (CommitFailedException e) {
assertTrue(e.isAccessControlViolation());
assertThat(e.getMessage(), containsString("/testRoot/rep:policy/duplicateAce"));
}
}
@Test
public void testAceDifferentByRestrictionValue() throws Exception {
ValueFactory vf = getValueFactory(root);
AccessControlManager acMgr = getAccessControlManager(root);
JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, testPath);
acl.addEntry(testPrincipal, privilegesFromNames(PrivilegeConstants.JCR_ADD_CHILD_NODES), true,
ImmutableMap.<String, Value>of(),
ImmutableMap.of(AccessControlConstants.REP_NT_NAMES, new Value[] {vf.createValue(NodeTypeConstants.NT_OAK_UNSTRUCTURED, PropertyType.NAME)}));
// add ac-entry that only differs by the value of the restriction
acl.addEntry(testPrincipal, privilegesFromNames(PrivilegeConstants.JCR_ADD_CHILD_NODES), true,
ImmutableMap.<String, Value>of(),
ImmutableMap.of(AccessControlConstants.REP_NT_NAMES, new Value[] {vf.createValue(NodeTypeConstants.NT_UNSTRUCTURED, PropertyType.NAME)}));
assertEquals(2, acl.getAccessControlEntries().length);
acMgr.setPolicy(testPath, acl);
// persisting changes must succeed; the 2 ac-entries must not be treated as equal.
root.commit();
}
@Test
public void hiddenNodeAdded() throws CommitFailedException {
AccessControlValidatorProvider provider = createValidatorProvider();
MemoryNodeStore store = new MemoryNodeStore();
NodeState root = store.getRoot();
NodeBuilder builder = root.builder();
NodeBuilder test = builder.child("test");
NodeBuilder hidden = test.child(":hidden");
Validator validator = provider.getRootValidator(
root, builder.getNodeState(), CommitInfo.EMPTY);
Validator childValidator = validator.childNodeAdded(
"test", test.getNodeState());
assertNotNull(childValidator);
Validator hiddenValidator = childValidator.childNodeAdded(":hidden", hidden.getNodeState());
assertNull(hiddenValidator);
}
@Test
public void hiddenNodeChanged() throws CommitFailedException {
AccessControlValidatorProvider provider = createValidatorProvider();
MemoryNodeStore store = new MemoryNodeStore();
NodeBuilder builder = store.getRoot().builder();
builder.child("test").child(":hidden");
NodeState root = builder.getNodeState();
NodeBuilder test = root.builder().child("test");
NodeBuilder hidden = test.child(":hidden");
hidden.child("added");
Validator validator = provider.getRootValidator(
root, builder.getNodeState(), CommitInfo.EMPTY);
Validator childValidator = validator.childNodeChanged(
"test", root.getChildNode("test"), test.getNodeState());
assertNotNull(childValidator);
Validator hiddenValidator = childValidator.childNodeChanged(":hidden", root.getChildNode("test").getChildNode(":hidden"), hidden.getNodeState());
assertNull(hiddenValidator);
}
@Test
public void hiddenNodeDeleted() throws CommitFailedException {
AccessControlValidatorProvider provider = createValidatorProvider();
MemoryNodeStore store = new MemoryNodeStore();
NodeBuilder builder = store.getRoot().builder();
builder.child("test").child(":hidden");
NodeState root = builder.getNodeState();
builder = root.builder();
NodeBuilder test = builder.child("test");
test.child(":hidden").remove();
Validator validator = provider.getRootValidator(
root, builder.getNodeState(), CommitInfo.EMPTY);
Validator childValidator = validator.childNodeChanged("test", root.getChildNode("test"), test.getNodeState());
assertNotNull(childValidator);
Validator hiddenValidator = childValidator.childNodeDeleted(
":hidden", root.getChildNode("test").getChildNode(":hidden"));
assertNull(hiddenValidator);
}
}