You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2021/06/22 13:26:10 UTC
svn commit: r1890973 - in /jackrabbit/oak/trunk/oak-auth-external/src:
main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
Author: angela
Date: Tue Jun 22 13:26:10 2021
New Revision: 1890973
URL: http://svn.apache.org/viewvc?rev=1890973&view=rev
Log:
OAK-9468 : Define mechanism to prevent cross-IDP membership
Added:
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java (with props)
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java?rev=1890973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java Tue Jun 22 13:26:10 2021
@@ -0,0 +1,215 @@
+/*
+ * 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.impl.principal;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
+import org.apache.jackrabbit.oak.spi.security.user.action.AbstractGroupAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.jackrabbit.oak.spi.security.RegistrationConstants.OAK_SECURITY_NAME;
+
+@Component(
+ service = AuthorizableActionProvider.class,
+ configurationPolicy = ConfigurationPolicy.REQUIRE,
+ property = OAK_SECURITY_NAME + "=org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal.ExternalAuthorizableActionProvider")
+@Designate(ocd = ExternalAuthorizableActionProvider.Configuration.class)
+public final class ExternalAuthorizableActionProvider implements AuthorizableActionProvider {
+
+ @ObjectClassDefinition(name = "Apache Jackrabbit Oak ExternalAuthorizableActionProvider", description = "Implementation of the AuthorizableActionProvider that specifically covers operations for users/groups defined by an external IDP that get synced into the repository.")
+ @interface Configuration {
+ @AttributeDefinition(
+ name = "Behavior when Adding Members from Different IDP",
+ description = "Defines if Group.addMember should fail when adding a member defined by a different IDP. If set to false only a warning is logged.")
+ boolean failAddMembersForDifferentIdp() default false;
+ }
+
+ private static final Logger log = LoggerFactory.getLogger(ExternalAuthorizableActionProvider.class);
+
+ private boolean failAddMembersForDifferentIdp = false;
+
+ private SyncHandlerMappingTracker syncHandlerMappingTracker;
+ private AutomembershipTracker automembershipTracker;
+
+ @Activate
+ public void activate(@NotNull BundleContext bundleContext, @NotNull Configuration configuration) {
+ failAddMembersForDifferentIdp = configuration.failAddMembersForDifferentIdp();
+
+ syncHandlerMappingTracker = new SyncHandlerMappingTracker(bundleContext);
+ syncHandlerMappingTracker.open();
+
+ automembershipTracker = new AutomembershipTracker(bundleContext, syncHandlerMappingTracker);
+ automembershipTracker.open();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ if (automembershipTracker != null) {
+ automembershipTracker.close();
+ }
+ if (syncHandlerMappingTracker != null) {
+ syncHandlerMappingTracker.close();
+ }
+ }
+
+ @Override
+ public @NotNull List<? extends AuthorizableAction> getAuthorizableActions(@NotNull SecurityProvider securityProvider) {
+ return Collections.singletonList(new IdpBoundaryAction());
+ }
+
+ @Nullable
+ private static String getIdpName(@Nullable ExternalIdentityRef ref) {
+ return (ref == null) ? null : ref.getProviderName();
+ }
+
+ private final class IdpBoundaryAction extends AbstractGroupAction {
+
+ @Override
+ public void onMemberAdded(@NotNull Group group, @NotNull Authorizable member, @NotNull Root root, @NotNull NamePathMapper namePathMapper) throws RepositoryException {
+ ExternalIdentityRef memberRef = DefaultSyncContext.getIdentityRef(member);
+ String idpName = getIdpName(memberRef);
+ if (idpName == null) {
+ // member is not an external members identity -> ignore
+ return;
+ }
+
+ String groupIdpName = getIdpName(DefaultSyncContext.getIdentityRef(group));
+ if (idpName.equals(groupIdpName)) {
+ // same IDP -> nothing to verify
+ return;
+ }
+
+ // not the same IDP -> validate
+ String groupId = group.getID();
+ if (automembershipTracker != null && automembershipTracker.isAutoMembership(idpName, groupId, member.isGroup())) {
+ // ignore groups that are configured in 'automembership' option for the given IDP
+ return;
+ }
+
+ String extId = memberRef.getString();
+ String msg = String.format("Attempt to add external identity '%s' as member of group '%s' defined by IDP '%s'.", extId, groupId, groupIdpName);
+ log.warn(msg);
+ if (failAddMembersForDifferentIdp) {
+ throw new ConstraintViolationException(msg);
+ }
+ }
+ }
+
+ private static final class AutomembershipTracker extends ServiceTracker {
+
+ private final SyncHandlerMappingTracker mappingTracker;
+ private final Set<ServiceReference> refs = new HashSet<>();
+
+ private final Map<String, Set<String>> userAutoMembership = new HashMap<>();
+ private final Map<String, Set<String>> groupAutoMembership = new HashMap<>();
+
+ AutomembershipTracker(@NotNull BundleContext context, @NotNull SyncHandlerMappingTracker mappingTracker) {
+ super(context, SyncHandler.class.getName(), null);
+ this.mappingTracker = mappingTracker;
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference) {
+ refs.add(reference);
+ userAutoMembership.clear(); // recalculate
+ groupAutoMembership.clear(); // recalculate
+ return super.addingService(reference);
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service) {
+ refs.add(reference);
+ userAutoMembership.clear(); // recalculate
+ groupAutoMembership.clear(); // recalculate
+ super.modifiedService(reference, service);
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ refs.remove(reference);
+ userAutoMembership.clear(); // recalculate
+ groupAutoMembership.clear(); // recalculate
+ super.removedService(reference, service);
+ }
+
+ @NotNull
+ private Map<String, Set<String>> getAutomembership(boolean memberIsGroup) {
+ Map<String, Set<String>> autoMembership = (memberIsGroup) ? groupAutoMembership : userAutoMembership;
+ if (autoMembership.isEmpty() && !refs.isEmpty()) {
+ for (ServiceReference ref : refs) {
+ String syncHandlerName = PropertiesUtil.toString(ref.getProperty(DefaultSyncConfigImpl.PARAM_NAME), DefaultSyncConfigImpl.PARAM_NAME_DEFAULT);
+ String[] userMembership = PropertiesUtil.toStringArray(ref.getProperty(DefaultSyncConfigImpl.PARAM_USER_AUTO_MEMBERSHIP), new String[0]);
+ String[] groupMembership = PropertiesUtil.toStringArray(ref.getProperty(DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP), new String[0]);
+
+ for (String idpName : mappingTracker.getIdpNames(syncHandlerName)) {
+ updateAutoMembershipMap(userAutoMembership, syncHandlerName, idpName, userMembership);
+ updateAutoMembershipMap(groupAutoMembership, syncHandlerName, idpName, groupMembership);
+ }
+ }
+ }
+ return autoMembership;
+ }
+
+ private static void updateAutoMembershipMap(@NotNull Map<String, Set<String>> map, @NotNull String syncHandlerName,
+ @NotNull String idpName, @NotNull String[] membership) {
+ Set<String> userMembership = ImmutableSet.copyOf(membership);
+ Set<String> previous = map.put(idpName, userMembership);
+ if (previous != null) {
+ String msg = previous.equals(userMembership) ? "Duplicate" : "Colliding";
+ log.debug("{} auto-membership configuration for IDP '{}'; replacing previous values {} by {} defined by SyncHandler '{}'", msg, idpName, previous, userMembership, syncHandlerName);
+ }
+ }
+
+ private boolean isAutoMembership(@NotNull String idpName, @NotNull String groupId, boolean memberIsGroup) {
+ Set<String> ids = getAutomembership(memberIsGroup).get(idpName);
+ return ids != null && ids.contains(groupId);
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java?rev=1890973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java Tue Jun 22 13:26:10 2021
@@ -0,0 +1,342 @@
+/*
+ * 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.impl.principal;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+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.SyncHandlerMapping;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.GroupAction;
+import org.apache.sling.testing.mock.osgi.MapUtil;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.ConstraintViolationException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants.REP_EXTERNAL_ID;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(Parameterized.class)
+public class ExternalAuthorizableActionProviderTest extends AbstractExternalAuthTest {
+
+ private static final String IDP_NAME = "idp1";
+ private static final String LOCAL_GROUP_ID = "localGroup";
+
+ private final ExternalAuthorizableActionProvider eap = new ExternalAuthorizableActionProvider();
+
+ private final Group localGroup = mock(Group.class);
+ private final Group externalGroup = mock(Group.class);
+ private final Authorizable externalUser = mock(Authorizable.class);
+
+ private SyncHandler sh;
+ private SyncHandlerMapping shMapping;
+
+ @Parameterized.Parameters(name = "name={1}")
+ public static Collection<Object[]> parameters() {
+ return Lists.newArrayList(
+ new Object[] { true, "Fail" },
+ new Object[] { false, "Only Warn" });
+ }
+
+ private final boolean failOnViolation;
+
+ public ExternalAuthorizableActionProviderTest(boolean failOnViolation, String name) {
+ this.failOnViolation = failOnViolation;
+ }
+ @Before
+ public void before() throws Exception {
+ super.before();
+
+ eap.activate(context.bundleContext(), new ExternalAuthorizableActionProvider.Configuration() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Annotation.class;
+ }
+
+ @Override
+ public boolean failAddMembersForDifferentIdp() {
+ return failOnViolation;
+ }
+ });
+
+ doReturn(LOCAL_GROUP_ID).when(localGroup).getID();
+ doReturn("externalGroup").when(externalGroup).getID();
+
+ Value v = getValueFactory(root).createValue(new ExternalIdentityRef("externalUserId", IDP_NAME).getString());
+ when(externalUser.getProperty(REP_EXTERNAL_ID)).thenReturn(new Value[] {v});
+ when(externalUser.isGroup()).thenReturn(false);
+
+ v = getValueFactory(root).createValue(new ExternalIdentityRef("externalGroupId", IDP_NAME).getString());
+ when(externalGroup.getProperty(REP_EXTERNAL_ID)).thenReturn(new Value[] {v});
+ when(externalGroup.isGroup()).thenReturn(true);
+
+ sh = new DefaultSyncHandler();
+ shMapping = new SyncHandlerMapping() {};
+ }
+
+ @After
+ public void after() throws Exception {
+ try {
+ reset(localGroup, externalGroup, externalUser);
+ } finally {
+ super.after();
+ }
+ }
+
+ private GroupAction getGroupAction() {
+ return (GroupAction) eap.getAuthorizableActions(getSecurityProvider()).get(0);
+ }
+
+ @Test
+ public void testGetActions() {
+ List<? extends AuthorizableAction> actions = eap.getAuthorizableActions(getSecurityProvider());
+ assertEquals(1, actions.size());
+ assertTrue(actions.get(0) instanceof GroupAction);
+ }
+
+ @Test
+ public void testMemberNotExternal() throws Exception {
+ GroupAction ga = getGroupAction();
+
+ ga.onMemberAdded(localGroup, getTestUser(), root, getNamePathMapper());
+ ga.onMemberAdded(externalGroup, getTestUser(), root, getNamePathMapper());
+
+ verifyNoInteractions(localGroup);
+ verifyNoInteractions(externalGroup);
+ }
+
+ @Test
+ public void testSameIDP() throws Exception {
+ GroupAction ga = getGroupAction();
+ ga.onMemberAdded(externalGroup, externalUser, root, getNamePathMapper());
+
+ verify(externalGroup).getProperty(REP_EXTERNAL_ID);
+ verifyNoMoreInteractions(externalGroup);
+ }
+
+ @Test
+ public void testUserMamaberGroupInUserAutomembership() throws Exception {
+ registerSyncHandlerSyncMapping(IDP_NAME, LOCAL_GROUP_ID, true);
+
+ GroupAction ga = getGroupAction();
+ ga.onMemberAdded(localGroup, externalUser, root, getNamePathMapper());
+
+ verify(localGroup).getProperty(REP_EXTERNAL_ID);
+ verify(localGroup).getID();
+ verifyNoMoreInteractions(localGroup);
+
+ ga.onMemberAdded(localGroup, externalUser, root, getNamePathMapper());
+ verify(localGroup, times(2)).getProperty(REP_EXTERNAL_ID);
+ verify(localGroup, times(2)).getID();
+ verifyNoMoreInteractions(localGroup);
+ }
+
+ @Test
+ public void testUserMemberGroupInGroupAutomembership() throws Exception {
+ registerSyncHandlerSyncMapping(IDP_NAME, LOCAL_GROUP_ID, false);
+ assertViolationDetected(externalUser);
+ verifyInvocations();
+ }
+
+ @Test
+ public void testGroupMemberGroupInAutomembership() throws Exception {
+ registerSyncHandlerSyncMapping(IDP_NAME, LOCAL_GROUP_ID, false);
+
+ GroupAction ga = getGroupAction();
+ ga.onMemberAdded(localGroup, externalGroup, root, getNamePathMapper());
+
+ verify(localGroup).getProperty(REP_EXTERNAL_ID);
+ verify(localGroup).getID();
+ verifyNoMoreInteractions(localGroup);
+
+ ga.onMemberAdded(localGroup, externalGroup, root, getNamePathMapper());
+ verify(localGroup, times(2)).getProperty(REP_EXTERNAL_ID);
+ verify(localGroup, times(2)).getID();
+ verifyNoMoreInteractions(localGroup);
+ }
+
+ @Test
+ public void testGroupMemberGroupInUserAutomembership() throws Exception {
+ registerSyncHandlerSyncMapping(IDP_NAME, LOCAL_GROUP_ID, true);
+ assertViolationDetected(externalGroup);
+ verifyInvocations();
+ }
+
+ @Test
+ public void testGroupInAutomembershipDifferentIDP() throws Exception {
+ registerSyncHandlerSyncMapping("anotherIDP", LOCAL_GROUP_ID, true);
+ assertViolationDetected(externalUser);
+ verifyInvocations();
+ }
+
+ @Test
+ public void testGroupNotInAutomembership() throws Exception {
+ registerSyncHandlerSyncMapping(IDP_NAME, "anotherGroup", false);
+ assertViolationDetected(externalGroup);
+ verifyInvocations();
+ }
+
+ @Test
+ public void testModifySyncHandler() throws Exception {
+ Map<String,Object> config = createSyncConfig(LOCAL_GROUP_ID, true);
+ ServiceRegistration sr = context.bundleContext().registerService(SyncHandler.class.getName(), sh, MapUtil.toDictionary(config));
+ context.registerService(SyncHandlerMapping.class, shMapping, MapUtil.toDictionary(createMappingConfig(IDP_NAME)));
+
+ getGroupAction().onMemberAdded(localGroup, externalUser, root, getNamePathMapper());
+
+ // modify sync-handler
+ sr.setProperties(MapUtil.toDictionary(createSyncConfig("anotherGroup", true)));
+ // FIXME: use osgi-mock to invoke the method
+ Field f = ExternalAuthorizableActionProvider.class.getDeclaredField("automembershipTracker");
+ f.setAccessible(true);
+ Object automembershipTracker = f.get(eap);
+ Method m = automembershipTracker.getClass().getDeclaredMethod("modifiedService", ServiceReference.class, Object.class);
+ m.setAccessible(true);
+ m.invoke(automembershipTracker, sr.getReference(), sh);
+ // now onMemberAdd will spot the violation
+ assertViolationDetected(externalUser);
+ }
+
+ @Test
+ public void testRemoveSyncHandler() throws RepositoryException {
+ Map<String,Object> config = createSyncConfig(LOCAL_GROUP_ID, false);
+ ServiceRegistration sr = context.bundleContext().registerService(SyncHandler.class.getName(), sh, MapUtil.toDictionary(config));
+ context.registerService(SyncHandlerMapping.class, shMapping, MapUtil.toDictionary(createMappingConfig(IDP_NAME)));
+
+ getGroupAction().onMemberAdded(localGroup, externalGroup, root, getNamePathMapper());
+
+ // remove sync-handler
+ sr.unregister();
+ // now onMemberAdd will spot the violation
+ assertViolationDetected(externalGroup);
+ }
+
+ @Test
+ public void testDuplicateSyncHandlerMapping() throws Exception {
+ registerSyncHandlerSyncMapping("anotherIdpName", LOCAL_GROUP_ID, true);
+ // register another sync-handler-mapping with the same properties
+ context.registerService(SyncHandlerMapping.class, new SyncHandlerMapping() {}, createMappingConfig("anotherIdpName"));
+ assertViolationDetected(externalUser);
+ }
+
+ @Test
+ public void testCollidingSyncHandler() throws Exception {
+ registerSyncHandlerSyncMapping("anotherIdpName", LOCAL_GROUP_ID, true);
+ // register another sync-handler with the same properties
+ SyncHandler anotherSh = new DefaultSyncHandler();
+ context.registerInjectActivateService(anotherSh, createSyncConfig("anotherGroupId", true));
+
+ assertViolationDetected(externalUser);
+ }
+
+ @Test
+ public void testDeactivate() throws RepositoryException {
+ // onMemberAdd will spot the violation
+ assertViolationDetected(externalUser);
+
+ // after deactivation, changes to sync-handler registration will no longer be tracked
+ eap.deactivate();
+ registerSyncHandlerSyncMapping(IDP_NAME, "localGroup", true);
+
+ // -> violation still detected
+ assertViolationDetected(externalUser);
+ }
+
+ @Test
+ public void testDeactivatePriorToActivate() throws RepositoryException {
+ ExternalAuthorizableActionProvider provider = new ExternalAuthorizableActionProvider();
+ provider.deactivate();
+
+ registerSyncHandlerSyncMapping(IDP_NAME, "localGroup", true);
+ // -> violation still detected
+ GroupAction groupAction = (GroupAction) provider.getAuthorizableActions(getSecurityProvider()).get(0);
+ // note: without prior activation 'failOnViolation' is not initialized and thus 'false'
+ assertViolationDetected(externalUser, groupAction, false);
+ }
+
+ private void assertViolationDetected(@NotNull Authorizable externalMember) throws RepositoryException {
+ assertViolationDetected(externalMember, getGroupAction(), failOnViolation);
+ }
+
+ private void assertViolationDetected(@NotNull Authorizable externalMember, @NotNull GroupAction ga, boolean failOnViolation) throws RepositoryException {
+ try {
+ ga.onMemberAdded(localGroup, externalMember, root, getNamePathMapper());
+ if (failOnViolation) {
+ fail("ConstraintViolationException expected");
+ }
+ } catch (ConstraintViolationException e) {
+ if (!failOnViolation) {
+ fail("No ConstraintViolationException expected");
+ }
+ }
+ }
+
+ private void verifyInvocations() throws RepositoryException {
+ verify(localGroup).getProperty(REP_EXTERNAL_ID);
+ verify(localGroup).getID();
+ verifyNoMoreInteractions(localGroup);
+ }
+
+ private void registerSyncHandlerSyncMapping(@NotNull String idpName, @NotNull String automembershipId, boolean isUserAutoMembership) {
+ context.registerInjectActivateService(sh, createSyncConfig(automembershipId, isUserAutoMembership));
+ context.registerService(SyncHandlerMapping.class, shMapping, createMappingConfig(idpName));
+ }
+
+ private static Map<String, Object> createSyncConfig(@NotNull String automembershipId, boolean isUserAutoMembership) {
+ String autoMembershipName = (isUserAutoMembership) ? DefaultSyncConfigImpl.PARAM_USER_AUTO_MEMBERSHIP : DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP;
+ return ImmutableMap.of(
+ DefaultSyncConfigImpl.PARAM_NAME, "sh",
+ autoMembershipName, new String[] {automembershipId});
+ }
+
+ private static Map<String, Object> createMappingConfig(@NotNull String idpName) {
+ return ImmutableMap.of(
+ SyncHandlerMapping.PARAM_IDP_NAME, idpName,
+ SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, "sh");
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalAuthorizableActionProviderTest.java
------------------------------------------------------------------------------
svn:eol-style = native