blob: 9d1430161bff44e87880168f6ddf91bfa373b6c1 [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.sling.jcr.repoinit;
import org.apache.jackrabbit.api.JackrabbitRepository;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.Jcr;
import org.apache.jackrabbit.oak.security.internal.SecurityProviderBuilder;
import org.apache.jackrabbit.oak.security.internal.SecurityProviderHelper;
import org.apache.jackrabbit.oak.spi.mount.Mounts;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
import org.apache.jackrabbit.oak.spi.security.authorization.principalbased.impl.FilterProviderImpl;
import org.apache.jackrabbit.oak.spi.security.authorization.principalbased.impl.PrincipalBasedAuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.sling.jcr.repoinit.impl.AclUtil;
import org.apache.sling.jcr.repoinit.impl.TestUtil;
import org.apache.sling.repoinit.parser.RepoInitParsingException;
import org.apache.sling.repoinit.parser.operations.AclLine;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import javax.security.auth.Subject;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PrincipalBasedAclTest {
@Rule
public final OsgiContext context = new OsgiContext();
private Repository repository;
private Session adminSession;
private TestUtil U;
private String path;
private String propPath;
private Session testSession;
@Before
public void before() throws Exception {
SecurityProvider sp = createSecurityProvider();
repository = new Jcr().with(sp).createRepository();
String uid = sp.getParameters(UserConfiguration.NAME).getConfigValue(UserConstants.PARAM_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID);
adminSession = repository.login(new SimpleCredentials(uid, uid.toCharArray()), null);
U = new TestUtil(adminSession);
Node tmp = adminSession.getRootNode().addNode("tmp_" + U.id);
Property prop = tmp.setProperty("prop", "value");
path = tmp.getPath();
propPath = prop.getPath();
adminSession.save();
U.parseAndExecute("create service user " + U.username);
testSession = loginSystemUserPrincipal(U.username);
assertPermission(testSession, PathUtils.ROOT_PATH, Session.ACTION_READ, false);
assertPermission(testSession, path, Session.ACTION_READ, false);
}
@After
public void after() throws Exception {
try {
adminSession.removeItem(path);
adminSession.save();
U.cleanupUser();
} finally {
adminSession.logout();
testSession.logout();
if (repository instanceof JackrabbitRepository) {
((JackrabbitRepository) repository).shutdown();
}
}
}
private SecurityProvider createSecurityProvider() {
/*
* use composition-type OR in order in order to simplify the permission setup. since the tests in this class
* don't setup permissions using the default authorization, the OR-setup will be sufficient to check for permissions
* granted with the principal-based module only.
*
* in an AND setup scenario one would need to inject the aggregation filter defined by oak-authorization-princialbased
*/
SecurityProvider sp = SecurityProviderBuilder.newBuilder().with(ConfigurationParameters.of("authorizationCompositionType", "OR")).build();
ConfigurationParameters userParams = sp.getParameters(UserConfiguration.NAME);
String userRoot = userParams.getConfigValue(UserConstants.PARAM_USER_PATH, UserConstants.DEFAULT_USER_PATH);
String systemRelPath = userParams.getConfigValue(UserConstants.PARAM_SYSTEM_RELATIVE_PATH, UserConstants.DEFAULT_SYSTEM_RELATIVE_PATH);
FilterProviderImpl fp = new FilterProviderImpl();
context.registerInjectActivateService(fp, Collections.singletonMap("path", PathUtils.concat(userRoot, systemRelPath)));
PrincipalBasedAuthorizationConfiguration authorizationConfig = new PrincipalBasedAuthorizationConfiguration();
authorizationConfig.bindMountInfoProvider(Mounts.defaultMountInfoProvider());
authorizationConfig.bindFilterProvider(fp);
SecurityProviderHelper.updateConfig(sp, authorizationConfig, AuthorizationConfiguration.class);
return sp;
}
/**
* Create a JCR Session for the given system principal that is based on a Subject that only contains SystemUserPrincipal(s).
* The result from U.loginService with result in a Session that additionally contains group-membership, which is not
* supported by the default FilterProvider implementation (i.e. access control setup and permission evaluation is not supported
* by the PrincipalBasedAuthorizationConfiguration as needed for these tests).
*
* @param systemUserPrincipalName Name of a system user principal
* @return A JCR Session
* @throws Exception If the repository login fails.
*/
private Session loginSystemUserPrincipal(final String systemUserPrincipalName) throws Exception {
SystemUserPrincipal principal = new SystemUserPrincipal() {
@Override
public String getName() {
return systemUserPrincipalName;
}
};
Subject subject = new Subject(true, Collections.singleton(principal), Collections.emptySet(), Collections.emptySet());
return Subject.doAs(subject, new PrivilegedExceptionAction<Session>() {
@Override
public Session run() throws Exception {
return repository.login(null, null);
}
});
}
private static void assertPermission(Session userSession, String absolutePath, String actions, boolean successExpected) throws RepositoryException {
assertEquals("Expecting "+actions+" for path " + absolutePath + " to be " + (successExpected ? "granted" : "denied"), successExpected, userSession.hasPermission(absolutePath, actions));
}
@Test
public void readGranted() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on " + path + "\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, path, Session.ACTION_READ, true);
assertPermission(testSession, propPath, Session.ACTION_READ, true);
assertPermission(testSession, propPath, Session.ACTION_SET_PROPERTY, false);
}
@Test
public void grantedNonExistingPath() throws Exception {
String nonExistingPath = path +"/nonExisting";
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on " + nonExistingPath + "\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, nonExistingPath, Session.ACTION_READ, true);
}
@Test
public void grantAtRootPath() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:all on /\n"
+ "end"
;
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, "/newChild", Session.ACTION_READ +","+ Session.ACTION_ADD_NODE +","+ Session.ACTION_REMOVE, true);
}
@Test
public void multiplePrivileges() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read, jcr:versionManagement on " + path + "\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
String permissions = Permissions.getString(Permissions.READ|Permissions.VERSION_MANAGEMENT);
assertPermission(testSession, path, permissions, true);
assertPermission(testSession, path + "/newchild", permissions, true);
}
@Test(expected = RuntimeException.class)
public void invalidPrivilege() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow my:invalidPrivilege on " + path + "\n"
+ "end";
U.parseAndExecute(setup);
}
@Test(expected = RuntimeException.class)
public void denyEntry() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "deny jcr:read on " + path + "\n"
+ "end";
U.parseAndExecute(setup);
}
@Test
public void repoLevelPermission() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:namespaceManagement on :repository\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertTrue(testSession.getAccessControlManager().hasPrivileges(null, AccessControlUtils.privilegesFromNames(testSession, "jcr:namespaceManagement")));
}
@Test
public void repoLevelAndPath() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:all on :repository, "+path+"\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
AccessControlManager acMgr = testSession.getAccessControlManager();
assertTrue(acMgr.hasPrivileges(path, AccessControlUtils.privilegesFromNames(testSession, "jcr:all")));
assertTrue(acMgr.hasPrivileges(null, AccessControlUtils.privilegesFromNames(testSession, "jcr:all")));
}
@Test
public void globRestriction() throws Exception {
String notAllowed = "/testxyz_" + U.id;
String allowed = "/testabc_" + U.id;
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on "+path+" restriction(rep:glob,*abc*)\n"
+ "end"
;
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, path + allowed, Session.ACTION_READ, true);
assertPermission(testSession, path + notAllowed, Session.ACTION_READ, false);
}
@Test
public void emptyGlobRestriction() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on "+path+" restriction (rep:glob)\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, "/", Session.ACTION_READ, false);
assertPermission(testSession, path, Session.ACTION_READ, true);
assertPermission(testSession, propPath, Session.ACTION_READ, false);
assertPermission(testSession, "/path/child", Session.ACTION_READ, false);
}
@Test
public void mvItemNamesRestriction() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:modifyProperties on / restriction(rep:itemNames,propName,prop)\n"
+ "end"
;
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, propPath, Session.ACTION_SET_PROPERTY, true);
assertPermission(testSession, path + "/propName", Session.ACTION_SET_PROPERTY, true);
}
@Test
public void emptyMvRestrictionTest() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on "+path+" restriction(rep:ntNames)\n"
+ "end"
;
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, propPath, Session.ACTION_READ, false);
}
@Test(expected = RuntimeException.class)
public void unsupportedRestriction() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on "+path+" restriction(jcr:unsupported,value)\n"
+ "end"
;
U.parseAndExecute(setup);
}
@Test
public void multiplePaths() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on "+path+", /content\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, PathUtils.ROOT_PATH, Session.ACTION_READ, false);
assertPermission(testSession, path, Session.ACTION_READ, true);
assertPermission(testSession, "/content", Session.ACTION_READ, true);
}
@Test(expected = RepoInitParsingException.class)
public void missingPath() throws Exception {
String setup =
"set principal ACL for " + U.username + "\n"
+ "allow jcr:read on \n"
+ "end";
U.parseAndExecute(setup);
}
@Test
public void multiplePrincipals() throws Exception {
Session s = null;
try {
U.parseAndExecute("create service user otherSystemPrincipal");
String setup =
"set principal ACL for " + U.username + ",otherSystemPrincipal \n"
+ "allow jcr:read on " + path + "\n"
+ "end";
U.parseAndExecute(setup);
testSession.refresh(false);
assertPermission(testSession, propPath, Session.ACTION_READ, true);
s = loginSystemUserPrincipal("otherSystemPrincipal");
assertPermission(s, propPath, Session.ACTION_READ, true);
} finally {
if (s != null) {
s.logout();
}
U.cleanupServiceUser("otherSystemPrincipal");
}
}
@Test(expected = RepoInitParsingException.class)
public void missingPrincipal() throws Exception {
String setup =
"set principal ACL for \n"
+ "allow jcr:read on "+path+"\n"
+ "end";
U.parseAndExecute(setup);
}
@Test
public void testHomePath() throws Exception {
UserManager uMgr = ((JackrabbitSession) U.adminSession).getUserManager();
Authorizable a = uMgr.getAuthorizable(U.username);
Principal principal = a.getPrincipal();
String userHomePath = a.getPath();
JackrabbitAccessControlManager accessControlManager = AclUtil.getJACM(U.adminSession);
assertNull(getAcl(principal, accessControlManager));
AclLine line = new AclLine(AclLine.Action.ALLOW);
line.setProperty(AclLine.PROP_PRINCIPALS, Collections.singletonList(principal.getName()));
line.setProperty(AclLine.PROP_PRIVILEGES, Collections.singletonList(Privilege.JCR_READ));
line.setProperty(AclLine.PROP_PATHS, Collections.singletonList(":home:"+U.username+"#"));
AclUtil.setPrincipalAcl(U.adminSession, U.username, Collections.singletonList(line));
PrincipalAccessControlList acl = getAcl(principal, accessControlManager);
assertNotNull(acl);
assertEquals(1, acl.size());
PrincipalAccessControlList.Entry entry = (PrincipalAccessControlList.Entry) acl.getAccessControlEntries()[0];
assertArrayEquals(AccessControlUtils.privilegesFromNames(accessControlManager, Privilege.JCR_READ), entry.getPrivileges());
assertEquals(a.getPath(), entry.getEffectivePath());
}
@Nullable
private static PrincipalAccessControlList getAcl(@NotNull Principal principal, @NotNull JackrabbitAccessControlManager jacm) throws RepositoryException {
for (AccessControlPolicy policy : jacm.getPolicies(principal)) {
if (policy instanceof PrincipalAccessControlList) {
return (PrincipalAccessControlList) policy;
}
}
return null;
}
}