/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jcr.security; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import javax.jcr.AccessDeniedException; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.security.AccessControlList; import javax.jcr.security.AccessControlManager; import javax.jcr.security.AccessControlPolicyIterator; import javax.jcr.security.Privilege; import org.hamcrest.Matcher; import org.hamcrest.core.IsNull; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.jcr.MultiUseAbstractTest; import org.modeshape.jcr.security.acl.Privileges; public class AccessControlManagerTest extends MultiUseAbstractTest { private AccessControlManager acm; private Privileges privileges; @BeforeClass public static final void beforeAll() throws Exception { MultiUseAbstractTest.beforeAll(); // Import the node types and the data ... registerNodeTypes("cars.cnd"); importContent("/", "io/cars-system-view-with-uuids.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); setPolicy("/", Privilege.JCR_ALL); setPolicy("/Cars/Luxury/Cadillac DTS", Privilege.JCR_READ, Privilege.JCR_WRITE, Privilege.JCR_MODIFY_ACCESS_CONTROL); setPolicy("/Cars/Luxury/", Privilege.JCR_READ, Privilege.JCR_MODIFY_ACCESS_CONTROL, Privilege.JCR_READ_ACCESS_CONTROL); setPolicy("/Cars/Sports/", Privilege.JCR_READ, Privilege.JCR_WRITE, Privilege.JCR_MODIFY_ACCESS_CONTROL); setPolicy("/Cars/Utility/Ford F-150/", Privilege.JCR_MODIFY_ACCESS_CONTROL, Privilege.JCR_READ_ACCESS_CONTROL); setPolicy("/Cars/Utility/", Privilege.JCR_READ_ACCESS_CONTROL); } @AfterClass public static final void afterAll() throws Exception { MultiUseAbstractTest.afterAll(); } @Override @Before public void beforeEach() throws Exception { super.beforeEach(); acm = session.getAccessControlManager(); privileges = new Privileges(session); } @Test public void testSecondSession() throws Exception { Session session2 = session.getRepository().login(); session2.logout(); } @Test public void shouldObtainAccessControlManager() throws Exception { assertTrue(acm != null); Matcher<AccessControlManager> m = IsNull.notNullValue(AccessControlManager.class); m.matches(session.getAccessControlManager()); } @Test public void testGetSupportedPrivileges() throws Exception { Privilege[] permissions = acm.getSupportedPrivileges("/"); assertEquals(privileges.listOfSupported().length, permissions.length); } @Test public void testPrivilegeForName() throws Exception { Privilege p = acm.privilegeFromName(Privilege.JCR_ALL); assertEquals("jcr:all", p.getName()); } // --------------- Testing access list -------------------------------------/ @Test public void shouldHaveReadPrivilege() throws Exception { Privilege[] privileges = acm.getPrivileges("/Cars/Luxury"); assertEquals("jcr:read", privileges[0].getName()); } @Test public void shoudlHaveReadWritePrivilege() throws Exception { Privilege[] privileges = acm.getPrivileges("/Cars/Luxury/Cadillac DTS"); assertTrue(contains("jcr:read", privileges)); assertTrue(contains("jcr:write", privileges)); } @Test public void shoudlDeriveAccessList() throws Exception { Privilege[] privileges = acm.getPrivileges("/Cars/Luxury/Lexus IS350"); assertEquals("jcr:read", privileges[0].getName()); } @Test public void shoudlGrantAllPermissions() throws Exception { Privilege[] privileges = acm.getPrivileges("/Cars/Hybrid"); assertTrue(contains("jcr:all", privileges)); } @Test public void shouldGrantAdd() throws Exception { Node sports = session.getNode("/Cars/Sports"); try { sports.addNode("Chevrolet Camaro", "car:Car"); } catch (AccessDeniedException e) { fail("Should grant add"); } } @Test public void shouldDenyAdd() throws RepositoryException { Node luxury = session.getNode("/Cars/Luxury"); try { luxury.addNode("Cadillac Flitwood", "car:Car"); fail("Should deny add node"); } catch (AccessDeniedException e) { //expected } } @Test public void shouldGrantModify() throws RepositoryException { Node infinity = session.getNode("/Cars/Sports/Infiniti G37"); try { infinity.setProperty("car:msrp", "$34,901"); } catch (AccessDeniedException e) { fail("Should grant modification"); } } @Test public void shouldDenyModify() throws RepositoryException { Node car = session.getNode("/Cars/Luxury/Lexus IS350"); try { car.setProperty("car:msrp", "$34,901"); fail("Should deny modification"); } catch (AccessDeniedException e) { //expected } } @Test @FixFor( "MODE-2428" ) public void shouldCheckPermissionsWhenSettingPropertyValues() throws RepositoryException { Node car = session.getNode("/Cars/Luxury/Lexus IS350"); Property maker = car.getProperty("car:maker"); Property rating = car.getProperty("car:userRating"); try { maker.setValue("some value"); fail("Should deny modification"); } catch (AccessDeniedException e) { //expected } try { rating.setValue(2); fail("Should deny modification"); } catch (RepositoryException e) { //expected } } @Test public void shouldGrantRemove() throws RepositoryException { Node car = session.getNode("/Cars/Sports/Infiniti G37"); try { car.remove(); } catch (AccessDeniedException e) { fail("Should grant remove operation"); } } @Test public void shouldDenyRemove() throws RepositoryException { Node car = session.getNode("/Cars/Luxury/Lexus IS350"); try { car.remove(); fail("Should deny remove operation"); } catch (AccessDeniedException e) { // expected } } @Test public void shoudlDenyRemove2() throws RepositoryException { Node car = session.getNode("/Cars/Luxury/Cadillac DTS"); try { car.remove(); fail("Should deny remove operation: Parent node has no privilege to remove child node"); } catch (AccessDeniedException e) { //expected } } @Test public void shouldAllowSetPolicy() throws RepositoryException { setPolicy("/Cars/Utility/Ford F-150", Privilege.JCR_ALL); } @Test public void shouldDenySetPolicy() throws RepositoryException { try { setPolicy("/Cars/Utility", Privilege.JCR_ALL); fail("Should deny access list modification"); } catch (AccessDeniedException e) { //expected } } // -------------------- Testing access control api --- @Test public void onlyAccessControlAPIAllowsRemoveACL() throws Exception { Node node = session.getNode("/Cars/Luxury/mode:acl"); assertThat(node, is(notNullValue())); try { node.remove(); fail("Only Access Control API allows modification"); } catch (AccessDeniedException e) { //expected } } @Test public void onlyAccessControlAPIAllowsAddACL() throws Exception { Node node = session.getNode("/Cars/Hybrid"); assertThat(node, is(notNullValue())); try { node.addMixin("mode:accessControllable"); Node acl = node.addNode("mode:acl", "mode:Acl"); acl.addNode("test", "mode:Permission"); fail("Only Access Control API allows modification"); } catch (ConstraintViolationException e) { //expected } } @Test public void shouldNotDependFromContentPermissions() throws Exception { setPolicy("/Cars/Luxury/Bentley Continental", Privilege.JCR_WRITE); } @Test @FixFor( "MODE-2036" ) public void shouldDenyAccessChildNode() throws Exception { Node root = session.getRootNode(); Node truks = root.addNode("truks"); session.save(); AccessControlManager acm = session.getAccessControlManager(); Privilege[] privileges = new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}; AccessControlList acl; AccessControlPolicyIterator it = acm.getApplicablePolicies(truks.getPath()); if (it.hasNext()) { acl = (AccessControlList)it.nextAccessControlPolicy(); } else { acl = (AccessControlList)acm.getPolicies(truks.getPath())[0]; } acl.addAccessControlEntry(SimplePrincipal.newInstance("Admin"), privileges); acm.setPolicy(truks.getPath(), acl); session.save(); try { root.getNode("truks"); fail("Access list should deny access"); } catch (javax.jcr.security.AccessControlException e) { //expected } } @Test public void shouldAllowAccessUsingRole() throws Exception { Node root = session.getRootNode(); Node truks = root.addNode("tractors"); session.save(); AccessControlManager acm = session.getAccessControlManager(); Privilege[] privileges = new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}; AccessControlList acl; AccessControlPolicyIterator it = acm.getApplicablePolicies(truks.getPath()); if (it.hasNext()) { acl = (AccessControlList)it.nextAccessControlPolicy(); } else { acl = (AccessControlList)acm.getPolicies(truks.getPath())[0]; } acl.addAccessControlEntry(SimplePrincipal.newInstance("admin"), privileges); acm.setPolicy(truks.getPath(), acl); session.save(); Node node = root.getNode("tractors"); assertThat(node, is(notNullValue())); } @Test public void shouldAllowRead() throws Exception { Node root = session.getRootNode(); Node aircraft = root.addNode("aircraft"); assertThat(aircraft, is(notNullValue())); AccessControlList acl2 = acl("/aircraft"); acl2.addAccessControlEntry(SimplePrincipal.newInstance("Admin"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acl2.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_READ)}); acm.setPolicy("/aircraft", acl2); AccessControlList acl = acl("/"); acl.addAccessControlEntry(SimplePrincipal.newInstance("Admin"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_READ)}); acm.setPolicy("/", acl); session.save(); root = session.getRootNode(); aircraft = root.getNode("aircraft"); } // ------------------------------- @Test public void testGetApplicablePolicies() throws Exception { AccessControlList acl = (AccessControlList)acm.getApplicablePolicies("/Cars").nextAccessControlPolicy(); assertTrue(acl != null); } @Test @FixFor( "MODE-2193" ) public void shouldAllowReadingAccessibleNodes() throws Exception { AccessControlList acl = acl("/"); acl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acm.setPolicy("/", acl); Node root = session.getRootNode(); Node ufo = root.addNode("ufo"); Node vans = root.addNode("vans"); assertThat(ufo, is(notNullValue())); assertThat(vans, is(notNullValue())); AccessControlList acl1 = acl("/ufo"); acl1.addAccessControlEntry(SimplePrincipal.newInstance("Admin"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acl1.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_READ)}); acm.setPolicy("/ufo", acl1); //No Access to "anonymous" on "vans" node AccessControlList acl2 = acl("/vans"); acl2.addAccessControlEntry(SimplePrincipal.newInstance("user"), new Privilege[] {acm.privilegeFromName(Privilege.JCR_ALL)}); acm.setPolicy("/vans", acl2); session.save(); root = session.getRootNode(); NodeIterator ni = root.getNodes(); while(ni.hasNext()){ ni.nextNode(); } } @Test @FixFor( "MODE-2408" ) public void shouldVerifyParentACLsIfChildHasEmptyACLList() throws Exception { Node parent = ((Node) session.getNode("/")).addNode("parent"); setPolicy("/parent", Privilege.JCR_ADD_CHILD_NODES, Privilege.JCR_MODIFY_ACCESS_CONTROL, Privilege.JCR_READ_ACCESS_CONTROL); session.save(); parent.addNode("child"); AccessControlList childAcl = acl("/parent/child"); // set an empty policy on the child node acm.setPolicy("/parent/child", childAcl); session.save(); // modify the parent's ACL to not allow changing of ACLs anymore AccessControlList parentAcl = acl("/parent"); parentAcl.removeAccessControlEntry(parentAcl.getAccessControlEntries()[0]); parentAcl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), new Privilege[]{ acm.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES), acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL)}); acm.setPolicy("/parent", parentAcl); session.save(); // attempt to modify the child's ACL and verify that it fails because the child has an empty ACL list so we should be really // checking the parent node try { setPolicy("/parent/child", Privilege.JCR_ALL); fail("Should not allow changing ACLs on a node with an empty policy list for which the parent doesn't have the appropriate permissions"); } catch (AccessDeniedException e) { // expected } } private static void setPolicy( String path, String... privileges ) throws UnsupportedRepositoryOperationException, RepositoryException { AccessControlManager acm = session.getAccessControlManager(); Privilege[] permissions = new Privilege[privileges.length]; for (int i = 0; i < privileges.length; i++) { permissions[i] = acm.privilegeFromName(privileges[i]); } AccessControlList acl = null; AccessControlPolicyIterator it = acm.getApplicablePolicies(path); if (it.hasNext()) { acl = (AccessControlList)it.nextAccessControlPolicy(); } else { acl = (AccessControlList)acm.getPolicies(path)[0]; } acl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), permissions); acm.setPolicy(path, acl); session.save(); } private boolean contains( String name, Privilege[] privileges ) { for (Privilege privilege : privileges) { if (name.equals(privilege.getName())) { return true; } } return false; } }