SLING-3227 - FormLoginModulePlugin does not work with Oak
Add FormLoginModule based on Oak support for JAAS authentication
-- FormAuthenticationHandler would use FormLoginModule or FormLoginModulePlugin depending on support for Oak LoginModule. This would enable use of same bundle in both Oak and JR2 based Sling deployments
-- For JAAS auth the handler would construct a FormCrendentials instance which is supported by FormLoginModule only. Once validated it would make use of Oak pre auth support to let Oak complete the JAAS login
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1649302 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index e129707..51d34ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -158,6 +158,19 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-core</artifactId>
+ <version>1.0.0</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.jaas</artifactId>
+ <version>0.0.2</version>
+ <optional>true</optional>
+ </dependency>
+
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
index 6343b04..d14c8c6 100644
--- a/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
+++ b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
@@ -36,6 +36,7 @@
import javax.servlet.http.HttpSession;
import org.apache.commons.codec.binary.Base64;
+import org.apache.felix.jaas.LoginModuleFactory;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
@@ -56,6 +57,8 @@
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;
import org.apache.sling.auth.form.FormReason;
+import org.apache.sling.auth.form.impl.jaas.FormCredentials;
+import org.apache.sling.auth.form.impl.jaas.JaasHelper;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
@@ -73,7 +76,12 @@
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Apache Sling Form Based Authentication Handler"),
@Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/", cardinality = 100),
@Property(name = AuthenticationHandler.TYPE_PROPERTY, value = HttpServletRequest.FORM_AUTH, propertyPrivate = true),
- @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false) })
+ @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false),
+
+ @Property(name = LoginModuleFactory.JAAS_CONTROL_FLAG, value = "sufficient"),
+ @Property(name = LoginModuleFactory.JAAS_REALM_NAME, value = "jackrabbit.oak"),
+ @Property(name = LoginModuleFactory.JAAS_RANKING, intValue = 1000)
+})
@Service
public class FormAuthenticationHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
@@ -295,6 +303,8 @@
*/
private boolean loginAfterExpire;
+ private JaasHelper jaasHelper;
+
/**
* Extracts cookie/session based credentials from the request. Returns
* <code>null</code> if the handler assumes HTTP Basic authentication would
@@ -622,7 +632,13 @@
final AuthenticationInfo info = new AuthenticationInfo(
HttpServletRequest.FORM_AUTH, userId);
- info.put(attrCookieAuthData, authData);
+
+ if (jaasHelper.enabled()) {
+ //JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS
+ info.put("user.jcr.credentials", new FormCredentials(userId, authData));
+ } else {
+ info.put(attrCookieAuthData, authData);
+ }
return info;
}
@@ -643,6 +659,8 @@
if (data instanceof String) {
return (String) data;
}
+ } else if (credentials instanceof FormCredentials){
+ return ((FormCredentials) credentials).getAuthData();
}
// no SimpleCredentials or no valid attribute
@@ -653,7 +671,7 @@
return getCookieAuthData(credentials) != null;
}
- boolean isValid(final Credentials credentials) {
+ public boolean isValid(final Credentials credentials) {
String authData = getCookieAuthData(credentials);
if (authData != null) {
return tokenStore.isValid(authData);
@@ -679,6 +697,7 @@
Dictionary<?, ?> properties = componentContext.getProperties();
+ this.jaasHelper = new JaasHelper(this, componentContext.getBundleContext(), properties);
this.loginForm = OsgiUtil.toString(properties.get(PAR_LOGIN_FORM),
AuthenticationFormServlet.SERVLET_PATH);
log.info("Login Form URL {}", loginForm);
@@ -730,12 +749,14 @@
this.tokenStore = new TokenStore(tokenFile, sessionTimeout, fastSeed);
this.loginModule = null;
- try {
- this.loginModule = FormLoginModulePlugin.register(this,
- componentContext.getBundleContext());
- } catch (Throwable t) {
- log.info("Cannot register FormLoginModulePlugin. This is expected if Sling LoginModulePlugin services are not supported");
- log.debug("dump", t);
+ if (!jaasHelper.enabled()) {
+ try {
+ this.loginModule = FormLoginModulePlugin.register(this,
+ componentContext.getBundleContext());
+ } catch (Throwable t) {
+ log.info("Cannot register FormLoginModulePlugin. This is expected if Sling LoginModulePlugin services are not supported");
+ log.debug("dump", t);
+ }
}
this.includeLoginForm = OsgiUtil.toBoolean(properties.get(PAR_INCLUDE_FORM), DEFAULT_INCLUDE_FORM);
@@ -745,6 +766,11 @@
@Deactivate
protected void deactivate() {
+ if (jaasHelper != null){
+ jaasHelper.close();
+ jaasHelper = null;
+ }
+
if (loginModule != null) {
loginModule.unregister();
loginModule = null;
diff --git a/src/main/java/org/apache/sling/auth/form/impl/jaas/FormCredentials.java b/src/main/java/org/apache/sling/auth/form/impl/jaas/FormCredentials.java
new file mode 100644
index 0000000..6f127fc
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/form/impl/jaas/FormCredentials.java
@@ -0,0 +1,40 @@
+/*
+ * 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.auth.form.impl.jaas;
+
+import javax.jcr.Credentials;
+
+public class FormCredentials implements Credentials {
+ private final String userId;
+ private final String authData;
+
+ public FormCredentials(String userId, String authData) {
+ this.userId = userId;
+ this.authData = authData;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getAuthData() {
+ return authData;
+ }
+}
diff --git a/src/main/java/org/apache/sling/auth/form/impl/jaas/FormLoginModule.java b/src/main/java/org/apache/sling/auth/form/impl/jaas/FormLoginModule.java
new file mode 100644
index 0000000..e98ddf1
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/form/impl/jaas/FormLoginModule.java
@@ -0,0 +1,102 @@
+/*
+ * 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.auth.form.impl.jaas;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
+import org.apache.jackrabbit.oak.spi.security.authentication.PreAuthenticatedLogin;
+import org.apache.sling.auth.form.impl.FormAuthenticationHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class FormLoginModule extends AbstractLoginModule {
+ private static final Logger log = LoggerFactory.getLogger(FormLoginModule.class);
+
+ /**
+ * The set of supported credentials. required by the {@link org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule}
+ */
+ private static final Set<Class> SUPPORTED_CREDENTIALS = Collections.<Class>singleton(FormCredentials.class);
+ private static final char[] EMPTY_PWD = new char[0];
+
+ /**
+ * Extracted userId during login call.
+ */
+ private String userId;
+
+ @Override
+ protected Set<Class> getSupportedCredentials() {
+ return SUPPORTED_CREDENTIALS;
+ }
+
+ /**
+ * The {@link org.apache.sling.auth.form.impl.FormAuthenticationHandler} used to validate the credentials
+ * and its contents.
+ */
+ private final FormAuthenticationHandler authHandler;
+
+ FormLoginModule(FormAuthenticationHandler authHandler) {
+ this.authHandler = authHandler;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean login() throws LoginException {
+ Credentials credentials = getCredentials();
+ if (credentials instanceof FormCredentials) {
+ FormCredentials cred = (FormCredentials) credentials;
+ userId = cred.getUserId();
+
+ if (!authHandler.isValid(cred)){
+ log.debug("Invalid credentials");
+ return false;
+ }
+
+ if (userId == null) {
+ log.debug("Could not extract userId/credentials");
+ } else {
+ // we just set the login name and rely on the following login modules to populate the subject
+ sharedState.put(SHARED_KEY_PRE_AUTH_LOGIN, new PreAuthenticatedLogin(userId));
+ sharedState.put(SHARED_KEY_CREDENTIALS, new SimpleCredentials(userId, EMPTY_PWD));
+ sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
+ log.debug("login succeeded with trusted user: {}", userId);
+ }
+ }
+ return false;
+ }
+
+ public boolean commit() throws LoginException {
+ if (userId == null) {
+ // login attempt in this login module was not successful
+ clearState();
+ }
+ return false;
+ }
+
+ @Override
+ protected void clearState() {
+ userId = null;
+ super.clearState();
+ }
+}
diff --git a/src/main/java/org/apache/sling/auth/form/impl/jaas/JaasHelper.java b/src/main/java/org/apache/sling/auth/form/impl/jaas/JaasHelper.java
new file mode 100644
index 0000000..bb5a1b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/form/impl/jaas/JaasHelper.java
@@ -0,0 +1,128 @@
+/*
+ * 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.auth.form.impl.jaas;
+
+import java.util.Dictionary;
+
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.felix.jaas.LoginModuleFactory;
+import org.apache.sling.auth.form.impl.FormAuthenticationHandler;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JaasHelper {
+
+ private static final Logger log = LoggerFactory.getLogger(JaasHelper.class);
+
+ private final FormAuthenticationHandler authHandler;
+
+ /**
+ * login module service registration
+ */
+ private final ServiceRegistration factoryRegistration;
+
+ /**
+ * Opens/Initializes the helper and registers the login module factory (LMF) service if possible.
+ *
+ * @param ctx the bundle context
+ * @param properties properties that contain the jaas related LMF service properties.
+ */
+ public JaasHelper(FormAuthenticationHandler authHandler, BundleContext ctx, Dictionary properties) {
+ this.authHandler = authHandler;
+ // we dynamically register the LoginModuleFactory for the case we detect a login module.
+ if (hasSSOLoginModule(ctx)) {
+ factoryRegistration = registerLoginModuleFactory(ctx, properties);
+ } else {
+ factoryRegistration = null;
+ }
+ }
+
+ /**
+ * Checks if JAAS support is enabled and the SSO login module is present.
+ *
+ * @return {@code true} if JAAS support is enabled.
+ */
+ public boolean enabled() {
+ return factoryRegistration != null;
+ }
+
+
+ /**
+ * Closes this helper and unregisters the login module factory if needed.
+ */
+ public void close() {
+ if (factoryRegistration != null) {
+ factoryRegistration.unregister();
+ }
+ }
+
+ private ServiceRegistration registerLoginModuleFactory(BundleContext ctx, Dictionary properties) {
+ ServiceRegistration reg = null;
+ try {
+ java.util.Properties props = new java.util.Properties();
+ final String desc = "LoginModule Support for FormAuthenticationHandler";
+ props.put(Constants.SERVICE_DESCRIPTION, desc);
+ props.put(Constants.SERVICE_VENDOR, ctx.getBundle().getHeaders().get(Constants.BUNDLE_VENDOR));
+
+ props.put(LoginModuleFactory.JAAS_RANKING, properties.get(LoginModuleFactory.JAAS_RANKING));
+ props.put(LoginModuleFactory.JAAS_CONTROL_FLAG, properties.get(LoginModuleFactory.JAAS_CONTROL_FLAG));
+ props.put(LoginModuleFactory.JAAS_REALM_NAME, properties.get(LoginModuleFactory.JAAS_REALM_NAME));
+ reg = ctx.registerService(LoginModuleFactory.class.getName(),
+ new LoginModuleFactory() {
+ public LoginModule createLoginModule() {
+ return new FormLoginModule(authHandler);
+ }
+
+ @Override
+ public String toString() {
+ return desc + " (" +FormLoginModule.class.getName()+")";
+ }
+ },
+ props
+ );
+ log.info("Registered FormLoginModuleFactory");
+ } catch (Throwable e) {
+ log.error("unable to create an register the SSO login module factory", e);
+ }
+ return reg;
+ }
+
+ /**
+ * Checks if the {@link org.apache.sling.auth.form.impl.jaas.FormLoginModule} is available. This would not be the case
+ * in an non-oak setup. Note this only checks if the login module can be loaded, not if it is actually enabled
+ * in the jaas config.
+ *
+ * @return {@code true} if the SSOLoginModule is available.
+ */
+ private static boolean hasSSOLoginModule(BundleContext ctx) {
+ try {
+ ctx.getBundle().loadClass("org.apache.sling.auth.form.impl.jaas.FormLoginModule");
+ log.debug("FormLoginModule available.");
+ return true;
+ } catch (Throwable e) {
+ log.debug("no FormLoginModule available.", e);
+ }
+ return false;
+ }
+}
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
index 2d92666..7070cdb 100644
--- a/src/main/resources/OSGI-INF/metatype/metatype.properties
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -91,4 +91,19 @@
form.default.cookie.domain.description = The domain on which authentication cookies will \
be set, unless overridden in the AuthenticationInfo object. The default is null \
which means to set the cookie on the request domain.
-
\ No newline at end of file
+
+jaas.controlFlag.name = JAAS Control Flag
+jaas.controlFlag.description = Property name specifying whether or not a LoginModule is REQUIRED, REQUISITE, SUFFICIENT \
+ or OPTIONAL. Refer to the JAAS configuration documentation for more details around the meaning of these flags. \
+ Jackrabbit Oak only.
+
+jaas.realmName.name = JAAS Realm
+jaas.realmName.description = Property name specifying the realm name (or application name) against which the LoginModule \
+ is be registered. If no realm name is provided then LoginModule is registered with a default realm as configured in \
+ the Felix JAAS configuration. \
+ Jackrabbit Oak only.
+
+jaas.ranking.name = JAAS Ranking
+jaas.ranking.description = Property name specifying the ranking (i.e. sort order) of the configured login module \
+ entries. The entries are sorted in a descending order (i.e. higher value ranked configurations come first). \
+ Jackrabbit Oak only.