blob: d0d8af1d25427e31b15e163bfcea7de578c379a2 [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 com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
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.ExternalIdentityConstants;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModuleFactory;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal.ExternalPrincipalConfiguration;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import javax.jcr.RepositoryException;
import javax.security.auth.Subject;
import java.security.PrivilegedExceptionAction;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Abstract base test for external-authentication tests.
*/
public abstract class AbstractExternalAuthTest extends AbstractSecurityTest {
@Rule
public final OsgiContext context = new OsgiContext();
protected static final String USER_ID = TestIdentityProvider.ID_TEST_USER;
protected static final String TEST_CONSTANT_PROPERTY_NAME = "profile/constantProperty";
protected static final String TEST_CONSTANT_PROPERTY_VALUE = "constant-value";
protected ExternalIdentityProvider idp;
protected DefaultSyncConfig syncConfig;
protected ExternalPrincipalConfiguration externalPrincipalConfiguration = new ExternalPrincipalConfiguration();
private Set<String> ids;
private ContentSession systemSession;
private Root systemRoot;
@Before
public void before() throws Exception {
super.before();
getTestUser();
ids = Sets.newHashSet(getAllAuthorizableIds(getUserManager(root)));
idp = createIDP();
syncConfig = createSyncConfig();
}
@After
public void after() throws Exception {
try {
destroyIDP();
idp = null;
// discard any pending changes
Root r = (systemRoot != null) ? systemRoot : root;
r.refresh();
UserManager userManager = getUserManager(r);
Iterator<String> iter = getAllAuthorizableIds(userManager);
while (iter.hasNext()) {
String id = iter.next();
if (!ids.remove(id)) {
Authorizable a = userManager.getAuthorizable(id);
if (a != null) {
a.remove();
}
}
}
r.commit();
} finally {
root.refresh();
if (systemRoot != null) {
systemRoot.refresh();
}
if (systemSession != null) {
systemSession.close();
}
super.after();
}
}
protected static void assertException(@NotNull CommitFailedException e, @NotNull String expectedType, int expectedCode) throws CommitFailedException {
assertEquals(expectedType, e.getType());
assertEquals(expectedCode, e.getCode());
throw e;
}
@NotNull
private static Iterator<String> getAllAuthorizableIds(@NotNull UserManager userManager) throws Exception {
Iterator<Authorizable> iter = userManager.findAuthorizables("jcr:primaryType", null);
return Iterators.filter(Iterators.transform(iter, input -> {
try {
if (input != null) {
return input.getID();
}
} catch (RepositoryException e) {
// failed to retrieve ID
}
return null;
}), Objects::nonNull);
}
@Override
@NotNull
protected SecurityProvider getSecurityProvider() {
if (securityProvider == null) {
securityProvider = TestSecurityProvider.newTestSecurityProvider(getSecurityConfigParameters(), externalPrincipalConfiguration);
// register PrincipalConfiguration with OSGi context
context.registerInjectActivateService(externalPrincipalConfiguration, getExternalPrincipalConfiguration());
}
return securityProvider;
}
@NotNull
protected Map<String, Object> getExternalPrincipalConfiguration() {
return Collections.emptyMap();
}
@NotNull
protected ExternalIdentityProvider createIDP() {
return new TestIdentityProvider();
}
protected void destroyIDP() {
// nothing to do
}
protected void addIDPUser(String id) {
((TestIdentityProvider) idp).addUser(new TestIdentityProvider.TestUser(id, idp.getName()));
}
@NotNull
protected DefaultSyncConfig createSyncConfig() {
DefaultSyncConfig syncConfig = new DefaultSyncConfig();
Map<String, String> mapping = new HashMap<>();
mapping.put("name", "name");
mapping.put("email", "email");
mapping.put("profile/name", "profile/name");
mapping.put("profile/age", "profile/age");
mapping.put(TEST_CONSTANT_PROPERTY_NAME, "\"" + TEST_CONSTANT_PROPERTY_VALUE + "\"");
syncConfig.user().setPropertyMapping(mapping);
syncConfig.user().setMembershipNestingDepth(1);
return syncConfig;
}
protected DefaultSyncHandler registerSyncHandler(@NotNull Map<String, Object> syncConfigMap, @NotNull String idpName) {
context.registerService(SyncHandlerMapping.class, new ExternalLoginModuleFactory(), ImmutableMap.of(
SyncHandlerMapping.PARAM_IDP_NAME, idpName,
SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, syncConfigMap.get(DefaultSyncConfigImpl.PARAM_NAME)
));
return (DefaultSyncHandler) context.registerService(SyncHandler.class, new DefaultSyncHandler(), syncConfigMap);
}
protected @NotNull Map<String, Object> syncConfigAsMap() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put(DefaultSyncConfigImpl.PARAM_NAME, syncConfig.getName())
.put(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, syncConfig.user().getDynamicMembership())
.put(DefaultSyncConfigImpl.PARAM_USER_MEMBERSHIP_NESTING_DEPTH, syncConfig.user().getMembershipNestingDepth())
.put(DefaultSyncConfigImpl.PARAM_USER_AUTO_MEMBERSHIP, syncConfig.user().getAutoMembership().toArray(new String[0]))
.put(DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, syncConfig.group().getAutoMembership().toArray(new String[0]))
.put(DefaultSyncConfigImpl.PARAM_GROUP_DYNAMIC_GROUPS, syncConfig.group().getDynamicGroups());
return builder.build();
}
@NotNull
protected Root getSystemRoot() throws Exception {
if (systemRoot == null) {
systemSession = Subject.doAs(SystemSubject.INSTANCE, (PrivilegedExceptionAction<ContentSession>) () -> getContentRepository().login(null, null));
systemRoot = systemSession.getLatestRoot();
}
return systemRoot;
}
protected static void waitUntilExpired(@NotNull User user, @NotNull Root root, long expTime) throws RepositoryException {
Tree t = root.getTree(user.getPath());
PropertyState ps = t.getProperty(ExternalIdentityConstants.REP_LAST_SYNCED);
if (ps == null || ps.count() == 0) {
return;
}
long lastSynced = ps.getValue(Type.LONG);
long now = Calendar.getInstance().getTimeInMillis();
while (now - lastSynced <= expTime) {
now = Calendar.getInstance().getTimeInMillis();
}
}
protected static Set<ExternalIdentityRef> getExpectedSyncedGroupRefs(long membershipNestingDepth, @NotNull ExternalIdentityProvider idp, @NotNull ExternalIdentity extId) throws Exception {
if (membershipNestingDepth <= 0) {
return Collections.emptySet();
}
Set<ExternalIdentityRef> groupRefs = new HashSet<>();
getExpectedSyncedGroupRefs(membershipNestingDepth, idp, extId, groupRefs);
return groupRefs;
}
protected static Set<String> getExpectedSyncedGroupIds(long membershipNestingDepth, @NotNull ExternalIdentityProvider idp, @NotNull ExternalIdentity extId) throws Exception {
Set<ExternalIdentityRef> groupRefs = getExpectedSyncedGroupRefs(membershipNestingDepth, idp, extId);
return groupRefs.stream().map(ExternalIdentityRef::getId).collect(Collectors.toSet());
}
private static void getExpectedSyncedGroupRefs(long membershipNestingDepth, @NotNull ExternalIdentityProvider idp,
@NotNull ExternalIdentity extId, @NotNull Set<ExternalIdentityRef> groupRefs) throws Exception {
extId.getDeclaredGroups().forEach(groupRefs::add);
if (membershipNestingDepth > 1) {
for (ExternalIdentityRef ref : extId.getDeclaredGroups()) {
ExternalIdentity id = idp.getIdentity(ref);
assertNotNull(id);
getExpectedSyncedGroupRefs(membershipNestingDepth-1, idp, id, groupRefs);
}
}
}
}