blob: 95c3dc38cb9568f3583864addbc73496b7e7c351 [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.privilege;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.Privilege;
import com.google.common.collect.ImmutableSet;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.jcr.Jcr;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest.dispose;
/**
* Test privilege registration.
*/
public class PrivilegeRegistrationTest extends AbstractPrivilegeTest {
private Repository repository;
private Session session;
private PrivilegeManager privilegeManager;
@Before
public void setUp() throws Exception {
super.setUp();
// create a separate repository in order to be able to remove registered privileges.
repository = new Jcr().createRepository();
session = getAdminSession();
privilegeManager = getPrivilegeManager(session);
// make sure the guest session has read access
try {
AccessControlUtils.addAccessControlEntry(session, "/", EveryonePrincipal.getInstance(), new String[]{Privilege.JCR_READ}, true);
session.save();
} catch (RepositoryException e) {
// failed to initialize
}
}
@After
public void tearDown() throws Exception {
try {
super.tearDown();
} finally {
session.logout();
repository = dispose(repository);
privilegeManager = null;
}
}
private Session getReadOnlySession() throws RepositoryException {
return repository.login(getHelper().getReadOnlyCredentials());
}
private Session getAdminSession() throws RepositoryException {
return repository.login(getHelper().getSuperuserCredentials());
}
@Test
public void testRegisterPrivilegeWithReadOnly() throws RepositoryException {
Session readOnly = getReadOnlySession();
try {
getPrivilegeManager(readOnly).registerPrivilege("test", true, new String[0]);
fail("Only admin is allowed to register privileges.");
} catch (AccessDeniedException e) {
// success
} finally {
readOnly.logout();
}
}
@Test
public void testCustomDefinitionsWithCyclicReferences() throws RepositoryException {
try {
privilegeManager.registerPrivilege("cycl-1", false, new String[] {"cycl-1"});
fail("Cyclic definitions must be detected upon registration.");
} catch (RepositoryException e) {
// success
}
}
@Test
public void testCustomEquivalentDefinitions() throws RepositoryException {
privilegeManager.registerPrivilege("custom4", false, new String[0]);
privilegeManager.registerPrivilege("custom5", false, new String[0]);
privilegeManager.registerPrivilege("custom2", false, new String[]{"custom4", "custom5"});
List<String[]> equivalent = new ArrayList<String[]>();
equivalent.add(new String[]{"custom4", "custom5"});
equivalent.add(new String[] {"custom2", "custom4"});
equivalent.add(new String[]{"custom2", "custom5"});
int cnt = 6;
for (String[] aggrNames : equivalent) {
try {
// the equivalent definition to 'custom1'
String name = "custom"+(cnt++);
privilegeManager.registerPrivilege(name, false, aggrNames);
fail("Equivalent '"+name+"' definitions must be detected.");
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterBuiltInPrivilege() throws RepositoryException {
Map<String, String[]> builtIns = new HashMap<String, String[]>();
builtIns.put(PrivilegeConstants.JCR_READ, new String[0]);
builtIns.put(PrivilegeConstants.JCR_LIFECYCLE_MANAGEMENT, new String[] {PrivilegeConstants.JCR_ADD_CHILD_NODES});
builtIns.put(PrivilegeConstants.REP_WRITE, new String[0]);
builtIns.put(PrivilegeConstants.JCR_ALL, new String[0]);
for (String builtInName : builtIns.keySet()) {
try {
privilegeManager.registerPrivilege(builtInName, false, builtIns.get(builtInName));
fail("Privilege name " +builtInName+ " already in use -> Exception expected");
} catch (RepositoryException e) {
// success
}
}
for (String builtInName : builtIns.keySet()) {
try {
privilegeManager.registerPrivilege(builtInName, true, builtIns.get(builtInName));
fail("Privilege name " +builtInName+ " already in use -> Exception expected");
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterInvalidNewAggregate() throws RepositoryException {
Map<String, String[]> newAggregates = new LinkedHashMap<String, String[]>();
// same as jcr:read
newAggregates.put("jcrReadAggregate", getAggregateNames(PrivilegeConstants.JCR_READ));
// aggregated combining built-in and an unknown privilege
newAggregates.put("newAggregate2", getAggregateNames(PrivilegeConstants.JCR_READ, "unknownPrivilege"));
// aggregate containing unknown privilege
newAggregates.put("newAggregate3", getAggregateNames("unknownPrivilege"));
// custom aggregated contains itself
newAggregates.put("newAggregate4", getAggregateNames("newAggregate"));
// same as rep:write
newAggregates.put("repWriteAggregate", getAggregateNames(PrivilegeConstants.JCR_MODIFY_PROPERTIES, PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_NODE_TYPE_MANAGEMENT, PrivilegeConstants.JCR_REMOVE_CHILD_NODES, PrivilegeConstants.JCR_REMOVE_NODE));
// aggregated combining built-in and unknown custom
newAggregates.put("newAggregate5", getAggregateNames(PrivilegeConstants.JCR_READ, "unknownPrivilege"));
for (String name : newAggregates.keySet()) {
try {
privilegeManager.registerPrivilege(name, true, newAggregates.get(name));
fail("New aggregate "+ name +" referring to unknown Privilege -> Exception expected");
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterInvalidNewAggregate2() throws RepositoryException {
Map<String, String[]> newCustomPrivs = new LinkedHashMap<String, String[]>();
newCustomPrivs.put("new", new String[0]);
newCustomPrivs.put("new2", new String[0]);
newCustomPrivs.put("new3", getAggregateNames("new", "new2"));
for (String name : newCustomPrivs.keySet()) {
boolean isAbstract = true;
String[] aggrNames = newCustomPrivs.get(name);
privilegeManager.registerPrivilege(name, isAbstract, aggrNames);
}
Map<String, String[]> newAggregates = new LinkedHashMap<String, String[]>();
// other illegal aggregates already represented by registered definition.
newAggregates.put("newA2", getAggregateNames("new"));
newAggregates.put("newA3", getAggregateNames("new2"));
for (String name : newAggregates.keySet()) {
boolean isAbstract = false;
String[] aggrNames = newAggregates.get(name);
try {
privilegeManager.registerPrivilege(name, isAbstract, aggrNames);
fail("Invalid aggregation in definition '"+ name.toString()+"' : Exception expected");
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterPrivilegeWithIllegalName() {
Map<String, String[]> illegal = new HashMap<String, String[]>();
// invalid privilege name
illegal.put(null, new String[0]);
illegal.put("", new String[0]);
illegal.put("invalid:privilegeName", new String[0]);
illegal.put(".e:privilegeName", new String[0]);
// invalid aggregate names
illegal.put("newPrivilege", new String[] {"invalid:privilegeName"});
illegal.put("newPrivilege", new String[] {".e:privilegeName"});
illegal.put("newPrivilege", new String[] {null});
illegal.put("newPrivilege", new String[] {""});
for (String illegalName : illegal.keySet()) {
try {
privilegeManager.registerPrivilege(illegalName, true, illegal.get(illegalName));
fail("Illegal name -> Exception expected");
} catch (NamespaceException e) {
// success
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterReservedName() {
Map<String, String[]> illegal = new HashMap<String, String[]>();
// invalid privilege name
illegal.put(null, new String[0]);
illegal.put("jcr:privilegeName", new String[0]);
illegal.put("rep:privilegeName", new String[0]);
illegal.put("nt:privilegeName", new String[0]);
illegal.put("mix:privilegeName", new String[0]);
illegal.put("sv:privilegeName", new String[0]);
illegal.put("xml:privilegeName", new String[0]);
illegal.put("xmlns:privilegeName", new String[0]);
// invalid aggregate names
illegal.put("newPrivilege", new String[] {"jcr:privilegeName"});
for (String illegalName : illegal.keySet()) {
try {
privilegeManager.registerPrivilege(illegalName, true, illegal.get(illegalName));
fail("Illegal name -> Exception expected");
} catch (RepositoryException e) {
// success
}
}
}
@Test
public void testRegisterCustomPrivileges() throws RepositoryException {
Workspace workspace = session.getWorkspace();
workspace.getNamespaceRegistry().registerNamespace("test", "http://www.apache.org/jackrabbit/test");
Map<String, String[]> newCustomPrivs = new HashMap<String, String[]>();
newCustomPrivs.put("new", new String[0]);
newCustomPrivs.put("test:new", new String[0]);
for (String name : newCustomPrivs.keySet()) {
boolean isAbstract = true;
String[] aggrNames = newCustomPrivs.get(name);
Privilege registered = privilegeManager.registerPrivilege(name, isAbstract, aggrNames);
// validate definition
Privilege privilege = privilegeManager.getPrivilege(name);
assertNotNull(privilege);
assertEquals(name, privilege.getName());
assertTrue(privilege.isAbstract());
assertEquals(0, privilege.getDeclaredAggregatePrivileges().length);
assertContainsDeclared(privilegeManager.getPrivilege(PrivilegeConstants.JCR_ALL), name);
}
Map<String, String[]> newAggregates = new HashMap<String, String[]>();
// a new aggregate of custom privileges
newAggregates.put("newA2", getAggregateNames("test:new", "new"));
// a new aggregate of custom and built-in privilege
newAggregates.put("newA1", getAggregateNames("new", PrivilegeConstants.JCR_READ));
// aggregating built-in privileges
newAggregates.put("aggrBuiltIn", getAggregateNames(PrivilegeConstants.JCR_MODIFY_PROPERTIES, PrivilegeConstants.JCR_READ));
for (String name : newAggregates.keySet()) {
boolean isAbstract = false;
String[] aggrNames = newAggregates.get(name);
privilegeManager.registerPrivilege(name, isAbstract, aggrNames);
Privilege p = privilegeManager.getPrivilege(name);
assertNotNull(p);
assertEquals(name, p.getName());
assertFalse(p.isAbstract());
for (String n : aggrNames) {
assertContainsDeclared(p, n);
}
assertContainsDeclared(privilegeManager.getPrivilege(PrivilegeConstants.JCR_ALL), name);
}
}
/**
* @since oak
*/
@Test
public void testRegisterCustomPrivilegesVisibleInContent() throws RepositoryException {
Workspace workspace = session.getWorkspace();
workspace.getNamespaceRegistry().registerNamespace("test", "http://www.apache.org/jackrabbit/test");
Map<String, String[]> newCustomPrivs = new HashMap<String, String[]>();
newCustomPrivs.put("new", new String[0]);
newCustomPrivs.put("test:new", new String[0]);
for (String name : newCustomPrivs.keySet()) {
boolean isAbstract = true;
String[] aggrNames = newCustomPrivs.get(name);
Privilege registered = privilegeManager.registerPrivilege(name, isAbstract, aggrNames);
Node privilegeRoot = session.getNode(PrivilegeConstants.PRIVILEGES_PATH);
assertTrue(privilegeRoot.hasNode(name));
Node privNode = privilegeRoot.getNode(name);
assertTrue(privNode.getProperty(PrivilegeConstants.REP_IS_ABSTRACT).getBoolean());
assertFalse(privNode.hasProperty(PrivilegeConstants.REP_AGGREGATES));
}
}
/**
* @since oak
*/
@Test
public void testCustomPrivilegeVisibleToNewSession() throws RepositoryException {
boolean isAbstract = false;
String privName = "testCustomPrivilegeVisibleToNewSession";
privilegeManager.registerPrivilege(privName, isAbstract, new String[0]);
Session s2 = getAdminSession();
try {
PrivilegeManager pm = getPrivilegeManager(s2);
Privilege priv = pm.getPrivilege(privName);
assertEquals(privName, priv.getName());
assertEquals(isAbstract, priv.isAbstract());
assertFalse(priv.isAggregate());
} finally {
s2.logout();
}
}
/**
* @since oak
*/
@Test
public void testCustomPrivilegeVisibleAfterRefresh() throws RepositoryException {
Session s2 = getAdminSession();
PrivilegeManager pm = getPrivilegeManager(s2);
try {
boolean isAbstract = false;
String privName = "testCustomPrivilegeVisibleAfterRefresh";
privilegeManager.registerPrivilege(privName, isAbstract, new String[0]);
// before refreshing: privilege not visible
try {
Privilege priv = pm.getPrivilege(privName);
fail("Custom privilege will show up after Session#refresh()");
} catch (AccessControlException e) {
// success
}
// latest after refresh privilege manager must be updated
s2.refresh(true);
Privilege priv = pm.getPrivilege(privName);
assertEquals(privName, priv.getName());
assertEquals(isAbstract, priv.isAbstract());
assertFalse(priv.isAggregate());
} finally {
s2.logout();
}
}
/**
* @since oak
*/
@Test
public void testRegisterPrivilegeWithPendingChanges() throws RepositoryException {
try {
session.getRootNode().addNode("test");
assertTrue(session.hasPendingChanges());
privilegeManager.registerPrivilege("new", true, new String[0]);
fail("Privileges may not be registered while there are pending changes.");
} catch (InvalidItemStateException e) {
// success
} finally {
superuser.refresh(false);
}
}
/**
* @see <a href="https://issues.apache.org/jira/browse/OAK-2015">OAK-2015</a>
*/
@Test
public void testJcrAllWithCustomPrivileges() throws Exception {
Node testNode = session.getRootNode().addNode("test");
String testPath = testNode.getPath();
AccessControlUtils.grantAllToEveryone(session, testPath);
session.save();
JackrabbitAccessControlManager acMgr = (JackrabbitAccessControlManager) session.getAccessControlManager();
Privilege[] allPrivileges = AccessControlUtils.privilegesFromNames(session, Privilege.JCR_ALL);
Set<Principal> principalSet = ImmutableSet.<Principal>of(EveryonePrincipal.getInstance());
assertTrue(acMgr.hasPrivileges(testPath, principalSet, allPrivileges));
privilegeManager.registerPrivilege("customPriv", false, null);
assertTrue(acMgr.hasPrivileges(testPath, principalSet, allPrivileges));
}
@Test
public void testRegisterPrivilegeAggregatingJcrAll() throws Exception {
privilegeManager.registerPrivilege("customPriv", false, null);
try {
privilegeManager.registerPrivilege("customPriv2", false, new String[]{"customPriv", Privilege.JCR_ALL});
fail("Aggregation containing jcr:all is invalid.");
} catch (RepositoryException e) {
// success
Throwable cause = e.getCause();
assertTrue(cause instanceof CommitFailedException);
assertEquals(53, ((CommitFailedException) cause).getCode());
} finally {
superuser.refresh(false);
}
}
}