/*
* 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.test.api.security;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.Repository;
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.test.NotExecutableException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <code>AccessControlEntryTest</code>...
*/
public class AccessControlListTest extends AbstractAccessControlTest {
private static Logger log = LoggerFactory.getLogger(AccessControlListTest.class);
private String path;
private Privilege[] privs;
private Principal testPrincipal;
private List privilegesToRestore = new ArrayList();
protected void setUp() throws Exception {
checkSupportedOption(Repository.OPTION_ACCESS_CONTROL_SUPPORTED);
super.setUp();
// TODO: make sure, entries to ADD are not present yet.
try {
// TODO: retrieve targetPath from configuration
Node n = testRootNode.addNode(nodeName1, testNodeType);
superuser.save();
path = n.getPath();
privs = acMgr.getSupportedPrivileges(path);
if (privs.length == 0) {
throw new NotExecutableException("No supported privileges at absPath " + path);
}
testPrincipal = getHelper().getKnownPrincipal(superuser);
// remember existing entries for test-principal -> later restore.
privilegesToRestore = currentPrivileges(getList(acMgr, path), testPrincipal);
} catch (Exception e) {
superuser.logout();
throw e;
}
}
protected void tearDown() throws Exception {
try {
// restore original entries (remove others).
AccessControlList list = getList(acMgr, path);
AccessControlEntry[] entries = list.getAccessControlEntries();
for (int i = 0; i < entries.length; i++) {
AccessControlEntry ace = entries[i];
if (testPrincipal.equals(ace.getPrincipal())) {
list.removeAccessControlEntry(ace);
}
}
if (!privilegesToRestore.isEmpty()) {
list.addAccessControlEntry(testPrincipal, (Privilege[]) privilegesToRestore.toArray(new Privilege[privilegesToRestore.size()]));
}
if (list.getAccessControlEntries().length > 0 && acMgr.getPolicies(path).length > 0) {
acMgr.setPolicy(path, list);
superuser.save();
}
} catch (Exception e) {
log.warn("Unexpected error while removing test entries.", e);
}
super.tearDown();
}
private static AccessControlList getList(AccessControlManager acMgr, String path)
throws NotExecutableException, AccessDeniedException, RepositoryException {
for (AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path); it.hasNext();) {
AccessControlPolicy acp = it.nextAccessControlPolicy();
if (acp instanceof AccessControlList) {
return (AccessControlList) acp;
}
}
AccessControlPolicy[] acps = acMgr.getPolicies(path);
for (int i = 0; i < acps.length; i++) {
if (acps[i] instanceof AccessControlList) {
return (AccessControlList) acps[i] ;
}
}
throw new NotExecutableException("No AccessControlList at " + path);
}
private static List currentPrivileges(AccessControlList acl, Principal principal) throws RepositoryException {
List privileges = new ArrayList();
AccessControlEntry[] entries = acl.getAccessControlEntries();
for (int i = 0; i < entries.length; i++) {
AccessControlEntry ace = entries[i];
if (principal.equals(ace.getPrincipal())) {
privileges.addAll(Arrays.asList(ace.getPrivileges()));
}
}
return privileges;
}
public void testGetAccessControlEntries() throws RepositoryException, AccessDeniedException, NotExecutableException {
checkCanReadAc(path);
AccessControlList acl = getList(acMgr, path);
// call must succeed.
AccessControlEntry[] entries = acl.getAccessControlEntries();
assertNotNull("AccessControlList#getAccessControlEntries must not return null.", entries);
for (int i = 0; i < entries.length; i++) {
assertNotNull("An ACE must contain a principal", entries[i].getPrincipal());
Privilege[] privs = entries[i].getPrivileges();
assertTrue("An ACE must contain at least a single privilege", privs != null && privs.length > 0);
}
}
public void testAddAccessControlEntry() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
Privilege[] privileges = new Privilege[] {privs[0]};
AccessControlList acl = getList(acMgr, path);
AccessControlEntry entry = null;
if (acl.addAccessControlEntry(testPrincipal, privileges)) {
AccessControlEntry[] aces = acl.getAccessControlEntries();
for (int i = 0; i < aces.length; i++) {
if (aces[i].getPrincipal().equals(testPrincipal) &&
Arrays.asList(privileges).equals(Arrays.asList(aces[i].getPrivileges()))) {
entry = aces[i];
}
}
if (entry == null) throw new NotExecutableException();
} else {
throw new NotExecutableException();
}
assertEquals("Principal name of the ACE must be equal to the name of the passed Principal", testPrincipal.getName(), entry.getPrincipal().getName());
assertEquals("Privileges of the ACE must be equal to the passed ones", Arrays.asList(privileges), Arrays.asList(entry.getPrivileges()));
}
public void testAddAggregatePrivilege() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
Privilege aggregate = null;
for (int i = 0; i < privs.length; i++) {
if (privs[i].isAggregate()) {
aggregate = privs[i];
break;
}
}
if (aggregate == null) {
throw new NotExecutableException("No aggregate privilege supported at " + path);
}
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, new Privilege[] {aggregate});
// make sure all privileges are present now
List privs = currentPrivileges(acl, testPrincipal);
assertTrue("Privileges added through 'addAccessControlEntry' must be " +
"reflected upon getAccessControlEntries",
privs.contains(aggregate) || privs.containsAll(Arrays.asList(aggregate.getAggregatePrivileges())));
}
public void testAddAggregatedPrivilegesSeparately() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
Privilege aggregate = null;
for (int i = 0; i < privs.length; i++) {
if (privs[i].isAggregate()) {
aggregate = privs[i];
break;
}
}
if (aggregate == null) {
throw new NotExecutableException("No aggregate privilege supported at " + path);
}
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, new Privilege[] {aggregate});
Privilege[] privs = aggregate.getAggregatePrivileges();
for (int i = 0; i < privs.length; i++) {
boolean modified = acl.addAccessControlEntry(testPrincipal, new Privilege[] {privs[i]});
assertFalse("Adding the aggregated privs individually later on must not modify the policy", modified);
}
}
public void testAddAbstractPrivilege() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
Privilege abstractPriv = null;
Privilege[] allPrivs = acMgr.privilegeFromName(Privilege.JCR_ALL).getAggregatePrivileges();
for (int i = 0; i < allPrivs.length; i++) {
if (allPrivs[i].isAbstract()) {
abstractPriv = allPrivs[i];
break;
}
}
if (abstractPriv == null) {
throw new NotExecutableException("No abstract privilege found.");
}
AccessControlList acl = getList(acMgr, path);
try {
acl.addAccessControlEntry(testPrincipal, new Privilege[] {abstractPriv});
fail("Adding an ACE with an abstract privilege must fail.");
} catch (AccessControlException e) {
// success
}
}
public void testAddPrivilegesPresentInEntries() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, privs);
Set assignedPrivs = new HashSet();
AccessControlEntry[] entries = acl.getAccessControlEntries();
for (int i = 0; i < entries.length; i++) {
if (entries[i].getPrincipal().equals(testPrincipal)) {
Privilege[] prvs = entries[i].getPrivileges();
for (int j = 0; j < prvs.length; j++) {
if (prvs[j].isAggregate()) {
assignedPrivs.addAll(Arrays.asList(prvs[j].getAggregatePrivileges()));
} else {
assignedPrivs.add(prvs[j]);
}
}
}
}
Set expected = new HashSet();
for (int i = 0; i < privs.length; i++) {
if (privs[i].isAggregate()) {
expected.addAll(Arrays.asList(privs[i].getAggregatePrivileges()));
} else {
expected.add(privs[i]);
}
}
assertTrue("getAccessControlEntries must contain an entry or entries that grant at least the added privileges.", assignedPrivs.containsAll(expected));
}
public void testAddAccessControlEntryAndSetPolicy() throws RepositoryException, NotExecutableException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
List originalAces = Arrays.asList(acl.getAccessControlEntries());
if (!acl.addAccessControlEntry(testPrincipal, privs)) {
throw new NotExecutableException();
}
// re-access ACL from AC-Manager -> must not yet have changed
assertEquals("Before calling setPolicy any modifications to an ACL must not be reflected in the policies", originalAces, Arrays.asList(getList(acMgr, path).getAccessControlEntries()));
// setting the modified policy -> policy must change.
acMgr.setPolicy(path, acl);
assertEquals("Before calling setPolicy any modifications to an ACL must not be reflected in the policies", Arrays.asList(acl.getAccessControlEntries()), Arrays.asList(getList(acMgr, path).getAccessControlEntries()));
}
public void testAddAccessControlEntryIsTransient() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
List originalAces = Arrays.asList(acl.getAccessControlEntries());
if (!acl.addAccessControlEntry(testPrincipal, privs)) {
throw new NotExecutableException();
}
// set the policy (see #testAddAccessControlEntryAndSetPolicy)
acMgr.setPolicy(path, acl);
// revert the changes made
superuser.refresh(false);
assertEquals("After calling Session.refresh() any changes to a nodes policies must be reverted.", originalAces, Arrays.asList(getList(acMgr, path).getAccessControlEntries()));
}
public void testAddAccessControlEntryInvalidPrincipal() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
try {
Principal invalidPrincipal = getHelper().getUnknownPrincipal(superuser);
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(invalidPrincipal, privs);
fail("Adding an entry with an unknown principal must throw AccessControlException.");
} catch (AccessControlException e) {
// success.
} finally {
superuser.refresh(false);
}
}
public void testAddAccessControlEntryEmptyPrivilegeArray() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
try {
Privilege[] invalidPrivs = new Privilege[0];
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, invalidPrivs);
fail("Adding an entry with an invalid privilege array must throw AccessControlException.");
} catch (AccessControlException e) {
// success.
} finally {
superuser.refresh(false);
}
}
public void testAddAccessControlEntryInvalidPrivilege() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
try {
Privilege[] invalidPrivs = new Privilege[] {new Privilege() {
public String getName() {
return null;
}
public boolean isAbstract() {
return false;
}
public boolean isAggregate() {
return false;
}
public Privilege[] getDeclaredAggregatePrivileges() {
return new Privilege[0];
}
public Privilege[] getAggregatePrivileges() {
return new Privilege[0];
}
}};
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, invalidPrivs);
fail("Adding an entry with an invalid privilege must throw AccessControlException.");
} catch (AccessControlException e) {
// success.
} finally {
superuser.refresh(false);
}
}
public void testRemoveAccessControlEntry() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
AccessControlEntry[] entries = acl.getAccessControlEntries();
if (entries.length > 0) {
AccessControlEntry ace = entries[0];
acl.removeAccessControlEntry(ace);
// retrieve entries again:
List remainingEntries = Arrays.asList(acl.getAccessControlEntries());
assertFalse("AccessControlList.getAccessControlEntries still returns a removed ACE.", remainingEntries.contains(ace));
}
}
public void testRemoveAddedAccessControlEntry() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
acl.addAccessControlEntry(testPrincipal, privs);
AccessControlEntry[] aces = acl.getAccessControlEntries();
for (int i = 0; i < aces.length; i++) {
acl.removeAccessControlEntry(aces[i]);
}
assertEquals("After removing all ACEs the ACL must be empty", 0, acl.getAccessControlEntries().length);
}
public void testRemoveAccessControlEntryAndSetPolicy() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
// add a new ACE that can be removed later on.
AccessControlList acl = getList(acMgr, path);
if (!acl.addAccessControlEntry(testPrincipal, privs)) {
throw new NotExecutableException();
} else {
acMgr.setPolicy(path, acl);
}
// try to re-access the modifiable ACL in order to remove the ACE
// added before.
acl = getList(acMgr, path);
AccessControlEntry ace = null;
AccessControlEntry[] aces = acl.getAccessControlEntries();
if (aces.length == 0) {
throw new NotExecutableException();
} else {
ace = aces[0];
acl.removeAccessControlEntry(ace);
}
// before setting the policy again -> no changes visible.
assertEquals("Removal of an ACE must only be visible upon 'setPolicy'", Arrays.asList(aces), Arrays.asList(getList(acMgr, path).getAccessControlEntries()));
// set policy again.
acMgr.setPolicy(path, acl);
assertEquals("After 'setPolicy' the ACE-removal must be visible to the editing session.", Arrays.asList(acl.getAccessControlEntries()), Arrays.asList(getList(acMgr, path).getAccessControlEntries()));
}
public void testRemoveAccessControlEntryIsTransient() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
// make sure an ACE is present and modifications are persisted.
if (acl.addAccessControlEntry(testPrincipal, privs)) {
acMgr.setPolicy(path, acl);
superuser.save();
} else {
throw new NotExecutableException();
}
// retrieve ACL again -> transient removal of the ace
acl = getList(acMgr, path);
AccessControlEntry ace = acl.getAccessControlEntries()[0];
acl.removeAccessControlEntry(ace);
acMgr.setPolicy(path, acl);
// revert changes -> removed entry must be present again.
superuser.refresh(false);
List entries = Arrays.asList(getList(acMgr, path).getAccessControlEntries());
assertTrue("After reverting any changes the removed ACE should be present again.", entries.contains(ace));
}
public void testRemoveIllegalAccessControlEntry() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
try {
AccessControlEntry entry = new AccessControlEntry() {
public Principal getPrincipal() {
return testPrincipal;
}
public Privilege[] getPrivileges() {
return privs;
}
};
AccessControlList acl = getList(acMgr, path);
acl.removeAccessControlEntry(entry);
fail("AccessControlManager.removeAccessControlEntry with an unknown entry must throw AccessControlException.");
} catch (AccessControlException e) {
// ok
}
}
public void testAddAccessControlEntryTwice() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList acl = getList(acMgr, path);
if (acl.addAccessControlEntry(testPrincipal, privs)) {
assertFalse("Adding the same ACE twice should not modify the AC-List.",
acl.addAccessControlEntry(testPrincipal, privs));
}
}
public void testAddAccessControlEntryAgain() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
AccessControlList list = getList(acMgr, path);
AccessControlEntry[] entries = list.getAccessControlEntries();
if (entries.length > 0) {
assertFalse("Adding an existing entry again must not modify the AC-List",
list.addAccessControlEntry(entries[0].getPrincipal(), entries[0].getPrivileges()));
} else {
throw new NotExecutableException();
}
}
public void testExtendPrivileges() throws NotExecutableException, RepositoryException {
checkCanModifyAc(path);
// search 2 non-aggregated privileges
List twoPrivs = new ArrayList(2);
for (int i = 0; i < privs.length && twoPrivs.size() < 2; i++) {
if (!privs[i].isAggregate()) {
twoPrivs.add(privs[i]);
}
}
if (twoPrivs.size() < 2) {
throw new NotExecutableException("At least 2 supported, non-aggregate privileges required at " + path);
}
AccessControlList acl = getList(acMgr, path);
Privilege privilege = (Privilege) twoPrivs.get(0);
// add first privilege:
acl.addAccessControlEntry(testPrincipal, new Privilege[] {privilege});
// add a second privilege (but not specifying the privilege added before)
// -> the first privilege must not be removed.
Privilege privilege2 = (Privilege) twoPrivs.get(1);
acl.addAccessControlEntry(testPrincipal, new Privilege[] {privilege2});
List currentPrivileges = currentPrivileges(acl, testPrincipal);
assertTrue("'AccessControlList.addAccessControlEntry' must not remove privileges added before", currentPrivileges.containsAll(twoPrivs));
}
}