blob: 0820a73228fbdb1ce9137ec5c421a7d8efd62b3b [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.spi.security.authentication.external;
import java.security.Principal;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.jcr.SimpleCredentials;
import javax.jcr.ValueFactory;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class ExternalLoginModuleAutoMembershipTest extends ExternalLoginModuleTestBase {
private static final String NON_EXISTING_NAME = "nonExisting";
@Rule
public final OsgiContext context = new OsgiContext();
private Root r;
private UserManager userManager;
private ValueFactory valueFactory;
private ExternalSetup setup1;
private ExternalSetup setup2;
private ExternalSetup setup3;
private ExternalSetup setup4;
private ExternalSetup setup5;
@Override
public void before() throws Exception {
super.before();
r = getSystemRoot();
userManager = getUserManager(r);
valueFactory = getValueFactory(r);
syncConfig.user().setDynamicMembership(true);
// register the ExternalPrincipal configuration in order to have it's
// activate method invoked.
context.registerInjectActivateService(externalPrincipalConfiguration);
// first configuration based on test base-setup with
// - dynamic membership = true
// - auto-membership = 'gr_default' and 'nonExisting'
syncConfig.user().setDynamicMembership(true);
setup1 = new ExternalSetup(idp, syncConfig, WhiteboardUtils.getService(whiteboard, SyncHandler.class), "gr" + UUID.randomUUID());
// second configuration with different IDP ('idp2') and
// - dynamic membership = true
// - auto-membership = 'gr_name2' and 'nonExisting'
DefaultSyncConfig sc2 = new DefaultSyncConfig();
sc2.setName("name2").user().setDynamicMembership(true);
setup2 = new ExternalSetup(new TestIdentityProvider("idp2"), sc2);
// third configuration with different IDP ('idp3') and
// - dynamic membership = false
// - auto-membership = 'gr_name3' and 'nonExisting'
DefaultSyncConfig sc3 = new DefaultSyncConfig();
sc3.setName("name3");
setup3 = new ExternalSetup(new TestIdentityProvider("idp3"), sc3);
// forth configuration based on different IDP ('idp4') but re-using
// sync-handler configuration (sc2)
setup4 = new ExternalSetup(new TestIdentityProvider("idp4"), sc2);
// fifth configuration with different IDP ('idp5') and
// - dynamic membership = true
// - auto-membership => nothing configured
DefaultSyncConfig sc5 = new DefaultSyncConfig();
sc5.setName("name5").user().setDynamicMembership(true);
setup5 = new ExternalSetup(new TestIdentityProvider("idp5"), sc5, new DefaultSyncHandler(sc5), null);
}
@Override
public void after() throws Exception {
try {
syncConfig.user().setAutoMembership().setExpirationTime(0);
setup1.close();
setup2.close();
setup3.close();
setup4.close();
} finally {
super.after();
}
}
@Override
protected Configuration getConfiguration() {
return new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
AppConfigurationEntry[] entries = new AppConfigurationEntry[5];
int i = 0;
for (ExternalSetup setup : new ExternalSetup[] {setup1, setup2, setup3, setup4, setup5}) {
entries[i++] = setup.asConfigurationEntry();
}
return entries;
}
};
}
private static void registerSyncHandlerMapping(@Nonnull OsgiContext ctx, @Nonnull ExternalSetup setup) {
String syncHandlerName = setup.sc.getName();
Map props = ImmutableMap.of(
DefaultSyncConfigImpl.PARAM_NAME, syncHandlerName,
DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, setup.sc.user().getDynamicMembership(),
DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, setup.sc.user().getAutoMembership());
ctx.registerService(SyncHandler.class, setup.sh, props);
Map mappingProps = ImmutableMap.of(
SyncHandlerMapping.PARAM_IDP_NAME, setup.idp.getName(),
SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, syncHandlerName);
ctx.registerService(SyncHandlerMapping.class, new SyncHandlerMapping() {}, mappingProps);
}
@Test
public void testLoginSyncAutoMembershipSetup1() throws Exception {
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must set the existing auto-membership principals to the subject
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
assertTrue(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup2.gr.getPrincipal()));
assertFalse(principals.contains(setup3.gr.getPrincipal()));
// however, the existing auto-membership group must _not_ have changed
// and the test user must not be a stored member of this group.
root.refresh();
UserManager uMgr = getUserManager(root);
User user = uMgr.getAuthorizable(USER_ID, User.class);
Group gr = uMgr.getAuthorizable(setup1.gr.getID(), Group.class);
assertFalse(gr.isDeclaredMember(user));
assertFalse(gr.isMember(user));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
@Test
public void testLoginAfterSyncSetup1() throws Exception {
setup1.sync(USER_ID, false);
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must set the configured + existing auto-membership principals
// to the subject; non-existing auto-membership entries must be ignored.
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
assertTrue(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup2.gr.getPrincipal()));
assertFalse(principals.contains(setup3.gr.getPrincipal()));
// however, the existing auto-membership group must _not_ have changed
// and the test user must not be a stored member of this group.
root.refresh();
UserManager uMgr = getUserManager(root);
User user = uMgr.getAuthorizable(USER_ID, User.class);
Group gr = uMgr.getAuthorizable(setup1.gr.getID(), Group.class);
assertFalse(gr.isDeclaredMember(user));
assertFalse(gr.isMember(user));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
@Test
public void testLoginAfterSyncSetup2() throws Exception {
setup2.sync(USER_ID, false);
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must set the existing auto-membership principals to the subject
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
assertTrue(principals.contains(setup2.gr.getPrincipal()));
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(setup3.gr.getPrincipal()));
// however, the existing auto-membership group must _not_ have changed
// and the test user must not be a stored member of this group.
root.refresh();
UserManager uMgr = getUserManager(root);
User user = uMgr.getAuthorizable(USER_ID, User.class);
Group gr = uMgr.getAuthorizable(setup2.gr.getID(), Group.class);
assertFalse(gr.isDeclaredMember(user));
assertFalse(gr.isMember(user));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
@Test
public void testLoginAfterSyncSetup3() throws Exception {
setup3.sync(USER_ID, false);
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must set the existing auto-membership principals to the subject
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
assertTrue(principals.contains(setup3.gr.getPrincipal()));
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(setup2.gr.getPrincipal()));
// however, the existing auto-membership group must _not_ have changed
// and the test user must not be a stored member of this group.
root.refresh();
UserManager uMgr = getUserManager(root);
User user = uMgr.getAuthorizable(USER_ID, User.class);
Group gr = uMgr.getAuthorizable(setup3.gr.getID(), Group.class);
assertTrue(gr.isDeclaredMember(user));
assertTrue(gr.isMember(user));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
@Test
public void testLoginAfterSyncSetup4() throws Exception {
setup4.sync(USER_ID, false);
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must set the existing auto-membership principals to the subject
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
assertTrue(principals.contains(setup4.gr.getPrincipal()));
assertTrue(principals.contains(setup2.gr.getPrincipal()));
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(setup3.gr.getPrincipal()));
// however, the existing auto-membership group must _not_ have changed
// and the test user must not be a stored member of this group.
root.refresh();
UserManager uMgr = getUserManager(root);
User user = uMgr.getAuthorizable(USER_ID, User.class);
Group gr = uMgr.getAuthorizable(setup4.gr.getID(), Group.class);
assertFalse(gr.isDeclaredMember(user));
assertFalse(gr.isMember(user));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
@Test
public void testLoginAfterSyncSetup5() throws Exception {
setup5.sync(USER_ID, false);
ContentSession cs = null;
try {
cs = login(new SimpleCredentials(USER_ID, new char[0]));
// the login must not set any auto-membership principals to the subject
// as auto-membership is not configured on this setup.
Set<Principal> principals = cs.getAuthInfo().getPrincipals();
Set<Principal> expected = ImmutableSet.of(EveryonePrincipal.getInstance(), userManager.getAuthorizable(USER_ID).getPrincipal());
assertEquals(expected, principals);
assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
assertFalse(principals.contains(setup1.gr.getPrincipal()));
assertFalse(principals.contains(setup2.gr.getPrincipal()));
assertFalse(principals.contains(setup3.gr.getPrincipal()));
assertFalse(principals.contains(setup4.gr.getPrincipal()));
} finally {
options.clear();
if (cs != null) {
cs.close();
}
}
}
private final class ExternalSetup {
private final ExternalIdentityProvider idp;
private final Registration idpRegistration;
private final DefaultSyncConfig sc;
private final SyncHandler sh;
private final Registration shRegistration;
private final Group gr;
private SyncContext ctx;
private ExternalSetup(@Nonnull ExternalIdentityProvider idp, @Nonnull DefaultSyncConfig sc) throws Exception {
this(idp, sc, new DefaultSyncHandler(sc), "gr_" + sc.getName());
}
private ExternalSetup(@Nonnull ExternalIdentityProvider idp, @Nonnull DefaultSyncConfig sc, @Nonnull SyncHandler sh, @CheckForNull String groupId) throws Exception {
this.idp = idp;
this.sc = sc;
this.sh = sh;
if (groupId != null) {
Group g = userManager.getAuthorizable(groupId, Group.class);
if (g != null) {
gr = g;
} else {
gr = userManager.createGroup(groupId);
}
r.commit();
sc.user().setAutoMembership(gr.getID(), NON_EXISTING_NAME).setExpirationTime(Long.MAX_VALUE);
} else {
gr = null;
}
idpRegistration = whiteboard.register(ExternalIdentityProvider.class, idp, Collections.<String, Object>emptyMap());
shRegistration = whiteboard.register(SyncHandler.class, sh, ImmutableMap.of(
DefaultSyncConfigImpl.PARAM_NAME, sh.getName(),
DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, sc.user().getDynamicMembership(),
DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, sc.user().getAutoMembership()));
registerSyncHandlerMapping(context, this);
}
private void sync(@Nonnull String id, boolean isGroup) throws Exception {
ctx = sh.createContext(idp, userManager, valueFactory);
ExternalIdentity exIdentity = (isGroup) ? idp.getGroup(id) : idp.getUser(id);
assertNotNull(exIdentity);
SyncResult res = ctx.sync(exIdentity);
assertEquals(idp.getName(), res.getIdentity().getExternalIdRef().getProviderName());
assertSame(SyncResult.Status.ADD, res.getStatus());
r.commit();
}
private void close() {
if (ctx != null) {
ctx.close();
}
if (idpRegistration != null) {
idpRegistration.unregister();
}
if (shRegistration != null) {
shRegistration.unregister();
}
}
private AppConfigurationEntry asConfigurationEntry() {
return new AppConfigurationEntry(
ExternalLoginModule.class.getName(),
AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT,
ImmutableMap.<String, String>of(
SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, sh.getName(),
SyncHandlerMapping.PARAM_IDP_NAME, idp.getName()
));
}
}
}