Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactoryProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactoryProvider.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactoryProvider.java (revision ) @@ -0,0 +1,31 @@ +/* + * 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.user; + +import org.apache.jackrabbit.oak.spi.security.SecurityProvider; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * {@code UserAuthenticationFactoryProvider} is used to provide {@code UserAuthenticationFactory}s + * for each instance of {@code UserManager}. + */ +public interface UserAuthenticationFactoryProvider { + + List getUserAuthenticationFactories(@Nonnull SecurityProvider securityProvider); +} \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java (revision 1636368) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java (revision ) @@ -56,6 +56,7 @@ import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableNodeName; import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAware; import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardRestrictionProvider; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUserAuthenticationFactoryProvider; import org.osgi.framework.BundleContext; import static com.google.common.base.Preconditions.checkNotNull; @@ -95,6 +96,8 @@ private final WhiteboardAuthorizableNodeName authorizableNodeName = new WhiteboardAuthorizableNodeName(); private final WhiteboardAuthorizableActionProvider authorizableActionProvider = new WhiteboardAuthorizableActionProvider(); private final WhiteboardRestrictionProvider restrictionProvider = new WhiteboardRestrictionProvider(); + private final WhiteboardUserAuthenticationFactoryProvider userAuthenticationFactoryProvider + = new WhiteboardUserAuthenticationFactoryProvider(); private ConfigurationParameters configuration; @@ -192,6 +195,7 @@ authorizableActionProvider.start(whiteboard); authorizableNodeName.start(whiteboard); restrictionProvider.start(whiteboard); + userAuthenticationFactoryProvider.start(whiteboard); initializeConfigurations(); } @@ -201,6 +205,7 @@ authorizableActionProvider.stop(); authorizableNodeName.stop(); restrictionProvider.stop(); + userAuthenticationFactoryProvider.stop(); } protected void bindPrincipalConfiguration(@Nonnull PrincipalConfiguration reference) { @@ -228,7 +233,8 @@ Map userMap = ImmutableMap.of( UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, authorizableActionProvider, - UserConstants.PARAM_AUTHORIZABLE_NODE_NAME, authorizableNodeName); + UserConstants.PARAM_AUTHORIZABLE_NODE_NAME, authorizableNodeName, + UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, userAuthenticationFactoryProvider); initConfiguration(userConfiguration, ConfigurationParameters.of(userMap)); initConfiguration(authenticationConfiguration); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/CompositeUserAuthenticationFactoryProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/CompositeUserAuthenticationFactoryProvider.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/CompositeUserAuthenticationFactoryProvider.java (revision ) @@ -0,0 +1,45 @@ +/* + * 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.user; + +import com.google.common.collect.Lists; +import org.apache.jackrabbit.oak.spi.security.SecurityProvider; + +import java.util.Collection; +import java.util.List; + +/** + * Aggregates a collection of {@link org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider}s into a single + * provider. + */ +public class CompositeUserAuthenticationFactoryProvider implements UserAuthenticationFactoryProvider { + + private final Collection providers; + + public CompositeUserAuthenticationFactoryProvider(Collection providers) { + this.providers = providers; + } + + @Override + public List getUserAuthenticationFactories(SecurityProvider securityProvider) { + List actions = Lists.newArrayList(); + for (UserAuthenticationFactoryProvider p : providers) { + actions.addAll(p.getUserAuthenticationFactories(securityProvider)); + } + return actions; + } +} \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java (revision 1636368) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserAuthenticationFactory.java (revision ) @@ -42,4 +42,15 @@ */ @Nonnull Authentication getAuthentication(@Nonnull UserConfiguration userConfiguration, @Nonnull Root root, @Nullable String userId); + + /** + * Indicates whether this factory accepts the given + * {@link org.apache.jackrabbit.oak.spi.security.user.UserConfiguration}, in which case the factory is considered + * for inclusion in the user configuration based on its service ranking. + * + * @param configuration The user configuration. + * @return true if this factory accepts the given user configuration. + */ + @Nonnull + boolean accepts(@Nonnull UserConfiguration configuration); } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImplTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImplTest.java (revision 1636368) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImplTest.java (revision ) @@ -16,30 +16,105 @@ */ package org.apache.jackrabbit.oak.security.user; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import org.apache.jackrabbit.oak.AbstractSecurityTest; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; +import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.security.authentication.Authentication; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactoryProvider; +import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.junit.Before; import org.junit.Test; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class UserAuthenticationFactoryImplTest extends AbstractSecurityTest { private String userId; - private UserAuthenticationFactoryImpl factory; + private static final List factories = Lists.newArrayList( + new TestUserAuthenticationFactoryImpl(), + new UserAuthenticationFactoryImpl() + ); + + private static final UserAuthenticationFactoryProvider provider = new UserAuthenticationFactoryProvider() { + @Override + public List getUserAuthenticationFactories(@Nonnull SecurityProvider securityProvider) { + return factories; + } + }; + + @Override + protected ConfigurationParameters getSecurityConfigParameters() { + Map map = Maps.newHashMap(); + map.put(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, provider); + map.put(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, true); + ConfigurationParameters userConfig = ConfigurationParameters.of(map); + ConfigurationParameters params = ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, userConfig)); + return ConfigurationParameters.of(super.getSecurityConfigParameters(), params); + } + @Before public void before() throws Exception { super.before(); - factory = new UserAuthenticationFactoryImpl(); userId = getTestUser().getID(); } @Test + public void testAcceptsUserConfiguration() throws Exception { + assertTrue(factories.size() == 2); + // get last (default factory) + UserAuthenticationFactory defaultFactory = factories.get(factories.size() - 1); + assertTrue(defaultFactory.accepts(getUserConfiguration())); + } + + @Test + public void testDoesNotAcceptUserConfiguration() throws Exception { + // get first factory + UserAuthenticationFactory factory = factories.get(0); + assertFalse(factory.accepts(getUserConfiguration())); + } + + @Test public void testGetAuthentication() throws Exception { - Authentication authentication = factory.getAuthentication(getUserConfiguration(), root, userId); + Authentication authentication = getUserAuthentication(getUserConfiguration(), root, userId); assertNotNull(authentication); assertTrue(authentication instanceof UserAuthentication); + } + + private Authentication getUserAuthentication(UserConfiguration config, Root root, String userId) { + for (UserAuthenticationFactory factory : factories) { + if (factory.accepts(config)) { + return factory.getAuthentication(config, root, userId); + } + } + return null; + } + + private static class TestUserAuthenticationFactoryImpl implements UserAuthenticationFactory { + @Nonnull + @Override + public Authentication getAuthentication(@Nonnull UserConfiguration userConfiguration, @Nonnull Root root, @Nullable String userId) { + return new UserAuthentication(userConfiguration, root, userId); + } + + @Nonnull + @Override + public boolean accepts(@Nonnull UserConfiguration configuration) { + // only asccept user configuration if initial pw change is disabled + return !configuration.getParameters().getConfigValue(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, false); + } } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (revision 1636368) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (revision ) @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.oak.security.user; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.spi.security.authentication.Authentication; import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory; @@ -24,14 +26,19 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +@Component +@Service public class UserAuthenticationFactoryImpl implements UserAuthenticationFactory { - public UserAuthenticationFactoryImpl() { - } - @Nonnull @Override public Authentication getAuthentication(@Nonnull UserConfiguration userConfiguration, @Nonnull Root root, @Nullable String userId) { return new UserAuthentication(userConfiguration, root, userId); + } + + @Nonnull + @Override + public boolean accepts(@Nonnull UserConfiguration configuration) { + return true; } } Index: oak-doc/src/site/markdown/security/authentication.md IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-doc/src/site/markdown/security/authentication.md (revision 1635074) +++ oak-doc/src/site/markdown/security/authentication.md (revision ) @@ -447,7 +447,7 @@ The default security setup as present with Oak 1.0 is able to provide custom implementation on various levels: -1. The complete authentiction setup can be changed by plugging a different +1. The complete authentication setup can be changed by plugging a different `AuthenticationConfiguration` implementations. In OSGi-base setup this is achieved by making the configuration a service. In a non-OSGi-base setup the custom configuration must be exposed by the `SecurityProvider` implementation. @@ -455,6 +455,13 @@ login modules and their individual settings. In an OSGi-base setup is achieved by making the modules accessible to the framework and setting their execution order accordingly. In a Non-OSGi setup this is specified in the [JAAS config]. +3. The `LoginModuleImpl` uses `UserAuthentication`-implementations for performing + the authentication process. Which user-authentication implementation to use is + determined by the available `UserAuthenticationFactory`s which provide user- + authentication implementations if a given `UserConfiguration` is accepted. + Which user-authentication-factory is chosen depends on its OSGi service + ranking property. The default factory has a ranking of 0 (OSGi default). Services with + the highest ranking take precedence. ### Further Reading \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java (revision 1636368) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java (revision ) @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.security.user; import java.security.Principal; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,6 +44,7 @@ import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration; import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactoryProvider; import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil; @@ -107,7 +109,14 @@ }) public class UserConfigurationImpl extends ConfigurationBase implements UserConfiguration, SecurityConfiguration { - private final UserAuthenticationFactory defaultAuthFactory = new UserAuthenticationFactoryImpl(); + private static final UserAuthenticationFactoryProvider defaultUserAuthenticationFactoryProvider = new UserAuthenticationFactoryProvider() { + @Override + public List getUserAuthenticationFactories(@Nonnull SecurityProvider securityProvider) { + ArrayList list = new ArrayList(); + list.add(new UserAuthenticationFactoryImpl()); + return list; + } + }; public UserConfigurationImpl() { super(); @@ -136,7 +145,8 @@ if (!params.containsKey(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY)) { return ConfigurationParameters.of( params, - ConfigurationParameters.of(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, defaultAuthFactory)); + ConfigurationParameters.of(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, + defaultUserAuthenticationFactoryProvider)); } else { return params; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java (revision 1636368) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java (revision ) @@ -20,6 +20,7 @@ import java.security.Principal; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; @@ -42,6 +43,7 @@ import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials; import org.apache.jackrabbit.oak.spi.security.authentication.PreAuthenticatedLogin; import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactoryProvider; import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil; @@ -225,16 +227,41 @@ private Authentication getUserAuthentication(@Nullable String userId) { SecurityProvider securityProvider = getSecurityProvider(); Root root = getRoot(); + Authentication authentication = null; if (securityProvider != null && root != null) { + UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class); - UserAuthenticationFactory factory = uc.getParameters().getConfigValue(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, null, UserAuthenticationFactory.class); - if (factory != null) { - return factory.getAuthentication(uc, root, userId); - } else { - log.error("No user authentication factory configured in user configuration."); + UserAuthenticationFactoryProvider provider = uc.getParameters().getConfigValue( + UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, + null, + UserAuthenticationFactoryProvider.class); + + if (null == provider) { + log.error("No user authentication factory provider available."); + return null; } - } + + List factories = provider.getUserAuthenticationFactories(securityProvider); + + if (factories.size() == 0) { + log.error("No user authentication factories available."); - return null; + return null; + } + + for (UserAuthenticationFactory factory : factories) { + if (factory.accepts(uc)) { + authentication = factory.getAuthentication(uc, root, userId); + log.debug("obtained user authentication [{}] from factory [{}]", authentication, factory); + break; + } + } + } + + if (null == authentication) { + log.error("Could not obtain any user authentication."); + } + + return authentication; } private AuthInfo createAuthInfo() { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryProvider.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryProvider.java (revision ) @@ -0,0 +1,44 @@ +/* + * 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.whiteboard; + +import org.apache.jackrabbit.oak.spi.security.SecurityProvider; +import org.apache.jackrabbit.oak.spi.security.user.CompositeUserAuthenticationFactoryProvider; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory; +import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactoryProvider; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Dynamic {@link org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactoryProvider} based on the available + * whiteboard services. + */ +public class WhiteboardUserAuthenticationFactoryProvider + extends AbstractServiceTracker + implements UserAuthenticationFactoryProvider { + + public WhiteboardUserAuthenticationFactoryProvider() { + super(UserAuthenticationFactoryProvider.class); + } + + @Override + public List getUserAuthenticationFactories(@Nonnull SecurityProvider securityProvider) { + UserAuthenticationFactoryProvider actionProvider = new CompositeUserAuthenticationFactoryProvider(getServices()); + return actionProvider.getUserAuthenticationFactories(securityProvider); + } +} \ No newline at end of file