blob: 29c310e0463200185dab1bcd8691753d10ee73cf [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.jcr.security.authorization;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.Arrays;
import java.util.List;
import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.test.AbstractJCRTest;
import org.jetbrains.annotations.Nullable;
public class AccessControlImporterTest extends AbstractJCRTest {
private static final String VALID_PRINCIPAL_NAME = "anonymous";
public static final String XML_POLICY_TREE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"test\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
" <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>nt:unstructured</sv:value></sv:property>" +
" <sv:property sv:name=\"jcr:mixinTypes\" sv:type=\"Name\">" +
" <sv:value>rep:AccessControllable</sv:value>" +
" </sv:property>" +
" <sv:node sv:name=\"rep:policy\">" +
" <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:ACL</sv:value></sv:property>" +
" <sv:node sv:name=\"allow\">" +
" <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
" <sv:value>rep:GrantACE</sv:value>" +
" </sv:property>" +
" <sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
" <sv:value>"+EveryonePrincipal.NAME+"</sv:value>" +
" </sv:property>" +
" <sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
" <sv:value>jcr:write</sv:value>" +
" </sv:property>" +
" </sv:node>" +
" </sv:node>" +
"</sv:node>";
public static final String XML_POLICY_TREE_2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"rep:policy\" " +
"xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:ACL</sv:value>" +
"</sv:property>" +
"<sv:node sv:name=\"allow\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:GrantACE</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
"<sv:value>"+EveryonePrincipal.NAME+"</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
"<sv:value>jcr:write</sv:value>" +
"</sv:property>" +
"</sv:node>" +
"</sv:node>";
public static final String XML_POLICY_TREE_3 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"rep:policy\" " +
"xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:ACL</sv:value>" +
"</sv:property>" +
"<sv:node sv:name=\"allow\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:GrantACE</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
"<sv:value>"+EveryonePrincipal.NAME+"</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
"<sv:value>jcr:write</sv:value>" +
"</sv:property>" +
"</sv:node>" +
"<sv:node sv:name=\"allow0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:GrantACE</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
"<sv:value>"+VALID_PRINCIPAL_NAME+"</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
"<sv:value>jcr:write</sv:value>" +
"</sv:property>" +
"</sv:node>" +
"</sv:node>";
public static final String XML_POLICY_TREE_5 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"rep:policy\" " +
"xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:ACL</sv:value>" +
"</sv:property>" +
"<sv:node sv:name=\"allow0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:GrantACE</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
"<sv:value>"+VALID_PRINCIPAL_NAME+"</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
"<sv:value>jcr:write</sv:value>" +
"</sv:property>" +
"</sv:node>" +
"</sv:node>";
public static final String XML_REPO_POLICY_TREE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"rep:repoPolicy\" " +
"xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:ACL</sv:value>" +
"</sv:property>" +
"<sv:node sv:name=\"allow\">" +
"<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
"<sv:value>rep:GrantACE</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
"<sv:value>"+VALID_PRINCIPAL_NAME+"</sv:value>" +
"</sv:property>" +
"<sv:property sv:name=\"rep:privileges\" sv:type=\"Name\">" +
"<sv:value>jcr:workspaceManagement</sv:value>" +
"</sv:property>" +
"</sv:node>" +
"</sv:node>";
public static final String XML_POLICY_ONLY = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<sv:node sv:name=\"test\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
" <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>nt:unstructured</sv:value></sv:property>" +
" <sv:property sv:name=\"jcr:mixinTypes\" sv:type=\"Name\">" +
" <sv:value>rep:AccessControllable</sv:value>" +
" </sv:property>" +
" <sv:node sv:name=\"rep:policy\">" +
" <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:ACL</sv:value></sv:property>" +
" </sv:node>" +
"</sv:node>";
protected void doImport(String parentPath, String xml) throws IOException, RepositoryException {
InputStream in = new ByteArrayInputStream(xml.getBytes("UTF-8"));
if (isSessionImport()) {
superuser.importXML(parentPath, in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
} else {
superuser.save();
superuser.getWorkspace().importXML(parentPath, in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
}
}
protected boolean isSessionImport() {
return true;
}
private Node createImportTarget() throws RepositoryException {
Node target = testRootNode.addNode(nodeName1);
target.addMixin("rep:AccessControllable");
if (!isSessionImport()) {
superuser.save();
}
return target;
}
private Node createImportTargetWithPolicy(@Nullable Principal principal) throws RepositoryException {
Node target = testRootNode.addNode("test", "test:sameNameSibsFalseChildNodeDefinition");
AccessControlManager acMgr = superuser.getAccessControlManager();
for (AccessControlPolicyIterator it = acMgr.getApplicablePolicies(target.getPath()); it.hasNext(); ) {
AccessControlPolicy policy = it.nextAccessControlPolicy();
if (policy instanceof AccessControlList) {
if (principal != null) {
Privilege[] privs = new Privilege[]{acMgr.privilegeFromName(Privilege.JCR_LOCK_MANAGEMENT)};
((AccessControlList) policy).addAccessControlEntry(principal, privs);
}
acMgr.setPolicy(target.getPath(), policy);
}
}
if (!isSessionImport()) {
superuser.save();
}
return target;
}
/**
* Imports a resource-based ACL containing a single entry.
*
* @throws Exception
*/
public void testImportACL() throws Exception {
try {
Node target = testRootNode;
doImport(target.getPath(), XML_POLICY_TREE);
assertTrue(target.hasNode("test"));
String path = target.getNode("test").getPath();
AccessControlManager acMgr = superuser.getAccessControlManager();
AccessControlPolicy[] policies = acMgr.getPolicies(path);
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(1, entries.length);
AccessControlEntry entry = entries[0];
assertEquals(EveryonePrincipal.NAME, entry.getPrincipal().getName());
assertEquals(1, entry.getPrivileges().length);
assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
if (entry instanceof JackrabbitAccessControlEntry) {
assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
}
} finally {
superuser.refresh(false);
}
}
public void testImportACLOnly() throws Exception {
try {
Node target = createImportTarget();
doImport(target.getPath(), XML_POLICY_TREE_3);
String path = target.getPath();
AccessControlManager acMgr = superuser.getAccessControlManager();
AccessControlPolicy[] policies = acMgr.getPolicies(path);
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(2, entries.length);
AccessControlEntry entry = entries[0];
assertEquals(EveryonePrincipal.NAME, entry.getPrincipal().getName());
assertEquals(1, entry.getPrivileges().length);
assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
entry = entries[1];
assertEquals(VALID_PRINCIPAL_NAME, entry.getPrincipal().getName());
assertEquals(1, entry.getPrivileges().length);
assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
if (entry instanceof JackrabbitAccessControlEntry) {
assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
}
} finally {
superuser.refresh(false);
}
}
public void testImportACLRemoveACE() throws Exception {
try {
Node target = createImportTarget();
doImport(target.getPath(), XML_POLICY_TREE_3);
doImport(target.getPath(), XML_POLICY_TREE_5);
String path = target.getPath();
AccessControlManager acMgr = superuser.getAccessControlManager();
AccessControlPolicy[] policies = acMgr.getPolicies(path);
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(1, entries.length);
AccessControlEntry entry = entries[0];
assertEquals(VALID_PRINCIPAL_NAME, entry.getPrincipal().getName());
assertEquals(1, entry.getPrivileges().length);
assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
if (entry instanceof JackrabbitAccessControlEntry) {
assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
}
} finally {
superuser.refresh(false);
}
}
/**
* Imports a resource-based ACL containing a single entry for a policy that
* already exists: expected outcome its that the existing ACE is replaced.
*/
public void testImportPolicyExists() throws Exception {
try {
Node target = createImportTargetWithPolicy(EveryonePrincipal.getInstance());
doImport(target.getPath(), XML_POLICY_TREE_2);
AccessControlManager acMgr = superuser.getAccessControlManager();
AccessControlPolicy[] policies = acMgr.getPolicies(target.getPath());
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(1, entries.length);
AccessControlEntry entry = entries[0];
assertEquals(EveryonePrincipal.getInstance(), entry.getPrincipal());
List<Privilege> privs = Arrays.asList(entry.getPrivileges());
assertEquals(1, privs.size());
assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
if (entry instanceof JackrabbitAccessControlEntry) {
assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
}
} finally {
superuser.refresh(false);
}
}
/**
* Imports an empty resource-based ACL for a policy that already exists.
*
* @throws Exception
*/
public void testImportEmptyExistingPolicy() throws Exception {
try {
Node target = createImportTargetWithPolicy(null);
doImport(target.getPath(), XML_POLICY_ONLY);
AccessControlPolicy[] policies = superuser.getAccessControlManager().getPolicies(target.getPath());
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(0, entries.length);
} finally {
superuser.refresh(false);
}
}
/**
* Repo level acl must be imported underneath the root node.
*
* @throws Exception
*/
public void testImportRepoACLAtRoot() throws Exception {
Node target = superuser.getRootNode();
AccessControlManager acMgr = superuser.getAccessControlManager();
try {
// need to add mixin. in contrast to only using JCR API to retrieve
// and set the policies the protected item import only is called if
// the node to be imported is defined to be protected. however, if
// the root node doesn't have the mixin assigned the defining node
// type of the imported policy nodes will be rep:root (unstructured)
// and the items will not be detected as being protected.
target.addMixin("rep:RepoAccessControllable");
if (!isSessionImport()) {
superuser.save();
}
doImport(target.getPath(), XML_REPO_POLICY_TREE);
AccessControlPolicy[] policies = acMgr.getPolicies(null);
assertEquals(1, policies.length);
assertTrue(policies[0] instanceof JackrabbitAccessControlList);
AccessControlEntry[] entries = ((JackrabbitAccessControlList) policies[0]).getAccessControlEntries();
assertEquals(1, entries.length);
assertEquals(1, entries[0].getPrivileges().length);
assertEquals(acMgr.privilegeFromName("jcr:workspaceManagement"), entries[0].getPrivileges()[0]);
assertTrue(target.hasNode("rep:repoPolicy"));
assertTrue(target.hasNode("rep:repoPolicy/allow"));
// clean up again
acMgr.removePolicy(null, policies[0]);
assertFalse(target.hasNode("rep:repoPolicy"));
assertFalse(target.hasNode("rep:repoPolicy/allow"));
} finally {
if (isSessionImport()) {
superuser.refresh(false);
} else {
superuser.save();
}
assertEquals(0, acMgr.getPolicies(null).length);
}
}
/**
* Make sure repo-level acl is not imported below any other node than the
* root node.
*
* @throws Exception
*/
public void testImportRepoACLAtTestNode() throws Exception {
try {
Node target = testRootNode.addNode("test");
target.addMixin("rep:RepoAccessControllable");
doImport(target.getPath(), XML_REPO_POLICY_TREE);
assertTrue(target.hasNode("rep:repoPolicy"));
assertFalse(target.hasNode("rep:repoPolicy/allow0"));
Node n = target.getNode("rep:repoPolicy");
assertEquals("rep:RepoAccessControllable", n.getDefinition().getDeclaringNodeType().getName());
try {
superuser.save();
fail("Importing repo policy to non-root node must fail");
} catch (AccessControlException e) {
// success
}
} finally {
superuser.refresh(false);
}
}
}