| /* |
| * 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); |
| } |
| } |
| } |