This is an automated email from the ASF dual-hosted git repository.
dklco pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git
The following commit(s) were added to refs/heads/master by this push:
new 11555fe Resolved SLING-7671 and fixing the bundle specification issue
in SLING-7264
11555fe is described below
commit 11555fe32802733b9b9c554f4e3153f5964b80e3
Author: Dan Klco <[email protected]>
AuthorDate: Wed May 16 00:30:18 2018 -0400
Resolved SLING-7671 and fixing the bundle specification issue in
SLING-7264
---
pom.xml | 374 ++--
.../auth/form/impl/AuthenticationFormServlet.java | 85 +-
.../auth/form/impl/FormAuthenticationHandler.java | 1816 +++++++++-----------
.../form/impl/FormAuthenticationHandlerConfig.java | 86 +
.../sling/auth/form/impl/jaas/JaasHelper.java | 22 +-
...mpl.FormAuthenticationHandlerConfig.properties} | 52 +-
.../form/impl/FormAuthenticationHandlerTest.java | 2 +-
7 files changed, 1164 insertions(+), 1273 deletions(-)
diff --git a/pom.xml b/pom.xml
index 312279b..a5c0131 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,206 +1,202 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
- <!--
- 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
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
- 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.
- -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>33</version>
+ <relativePath />
+ </parent>
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.apache.sling</groupId>
- <artifactId>sling</artifactId>
- <version>26</version>
- <relativePath />
- </parent>
+ <artifactId>org.apache.sling.auth.form</artifactId>
+ <version>1.0.9-SNAPSHOT</version>
+ <packaging>bundle</packaging>
- <artifactId>org.apache.sling.auth.form</artifactId>
- <version>1.0.9-SNAPSHOT</version>
- <packaging>bundle</packaging>
-
- <name>Apache Sling Form Based Authentication Handler</name>
- <description>
+ <name>Apache Sling Form Based Authentication Handler</name>
+ <description>
Bundle implementing form based authentication with login
and logout support. Authentication state is maintained in
a Cookie or in an HTTP Session. The password is only submitted
when first authenticating.
</description>
- <properties>
- <site.jira.version.id>12314785</site.jira.version.id>
- <site.javadoc.exclude>**.impl.**</site.javadoc.exclude>
- </properties>
+ <properties>
+ <site.jira.version.id>12314785</site.jira.version.id>
+ <site.javadoc.exclude>**.impl.**</site.javadoc.exclude>
+ </properties>
- <scm>
-
<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git</connection>
-
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git</developerConnection>
-
<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-auth-form.git</url>
- </scm>
+ <scm>
+
<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git</connection>
+
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git</developerConnection>
+
<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-auth-form.git</url>
+ </scm>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-scr-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <instructions>
- <Bundle-DocURL>
-
http://sling.apache.org/site/form-based-authenticationhandler.html
- </Bundle-DocURL>
- <Export-Package>
- org.apache.sling.auth.form;version=1.0
- </Export-Package>
- <Private-Package>
- org.apache.sling.auth.form.impl.*
- </Private-Package>
- <Import-Package>
- javax.security.auth.callback;
- javax.security.auth.login;
- org.apache.sling.jcr.jackrabbit.server.security;
- resolution:=optional,
- *
- </Import-Package>
- <Embed-Dependency>
-
org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/OsgiUtil.*",
-
commons-codec;inline="org/apache/commons/codec/binary/Base64.*
- |org/apache/commons/codec/binary/Hex*
- |org/apache/commons/codec/binary/StringUtils*
- |org/apache/commons/codec/BinaryEncoder*
- |org/apache/commons/codec/BinaryDecoder*
- |org/apache/commons/codec/Encoder*
- |org/apache/commons/codec/Decoder*
- |org/apache/commons/codec/EncoderException*
- |org/apache/commons/codec/DecoderException*"
- </Embed-Dependency>
- </instructions>
- </configuration>
- </plugin>
- </plugins>
- </build>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-DocURL>
+
http://sling.apache.org/site/form-based-authenticationhandler.html
+ </Bundle-DocURL>
+ <Export-Package>
+
org.apache.sling.auth.form;version=1.0
+ </Export-Package>
+ <Private-Package>
+
org.apache.sling.auth.form.impl.*
+ </Private-Package>
+ <Import-Package>
+
javax.security.auth.callback;
+
javax.security.auth.login;
+
org.apache.sling.jcr.jackrabbit.server.security;
+ resolution:=optional,
+ *
+ </Import-Package>
+ <Embed-Dependency>
+
org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/OsgiUtil.*",
+
commons-codec;inline="org/apache/commons/codec/binary/Base64.*
+
|org/apache/commons/codec/binary/Hex*
+
|org/apache/commons/codec/binary/StringUtils*
+
|org/apache/commons/codec/BinaryEncoder*
+
|org/apache/commons/codec/BinaryDecoder*
+
|org/apache/commons/codec/Encoder*
+
|org/apache/commons/codec/Decoder*
+
|org/apache/commons/codec/EncoderException*
+
|org/apache/commons/codec/DecoderException*"
+ </Embed-Dependency>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
- <dependencies>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr.annotations</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.auth.core</artifactId>
- <version>1.1.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>javax.jcr</groupId>
- <artifactId>jcr</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.jcr.jackrabbit.server</artifactId>
- <version>2.0.6</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.api</artifactId>
- <version>2.1.0</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.compendium</artifactId>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </dependency>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+
<artifactId>org.osgi.service.component.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+
<artifactId>org.osgi.service.metatype.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.auth.core</artifactId>
+ <version>1.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+
<artifactId>org.apache.sling.jcr.jackrabbit.server</artifactId>
+ <version>2.0.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
- <!-- Embedded Dependencies -->
- <dependency>
- <groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.commons.osgi</artifactId>
- <version>2.0.2-incubator</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <version>1.4</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.4</version>
- <scope>provided</scope>
- </dependency>
+ <!-- Embedded Dependencies -->
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.0.2-incubator</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.4</version>
+ <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>
+ <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>
- <artifactId>junit</artifactId>
- </dependency>
- <dependency>
- <groupId>org.jmock</groupId>
- <artifactId>jmock-junit4</artifactId>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- </dependency>
- <dependency>
- <groupId>org.easymock</groupId>
- <artifactId>easymock</artifactId>
- <version>3.4</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-module-junit4</artifactId>
- <version>1.6.4</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-api-easymock</artifactId>
- <version>1.6.4</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>3.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>1.6.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-easymock</artifactId>
+ <version>1.6.4</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git
a/src/main/java/org/apache/sling/auth/form/impl/AuthenticationFormServlet.java
b/src/main/java/org/apache/sling/auth/form/impl/AuthenticationFormServlet.java
index 5ad6667..78c865e 100644
---
a/src/main/java/org/apache/sling/auth/form/impl/AuthenticationFormServlet.java
+++
b/src/main/java/org/apache/sling/auth/form/impl/AuthenticationFormServlet.java
@@ -21,68 +21,55 @@ package org.apache.sling.auth.form.impl;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Service;
import org.apache.sling.auth.core.spi.AbstractAuthenticationFormServlet;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.form.FormReason;
+import org.osgi.service.component.annotations.Component;
/**
* The <code>AuthenticationFormServlet</code> provides the default login form
* used for Form Based Authentication.
*/
-@Component
-@Properties( {
- @Property(name = "service.description", value = "Default Login Form for
Form Based Authentication") })
-@Service(value = Servlet.class)
-@SuppressWarnings("serial")
+@Component(service = Servlet.class, property = { "sling.auth.requirements=" +
AuthenticationFormServlet.AUTH_REQUIREMENTS,
+ "sling.servlet.paths=" + AuthenticationFormServlet.SERVLET_PATH,
+ "service.description=Default Login Form for Form Based
Authentication" })
public class AuthenticationFormServlet extends
AbstractAuthenticationFormServlet {
- /**
- * The constant is used to provide the service registration path
- */
- @Property(name = "sling.servlet.paths")
- static final String SERVLET_PATH = "/system/sling/form/login";
+ public static final String SERVLET_PATH = "/system/sling/form/login";
+ public static final String AUTH_REQUIREMENTS = "-" + SERVLET_PATH;
- /**
- * This constant is used to provide the service registration property
- * indicating to pass requests to this servlet unauthenticated.
- */
- @Property(name = "sling.auth.requirements")
- @SuppressWarnings("unused")
- private static final String AUTH_REQUIREMENT = "-" + SERVLET_PATH;
+ private static final long serialVersionUID = -1497963620502763188L;
- /**
- * Returns an informational message according to the value provided in the
- * <code>j_reason</code> request parameter. Supported reasons are invalid
- * credentials and session timeout.
- *
- * @param request The request providing the parameter
- * @return The "translated" reason to render the login form or an empty
- * string if there is no specific reason
- */
- @Override
- protected String getReason(final HttpServletRequest request) {
- // return the resource attribute if set to a non-empty string
- Object resObj =
request.getAttribute(AuthenticationHandler.FAILURE_REASON);
- if (resObj instanceof FormReason) {
- return ((FormReason) resObj).toString();
- }
+ /**
+ * Returns an informational message according to the value provided in
the
+ * <code>j_reason</code> request parameter. Supported reasons are
invalid
+ * credentials and session timeout.
+ *
+ * @param request
+ * The request providing the parameter
+ * @return The "translated" reason to render the login form or an empty
string
+ * if there is no specific reason
+ */
+ @Override
+ protected String getReason(final HttpServletRequest request) {
+ // return the resource attribute if set to a non-empty string
+ Object resObj =
request.getAttribute(AuthenticationHandler.FAILURE_REASON);
+ if (resObj instanceof FormReason) {
+ return ((FormReason) resObj).toString();
+ }
- final String reason =
request.getParameter(AuthenticationHandler.FAILURE_REASON);
- if (reason != null) {
- try {
- return FormReason.valueOf(reason).toString();
- } catch (IllegalArgumentException iae) {
- // thrown if the reason is not an expected value, assume none
- }
+ final String reason =
request.getParameter(AuthenticationHandler.FAILURE_REASON);
+ if (reason != null) {
+ try {
+ return FormReason.valueOf(reason).toString();
+ } catch (IllegalArgumentException iae) {
+ // thrown if the reason is not an expected
value, assume none
+ }
- // no valid FormReason value, use raw value
- return reason;
- }
+ // no valid FormReason value, use raw value
+ return reason;
+ }
- return "";
- }
+ return "";
+ }
}
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 b821dd6..8695712 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
@@ -23,7 +23,6 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
-import java.util.Dictionary;
import java.util.HashMap;
import javax.jcr.Credentials;
@@ -36,16 +35,6 @@ import javax.servlet.http.HttpServletResponse;
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;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.PropertyOption;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.auth.Authenticator;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
@@ -59,11 +48,15 @@ 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;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,991 +64,818 @@ import org.slf4j.LoggerFactory;
* The <code>FormAuthenticationHandler</code> class implements the
authorization
* steps based on a cookie.
*/
-@Component(label = "%auth.form.name", description = "%auth.form.description",
metatype = true, name = "org.apache.sling.auth.form.FormAuthenticationHandler")
-@Properties( {
- @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 = LoginModuleFactory.JAAS_CONTROL_FLAG, value =
"sufficient"),
- @Property(name = LoginModuleFactory.JAAS_REALM_NAME, value =
"jackrabbit.oak"),
- @Property(name = LoginModuleFactory.JAAS_RANKING, intValue = 1000)
-})
-@Service
+@Component(name = "org.apache.sling.auth.form.FormAuthenticationHandler",
property = {
+ AuthenticationHandler.TYPE_PROPERTY + "="
+ + HttpServletRequest.FORM_AUTH }, service =
AuthenticationHandler.class, immediate = true)
+@Designate(ocd = FormAuthenticationHandlerConfig.class)
public class FormAuthenticationHandler extends
DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
- /**
- * The name of the parameter providing the login form URL.
- */
- @Property(value=AuthenticationFormServlet.SERVLET_PATH)
- private static final String PAR_LOGIN_FORM = "form.login.form";
-
- /**
- * The value of the {@link #PAR_AUTH_STORAGE} parameter indicating the use
- * of a Cookie to store the authentication data.
- */
- private static final String AUTH_STORAGE_COOKIE = "cookie";
-
- /**
- * The value of the {@link #PAR_AUTH_STORAGE} parameter indicating the use
- * of a session attribute to store the authentication data.
- */
- private static final String AUTH_STORAGE_SESSION_ATTRIBUTE = "session";
-
- /**
- * To be used to determine if the auth has value comes from a cookie or
from
- * a session attribute.
- */
- private static final String DEFAULT_AUTH_STORAGE = AUTH_STORAGE_COOKIE;
-
- @Property(value = DEFAULT_AUTH_STORAGE, options = {
- @PropertyOption(name = AUTH_STORAGE_COOKIE, value = "Cookie"),
- @PropertyOption(name = AUTH_STORAGE_SESSION_ATTRIBUTE, value =
"Session Attribute") })
- private static final String PAR_AUTH_STORAGE = "form.auth.storage";
-
- /**
- * The default Cookie or session attribute name
- *
- * @see #PAR_AUTH_NAME
- */
- private static final String DEFAULT_AUTH_NAME = "sling.formauth";
-
- /**
- * The name of the configuration parameter providing the Cookie or session
- * attribute name.
- */
- @Property(value = DEFAULT_AUTH_NAME)
- private static final String PAR_AUTH_NAME = "form.auth.name";
-
- /**
- * Default value for the {@link #PAR_CREDENTIALS_ATTRIBUTE_NAME} property
- */
- private static final String DEFAULT_CREDENTIALS_ATTRIBUTE_NAME =
DEFAULT_AUTH_NAME;
-
- /**
- * This is the name of the SimpleCredentials attribute that holds the auth
- * info extracted from the cookie value.
- */
- @Property(value = DEFAULT_CREDENTIALS_ATTRIBUTE_NAME)
- private static final String PAR_CREDENTIALS_ATTRIBUTE_NAME =
"form.credentials.name";
-
- /**
- * The default authentication data time out value.
- *
- * @see #PAR_AUTH_TIMEOUT
- */
- private static final int DEFAULT_AUTH_TIMEOUT = 30;
-
- /**
- * The number of minutes after which a login session times out. This value
- * is used as the expiry time set in the authentication data.
- */
- @Property(intValue = DEFAULT_AUTH_TIMEOUT)
- public static final String PAR_AUTH_TIMEOUT = "form.auth.timeout";
-
- private static final String DEFAULT_TOKEN_FILE = "cookie-tokens.bin";
-
- /**
- * The name of the file used to persist the security tokens
- */
- @Property(value = DEFAULT_TOKEN_FILE)
- private static final String PAR_TOKEN_FILE = "form.token.file";
-
- private static final boolean DEFAULT_TOKEN_FAST_SEED = false;
-
- /**
- * Whether to use a less secure but faster seeding mechanism to seed the
- * random number generator in the {@link TokenStore}. By default the faster
- * mechanism is disabled and the platform provided seeding is used. This
may
- * however block the startup considerably, particularly on Linux and
Solaris
- * platforms which use the (blocking but secure) <code>/dev/random</code>
- * device for seeding.
- */
- @Property(boolValue = DEFAULT_TOKEN_FAST_SEED)
- private static final String PAR_TOKEN_FAST_SEED = "form.token.fastseed";
-
- /**
- * The default include value.
- *
- * @see #PAR_INCLUDE_FORM
- */
- private static final boolean DEFAULT_INCLUDE_FORM = false;
-
- /**
- * Whether to redirect to the login form or simple do an include.
- */
- @Property(boolValue = DEFAULT_INCLUDE_FORM)
- public static final String PAR_INCLUDE_FORM = "form.use.include";
-
- /**
- * The default login after expire of a cookie.
- *
- * @see #PAR_LOGIN_AFTER_EXPIRE
- */
- private static final boolean DEFAULT_LOGIN_AFTER_EXPIRE = false;
-
- /**
- * Whether to present a login form when a users cookie expires, the default
- * is not to present the form.
- */
- @Property(boolValue = DEFAULT_LOGIN_AFTER_EXPIRE)
- private static final String PAR_LOGIN_AFTER_EXPIRE = "form.onexpire.login";
-
- /**
- * The default domain on which to see the auth cookie (if cookie storage
is used)
- */
- @Property
- private static final String PAR_DEFAULT_COOKIE_DOMAIN =
"form.default.cookie.domain";
-
- /**
- * The request method required for user name and password submission by the
- * form (value is "POST").
- */
- private static final String REQUEST_METHOD = "POST";
-
- /**
- * The last segment of the request URL for the user name and password
- * submission by the form (value is "/j_security_check").
- * <p>
- * This name is derived from the prescription in the Servlet API 2.4
- * Specification, Section SRV.12.5.3.1 Login Form Notes: <i>In order for
the
- * authentication to proceed appropriately, the action of the login form
- * must always be set to <code>j_security_check</code>.</i>
- */
- private static final String REQUEST_URL_SUFFIX = "/j_security_check";
-
- /**
- * The name of the form submission parameter providing the name of the user
- * to authenticate (value is "j_username").
- * <p>
- * This name is prescribed by the Servlet API 2.4 Specification, Section
- * SRV.12.5.3 Form Based Authentication.
- */
- private static final String PAR_J_USERNAME = "j_username";
-
- /**
- * The name of the form submission parameter providing the password of the
- * user to authenticate (value is "j_password").
- * <p>
- * This name is prescribed by the Servlet API 2.4 Specification, Section
- * SRV.12.5.3 Form Based Authentication.
- */
- private static final String PAR_J_PASSWORD = "j_password";
-
- /**
- * Key in the AuthenticationInfo map which contains the domain on which the
- * auth cookie should be set.
- */
- private static final String COOKIE_DOMAIN = "cookie.domain";
-
- /**
- * The factor to convert minute numbers into milliseconds used internally
- */
- private static final long MINUTES = 60L * 1000L;
-
- /** default log */
- private final Logger log = LoggerFactory.getLogger(getClass());
-
- private AuthenticationStorage authStorage;
-
- private String loginForm;
-
- /**
- * The timeout of a login session in milliseconds, converted from the
- * configuration property {@link #PAR_AUTH_TIMEOUT} by multiplying with
- * {@link #MINUTES}.
- */
- private long sessionTimeout;
-
- /**
- * The name of the credentials attribute which is set to the cookie data
- * to validate.
- */
- private String attrCookieAuthData;
-
- /**
- * The {@link TokenStore} used to persist and check authentication data
- */
- private TokenStore tokenStore;
-
- /**
- * The {@link FormLoginModulePlugin} service registration created when
- * this authentication handler is registered. If the login module plugin
- * cannot be created this field is set to <code>null</code>.
- */
- private ServiceRegistration loginModule;
-
- /**
- * If true, the handler will attempt to include the login form instead of
- * doing a redirect.
- */
- private boolean includeLoginForm;
-
- /**
- * The resource resolver factory used to resolve the login form as a
resource
- */
- @Reference(policy = ReferencePolicy.DYNAMIC, cardinality =
ReferenceCardinality.OPTIONAL_UNARY)
- private volatile ResourceResolverFactory resourceResolverFactory;
-
- /**
- * If true the login form will be presented when the token expires.
- */
- 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
- * be more appropriate, if no form fields are present in the request and if
- * the secure user data is not present either in the cookie or an HTTP
- * Session.
- */
- @Override
- public AuthenticationInfo extractCredentials(HttpServletRequest request,
- HttpServletResponse response) {
-
- AuthenticationInfo info = null;
-
- // 1. try credentials from POST'ed request parameters
- info = this.extractRequestParameterAuthentication(request);
-
- // 2. try credentials from the cookie or session
- if (info == null) {
- String authData = authStorage.extractAuthenticationInfo(request);
- if (authData != null) {
- if (tokenStore.isValid(authData)) {
- info = createAuthInfo(authData);
- } else {
- // clear the cookie, its invalid and we should get rid of
it
- // so that the invalid cookie isn't present on the authN
- // operation.
- authStorage.clear(request, response);
- if (this.loginAfterExpire ||
AuthUtil.isValidateRequest(request)) {
- // signal the requestCredentials method a previous
login
- // failure
- request.setAttribute(FAILURE_REASON,
FormReason.TIMEOUT);
- info = AuthenticationInfo.FAIL_AUTH;
- }
- }
- }
- }
-
- return info;
- }
-
- /**
- * Unless the <code>sling:authRequestLogin</code> to anything other than
- * <code>Form</code> this method either sends back a 403/FORBIDDEN response
- * if the <code>j_verify</code> parameter is set to <code>true</code> or
- * redirects to the login form to ask for credentials.
- * <p>
- * This method assumes the <code>j_verify</code> request parameter to only
- * be set in the initial username/password submission through the login
- * form. No further checks are applied, though, before sending back the
- * 403/FORBIDDEN response.
- */
- @Override
- public boolean requestCredentials(HttpServletRequest request,
- HttpServletResponse response) throws IOException {
-
- // 0. ignore this handler if an authentication handler is requested
- if (ignoreRequestCredentials(request)) {
- // consider this handler is not used
- return false;
- }
-
- //check the referrer to see if the request is for this handler
- if (!AuthUtil.checkReferer(request, loginForm)) {
- //not for this handler, so return
- return false;
- }
-
- final String resource = AuthUtil.setLoginResourceAttribute(request,
- request.getRequestURI());
-
- if (includeLoginForm && (resourceResolverFactory != null)) {
- ResourceResolver resourceResolver = null;
- try {
- resourceResolver =
resourceResolverFactory.getAdministrativeResourceResolver(null);
- Resource loginFormResource =
resourceResolver.resolve(loginForm);
- Servlet loginFormServlet =
loginFormResource.adaptTo(Servlet.class);
- if (loginFormServlet != null) {
- try {
- loginFormServlet.service(request, response);
- return true;
- } catch (ServletException e) {
- log.error("Failed to include the form: " + loginForm,
e);
- }
- }
- } catch (LoginException e) {
- log.error("Unable to get a resource resolver to include for
the login resource. Will redirect instead.");
- } finally {
- if (resourceResolver != null) {
- resourceResolver.close();
- }
- }
- }
-
- HashMap<String, String> params = new HashMap<String, String>();
- params.put(Authenticator.LOGIN_RESOURCE, resource);
-
- // append indication of previous login failure
- if (request.getAttribute(FAILURE_REASON) != null) {
- final Object jReason = request.getAttribute(FAILURE_REASON);
- @SuppressWarnings("rawtypes")
- final String reason = (jReason instanceof Enum)
- ? ((Enum) jReason).name()
- : jReason.toString();
- params.put(FAILURE_REASON, reason);
- }
-
- try {
- AuthUtil.sendRedirect(request, response, request.getContextPath()
+ loginForm, params);
- } catch (IOException e) {
- log.error("Failed to redirect to the login form " + loginForm, e);
- }
-
- return true;
- }
-
- /**
- * Clears all authentication state which might have been prepared by this
- * authentication handler.
- */
- @Override
- public void dropCredentials(HttpServletRequest request,
- HttpServletResponse response) {
- authStorage.clear(request, response);
- }
-
- // ---------- AuthenticationFeedbackHandler
-
- /**
- * Called after an unsuccessful login attempt. This implementation makes
- * sure the authentication data is removed either by removing the cookie or
- * by remove the HTTP Session attribute.
- */
- @Override
- public void authenticationFailed(HttpServletRequest request,
- HttpServletResponse response, AuthenticationInfo authInfo) {
-
- /*
- * Note: This method is called if this handler provided credentials
- * which cause a login failure
- */
-
- // clear authentication data from Cookie or Http Session
- authStorage.clear(request, response);
-
- // signal the reason for login failure
- request.setAttribute(FAILURE_REASON, FormReason.INVALID_CREDENTIALS);
- }
-
- /**
- * Called after successful login with the given authentication info. This
- * implementation ensures the authentication data is set in either the
- * cookie or the HTTP session with the correct security tokens.
- * <p>
- * If no authentication data already exists, it is created. Otherwise if
the
- * data has expired the data is updated with a new security token and a new
- * expiry time.
- * <p>
- * If creating or updating the authentication data fails, it is actually
- * removed from the cookie or the HTTP session and future requests will not
- * be authenticated any longer.
- */
- @Override
- public boolean authenticationSucceeded(HttpServletRequest request,
- HttpServletResponse response, AuthenticationInfo authInfo) {
-
- /*
- * Note: This method is called if this handler provided credentials
- * which succeeded login into the repository
- */
-
- // ensure fresh authentication data
- refreshAuthData(request, response, authInfo);
-
- final boolean result;
- // SLING-1847: only consider a resource redirect if this is a POST
request
- // to the j_security_check URL
- if (REQUEST_METHOD.equals(request.getMethod())
- &&
request.getRequestURI().endsWith(REQUEST_URL_SUFFIX)) {
-
- if (DefaultAuthenticationFeedbackHandler.handleRedirect(request,
response)) {
- // terminate request, all done in the default handler
- result = false;
- } else {
- // check whether redirect is requested by the resource parameter
- final String targetResource =
AuthUtil.getLoginResource(request, null);
- if (targetResource != null) {
- try {
- if (response.isCommitted()) {
- throw new IllegalStateException("Response is
already committed");
- }
- response.resetBuffer();
-
- StringBuilder b = new StringBuilder();
- if (AuthUtil.isRedirectValid(request, targetResource)) {
- b.append(targetResource);
- } else if (request.getContextPath().length() == 0) {
- b.append("/");
- } else {
- b.append(request.getContextPath());
- }
- response.sendRedirect(b.toString());
- } catch (IOException ioe) {
- log.error("Failed to send redirect to: " +
targetResource, ioe);
- }
-
- // terminate request, all done
- result = true;
- } else {
- // no redirect, hence continue processing
- result = false;
- }
- }
- } else {
- // no redirect, hence continue processing
- result = false;
- }
-
- // no redirect
- return result;
- }
-
- @Override
- public String toString() {
- return "Form Based Authentication Handler";
- }
-
- // --------- Force HTTP Basic Auth ---------
-
- /**
- * Returns <code>true</code> if this authentication handler should ignore
- * the call to
- * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)}.
- * <p>
- * This method returns <code>true</code> if the
- * {@link #REQUEST_LOGIN_PARAMETER} is set to any value other than "Form"
- * (HttpServletRequest.FORM_AUTH).
- */
- private boolean ignoreRequestCredentials(final HttpServletRequest request)
{
- final String requestLogin =
request.getParameter(REQUEST_LOGIN_PARAMETER);
- return requestLogin != null
- && !HttpServletRequest.FORM_AUTH.equals(requestLogin);
- }
-
- /**
- * Ensures the authentication data is set (if not set yet) and the expiry
- * time is prolonged (if auth data already existed).
- * <p>
- * This method is intended to be called in case authentication succeeded.
- *
- * @param request The current request
- * @param response The current response
- * @param authInfo The authentication info used to successful log in
- */
- private void refreshAuthData(final HttpServletRequest request,
- final HttpServletResponse response,
- final AuthenticationInfo authInfo) {
-
- // get current authentication data, may be missing after first login
- String authData = getCookieAuthData(authInfo);
-
- // check whether we have to "store" or create the data
- final boolean refreshCookie = needsRefresh(authData,
- this.sessionTimeout);
-
- // add or refresh the stored auth hash
- if (refreshCookie) {
- long expires = System.currentTimeMillis() + this.sessionTimeout;
- try {
- authData = null;
- authData = tokenStore.encode(expires, authInfo.getUser());
- } catch (InvalidKeyException e) {
- log.error(e.getMessage(), e);
- } catch (IllegalStateException e) {
- log.error(e.getMessage(), e);
- } catch (UnsupportedEncodingException e) {
- log.error(e.getMessage(), e);
- } catch (NoSuchAlgorithmException e) {
- log.error(e.getMessage(), e);
- }
-
- if (authData != null) {
- authStorage.set(request, response, authData, authInfo);
- } else {
- authStorage.clear(request, response);
- }
- }
- }
-
- // --------- Request Parameter Auth ---------
-
- private AuthenticationInfo extractRequestParameterAuthentication(
- HttpServletRequest request) {
- AuthenticationInfo info = null;
-
- // only consider login form parameters if this is a POST request
- // to the j_security_check URL
- if (REQUEST_METHOD.equals(request.getMethod())
- && request.getRequestURI().endsWith(REQUEST_URL_SUFFIX)) {
-
- String user = request.getParameter(PAR_J_USERNAME);
- String pwd = request.getParameter(PAR_J_PASSWORD);
-
- if (user != null && pwd != null) {
- info = new AuthenticationInfo(HttpServletRequest.FORM_AUTH,
- user, pwd.toCharArray());
- info.put(AuthConstants.AUTH_INFO_LOGIN, new Object());
-
- // if this request is providing form credentials, we have to
- // make sure, that the request is redirected after successful
- // authentication, otherwise the request may be processed
- // as a POST request to the j_security_check page (unless
- // the j_validate parameter is set); but only if this is not
- // a validation request
- if (!AuthUtil.isValidateRequest(request)) {
- AuthUtil.setLoginResourceAttribute(request,
request.getContextPath());
- }
- }
- }
-
- return info;
- }
-
- private AuthenticationInfo createAuthInfo(final String authData) {
- final String userId = getUserId(authData);
- if (userId == null) {
- return null;
- }
-
- final AuthenticationInfo info = new AuthenticationInfo(
- HttpServletRequest.FORM_AUTH, userId);
-
- if (jaasHelper.enabled()) {
- //JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS
- info.put("user.jcr.credentials", new FormCredentials(userId,
authData));
- } else {
- info.put(attrCookieAuthData, authData);
- }
-
- return info;
- }
-
- private String getCookieAuthData(final AuthenticationInfo info) {
- Object data = info.get(attrCookieAuthData);
- if (data instanceof String) {
- return (String) data;
- }
- return null;
- }
-
- // ---------- LoginModulePlugin support
-
- private String getCookieAuthData(final Credentials credentials) {
- if (credentials instanceof SimpleCredentials) {
- Object data = ((SimpleCredentials)
credentials).getAttribute(attrCookieAuthData);
- if (data instanceof String) {
- return (String) data;
- }
- } else if (credentials instanceof FormCredentials){
- return ((FormCredentials) credentials).getAuthData();
- }
-
- // no SimpleCredentials or no valid attribute
- return null;
- }
-
- boolean hasAuthData(final Credentials credentials) {
- return getCookieAuthData(credentials) != null;
- }
-
- public boolean isValid(final Credentials credentials) {
- String authData = getCookieAuthData(credentials);
- if (authData != null) {
- return tokenStore.isValid(authData);
- }
-
- // no authdata, not valid
- return false;
- }
-
- // ---------- SCR Integration
----------------------------------------------
-
- /**
- * Called by SCR to activate the authentication handler.
- *
- * @throws InvalidKeyException
- * @throws NoSuchAlgorithmException
- * @throws IllegalStateException
- * @throws UnsupportedEncodingException
- */
- protected void activate(ComponentContext componentContext)
- throws InvalidKeyException, NoSuchAlgorithmException,
- IllegalStateException, UnsupportedEncodingException {
-
- 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);
-
- final String authName = OsgiUtil.toString(
- properties.get(PAR_AUTH_NAME), DEFAULT_AUTH_NAME);
-
- String defaultCookieDomain = OsgiUtil.toString(
- properties.get(PAR_DEFAULT_COOKIE_DOMAIN), "");
- if (defaultCookieDomain.length() == 0) {
- defaultCookieDomain = null;
- }
-
- final String authStorage = OsgiUtil.toString(
- properties.get(PAR_AUTH_STORAGE), DEFAULT_AUTH_STORAGE);
- if (AUTH_STORAGE_SESSION_ATTRIBUTE.equals(authStorage)) {
-
- this.authStorage = new SessionStorage(authName);
- log.info("Using HTTP Session store with attribute name {}",
- authName);
-
- } else {
-
- this.authStorage = new CookieStorage(authName,
defaultCookieDomain);
- log.info("Using Cookie store with name {}", authName);
-
- }
-
- this.attrCookieAuthData = OsgiUtil.toString(
- properties.get(PAR_CREDENTIALS_ATTRIBUTE_NAME),
- DEFAULT_CREDENTIALS_ATTRIBUTE_NAME);
- log.info("Setting Auth Data attribute name {}", attrCookieAuthData);
-
- int timeoutMinutes = OsgiUtil.toInteger(
- properties.get(PAR_AUTH_TIMEOUT), DEFAULT_AUTH_TIMEOUT);
- if (timeoutMinutes < 1) {
- timeoutMinutes = DEFAULT_AUTH_TIMEOUT;
- }
- log.info("Setting session timeout {} minutes", timeoutMinutes);
- this.sessionTimeout = MINUTES * timeoutMinutes;
-
- final String tokenFileName = OsgiUtil.toString(
- properties.get(PAR_TOKEN_FILE), DEFAULT_TOKEN_FILE);
- final File tokenFile = getTokenFile(tokenFileName,
- componentContext.getBundleContext());
- final boolean fastSeed = OsgiUtil.toBoolean(
- properties.get(PAR_TOKEN_FAST_SEED), DEFAULT_TOKEN_FAST_SEED);
- log.info("Storing tokens in {}", tokenFile.getAbsolutePath());
- this.tokenStore = new TokenStore(tokenFile, sessionTimeout, fastSeed);
-
- this.loginModule = null;
- 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);
-
- this.loginAfterExpire =
OsgiUtil.toBoolean(properties.get(PAR_LOGIN_AFTER_EXPIRE),
DEFAULT_LOGIN_AFTER_EXPIRE);
- }
-
- @Deactivate
- protected void deactivate() {
- if (jaasHelper != null){
- jaasHelper.close();
- jaasHelper = null;
- }
-
- if (loginModule != null) {
- loginModule.unregister();
- loginModule = null;
- }
- }
-
- /**
- * Returns an absolute file indicating the file to use to persist the
- * security tokens.
- * <p>
- * This method is not part of the API of this class and is package private
- * to enable unit tests.
- *
- * @param tokenFileName The configured file name, must not be null
- * @param bundleContext The BundleContext to use to make an relative file
- * absolute
- * @return The absolute file
- */
- File getTokenFile(final String tokenFileName,
- final BundleContext bundleContext) {
- File tokenFile = new File(tokenFileName);
- if (tokenFile.isAbsolute()) {
- return tokenFile;
- }
-
- tokenFile = bundleContext.getDataFile(tokenFileName);
- if (tokenFile == null) {
- final String slingHome = bundleContext.getProperty("sling.home");
- if (slingHome != null) {
- tokenFile = new File(slingHome, tokenFileName);
- } else {
- tokenFile = new File(tokenFileName);
- }
- }
-
- return tokenFile.getAbsoluteFile();
- }
-
- /**
- * Returns the user id from the authentication data. If the authentication
- * data is a non-<code>null</code> value with 3 fields separated by an @
- * sign, the value of the third field is returned. Otherwise
- * <code>null</code> is returned.
- * <p>
- * This method is not part of the API of this class and is package private
- * to enable unit tests.
- *
- * @param authData
- * @return
- */
- String getUserId(final String authData) {
- if (authData != null) {
- String[] parts = TokenStore.split(authData);
- if (parts != null) {
- return parts[2];
- }
- }
- return null;
- }
-
- /**
- * Refresh the cookie periodically.
- *
- * @param sessionTimeout time to live for the session
- * @return true or false
- */
- private boolean needsRefresh(final String authData,
- final long sessionTimeout) {
- boolean updateCookie = false;
- if (authData == null) {
- updateCookie = true;
- } else {
- String[] parts = TokenStore.split(authData);
- if (parts != null && parts.length == 3) {
- long cookieTime = Long.parseLong(parts[1].substring(1));
- if (System.currentTimeMillis() + (sessionTimeout / 2) >
cookieTime) {
- updateCookie = true;
- }
- }
- }
- return updateCookie;
- }
-
- /**
- * The <code>AuthenticationStorage</code> interface abstracts the API
- * required to store the authentication data in an HTTP cookie or in an
- * HTTP Session. The concrete class -- {@link CookieStorage} or
- * {@link SessionStorage} -- is selected using the
- * {@link FormAuthenticationHandler#PAR_AUTH_STORAGE} configuration
- * parameter, {@link CookieStorage} by default.
- */
- private static interface AuthenticationStorage {
- String extractAuthenticationInfo(HttpServletRequest request);
-
- void set(HttpServletRequest request, HttpServletResponse response,
- String authData, AuthenticationInfo info);
-
- void clear(HttpServletRequest request, HttpServletResponse response);
- }
-
- /**
- * The <code>CookieStorage</code> class supports storing the
- * authentication data in an HTTP Cookie.
- */
- private static class CookieStorage implements AuthenticationStorage {
-
- /**
- * The Set-Cookie header used to manage the login cookie.
- *
- * @see CookieStorage#setCookie(HttpServletRequest,
HttpServletResponse,
- * String, String, int, String)
- */
- private static final String HEADER_SET_COOKIE = "Set-Cookie";
-
- private final String cookieName;
- private final String domainCookieName;
- private final String defaultCookieDomain;
-
- public CookieStorage(final String cookieName, final String
defaultCookieDomain) {
- this.cookieName = cookieName;
- this.domainCookieName = cookieName + "." + COOKIE_DOMAIN;
- this.defaultCookieDomain = defaultCookieDomain;
- }
-
- @Override
- public String extractAuthenticationInfo(HttpServletRequest request) {
- Cookie[] cookies = request.getCookies();
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- if (this.cookieName.equals(cookie.getName())) {
- // found the cookie, so try to extract the credentials
- // from it and reverse the base64 encoding
- String value = cookie.getValue();
- if (value.length() > 0) {
- try {
- return new String(Base64.decodeBase64(value),
- "UTF-8");
- } catch (UnsupportedEncodingException e1) {
- throw new RuntimeException(e1);
- }
- }
- }
- }
- }
-
- return null;
- }
-
- @Override
- public void set(HttpServletRequest request,
- HttpServletResponse response, String authData,
AuthenticationInfo info) {
- // base64 encode to handle any special characters
- String cookieValue;
- try {
- cookieValue =
Base64.encodeBase64URLSafeString(authData.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException e1) {
- throw new RuntimeException(e1);
- }
-
- // send the cookie to the response
- String cookieDomain = (String) info.get(COOKIE_DOMAIN);
- if (cookieDomain == null || cookieDomain.length() == 0) {
- cookieDomain = defaultCookieDomain;
- }
- setCookie(request, response, this.cookieName, cookieValue, -1,
- cookieDomain);
-
- // send the cookie domain cookie if domain is not null
- if (cookieDomain != null) {
- setCookie(request, response, this.domainCookieName,
- cookieDomain, -1, cookieDomain);
- }
- }
-
- @Override
- public void clear(HttpServletRequest request,
- HttpServletResponse response) {
- Cookie oldCookie = null;
- String oldCookieDomain = null;
- Cookie[] cookies = request.getCookies();
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- if (this.cookieName.equals(cookie.getName())) {
- // found the cookie
- oldCookie = cookie;
- } else if (this.domainCookieName.equals(cookie.getName()))
{
- oldCookieDomain = cookie.getValue();
- }
- }
- }
-
- // remove the old cookie from the client
- if (oldCookie != null) {
- setCookie(request, response, this.cookieName, "", 0,
oldCookieDomain);
- if (oldCookieDomain != null && oldCookieDomain.length() > 0) {
- setCookie(request, response, this.domainCookieName, "", 0,
oldCookieDomain);
- }
- }
- }
-
- private void setCookie(final HttpServletRequest request,
- final HttpServletResponse response, final String name,
- final String value, final int age, final String domain) {
-
- final String ctxPath = request.getContextPath();
- final String cookiePath = (ctxPath == null || ctxPath.length() ==
0)
- ? "/"
- : ctxPath;
-
- /*
- * The Servlet Spec 2.5 does not allow us to set the commonly used
- * HttpOnly attribute on cookies (Servlet API 3.0 does) so we
create
- * the Set-Cookie header manually. See
- * http://www.owasp.org/index.php/HttpOnly for information on what
- * the HttpOnly attribute is used for.
- */
-
- final StringBuilder header = new StringBuilder();
-
- // default setup with name, value, cookie path and HttpOnly
- header.append(name).append("=").append(value);
- header.append("; Path=").append(cookiePath);
- header.append("; HttpOnly"); // don't allow JS access
-
- // set the cookie domain if so configured
- if (domain != null) {
- header.append("; Domain=").append(domain);
- }
-
- // Only set the Max-Age attribute to remove the cookie
- if (age >= 0) {
- header.append("; Max-Age=").append(age);
- }
-
- // ensure the cookie is secured if this is an https request
- if (request.isSecure()) {
- header.append("; Secure");
- }
-
- response.addHeader(HEADER_SET_COOKIE, header.toString());
- }
- }
-
- /**
- * The <code>SessionStorage</code> class provides support to store the
- * authentication data in an HTTP Session.
- */
- private static class SessionStorage implements AuthenticationStorage {
- private final String sessionAttributeName;
-
- SessionStorage(final String sessionAttributeName) {
- this.sessionAttributeName = sessionAttributeName;
- }
-
- @Override
- public String extractAuthenticationInfo(HttpServletRequest request) {
- HttpSession session = request.getSession(false);
- if (session != null) {
- Object attribute = session.getAttribute(sessionAttributeName);
- if (attribute instanceof String) {
- return (String) attribute;
- }
- }
- return null;
- }
-
- @Override
- public void set(HttpServletRequest request,
- HttpServletResponse response, String authData,
AuthenticationInfo info) {
- // store the auth hash as a session attribute
- HttpSession session = request.getSession();
- session.setAttribute(sessionAttributeName, authData);
- }
-
- @Override
- public void clear(HttpServletRequest request,
- HttpServletResponse response) {
- HttpSession session = request.getSession(false);
- if (session != null) {
- session.removeAttribute(sessionAttributeName);
- }
- }
-
- }
+ /**
+ * The request method required for user name and password submission by
the form
+ * (value is "POST").
+ */
+ private static final String REQUEST_METHOD = "POST";
+
+ /**
+ * The last segment of the request URL for the user name and password
submission
+ * by the form (value is "/j_security_check").
+ * <p>
+ * This name is derived from the prescription in the Servlet API 2.4
+ * Specification, Section SRV.12.5.3.1 Login Form Notes: <i>In order
for the
+ * authentication to proceed appropriately, the action of the login
form must
+ * always be set to <code>j_security_check</code>.</i>
+ */
+ private static final String REQUEST_URL_SUFFIX = "/j_security_check";
+
+ /**
+ * The name of the form submission parameter providing the name of the
user to
+ * authenticate (value is "j_username").
+ * <p>
+ * This name is prescribed by the Servlet API 2.4 Specification, Section
+ * SRV.12.5.3 Form Based Authentication.
+ */
+ private static final String PAR_J_USERNAME = "j_username";
+
+ /**
+ * The name of the form submission parameter providing the password of
the user
+ * to authenticate (value is "j_password").
+ * <p>
+ * This name is prescribed by the Servlet API 2.4 Specification, Section
+ * SRV.12.5.3 Form Based Authentication.
+ */
+ private static final String PAR_J_PASSWORD = "j_password";
+
+ /**
+ * Key in the AuthenticationInfo map which contains the domain on which
the auth
+ * cookie should be set.
+ */
+ private static final String COOKIE_DOMAIN = "cookie.domain";
+
+ /**
+ * The factor to convert minute numbers into milliseconds used
internally
+ */
+ private static final long MINUTES = 60L * 1000L;
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private AuthenticationStorage authStorage;
+
+ private String loginForm;
+
+ /**
+ * The timeout of a login session in milliseconds, converted from the
+ * configuration property {@link #PAR_AUTH_TIMEOUT} by multiplying with
+ * {@link #MINUTES}.
+ */
+ private long sessionTimeout;
+
+ /**
+ * The name of the credentials attribute which is set to the cookie
data to
+ * validate.
+ */
+ private String attrCookieAuthData;
+
+ /**
+ * The {@link TokenStore} used to persist and check authentication data
+ */
+ private TokenStore tokenStore;
+
+ /**
+ * The {@link FormLoginModulePlugin} service registration created when
this
+ * authentication handler is registered. If the login module plugin
cannot be
+ * created this field is set to <code>null</code>.
+ */
+ private ServiceRegistration<?> loginModule;
+
+ /**
+ * If true, the handler will attempt to include the login form instead
of doing
+ * a redirect.
+ */
+ private boolean includeLoginForm;
+
+ /**
+ * The resource resolver factory used to resolve the login form as a
resource
+ */
+ @Reference(policy = ReferencePolicy.DYNAMIC, cardinality =
ReferenceCardinality.OPTIONAL)
+ private volatile ResourceResolverFactory resourceResolverFactory;
+
+ /**
+ * If true the login form will be presented when the token expires.
+ */
+ 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 be
+ * more appropriate, if no form fields are present in the request and
if the
+ * secure user data is not present either in the cookie or an HTTP
Session.
+ */
+ @Override
+ public AuthenticationInfo extractCredentials(HttpServletRequest
request, HttpServletResponse response) {
+
+ AuthenticationInfo info = null;
+
+ // 1. try credentials from POST'ed request parameters
+ info = this.extractRequestParameterAuthentication(request);
+
+ // 2. try credentials from the cookie or session
+ if (info == null) {
+ String authData =
authStorage.extractAuthenticationInfo(request);
+ if (authData != null) {
+ if (tokenStore.isValid(authData)) {
+ info = createAuthInfo(authData);
+ } else {
+ // clear the cookie, its invalid and we
should get rid of it
+ // so that the invalid cookie isn't
present on the authN
+ // operation.
+ authStorage.clear(request, response);
+ if (this.loginAfterExpire ||
AuthUtil.isValidateRequest(request)) {
+ // signal the
requestCredentials method a previous login
+ // failure
+
request.setAttribute(FAILURE_REASON, FormReason.TIMEOUT);
+ info =
AuthenticationInfo.FAIL_AUTH;
+ }
+ }
+ }
+ }
+
+ return info;
+ }
+
+ /**
+ * Unless the <code>sling:authRequestLogin</code> to anything other than
+ * <code>Form</code> this method either sends back a 403/FORBIDDEN
response if
+ * the <code>j_verify</code> parameter is set to <code>true</code> or
redirects
+ * to the login form to ask for credentials.
+ * <p>
+ * This method assumes the <code>j_verify</code> request parameter to
only be
+ * set in the initial username/password submission through the login
form. No
+ * further checks are applied, though, before sending back the
403/FORBIDDEN
+ * response.
+ */
+ @Override
+ public boolean requestCredentials(HttpServletRequest request,
HttpServletResponse response) throws IOException {
+
+ // 0. ignore this handler if an authentication handler is
requested
+ if (ignoreRequestCredentials(request)) {
+ // consider this handler is not used
+ return false;
+ }
+
+ // check the referrer to see if the request is for this handler
+ if (!AuthUtil.checkReferer(request, loginForm)) {
+ // not for this handler, so return
+ return false;
+ }
+
+ final String resource =
AuthUtil.setLoginResourceAttribute(request, request.getRequestURI());
+
+ if (includeLoginForm && (resourceResolverFactory != null)) {
+ ResourceResolver resourceResolver = null;
+ try {
+ resourceResolver =
resourceResolverFactory.getAdministrativeResourceResolver(null);
+ Resource loginFormResource =
resourceResolver.resolve(loginForm);
+ Servlet loginFormServlet =
loginFormResource.adaptTo(Servlet.class);
+ if (loginFormServlet != null) {
+ try {
+
loginFormServlet.service(request, response);
+ return true;
+ } catch (ServletException e) {
+ log.error("Failed to include
the form: " + loginForm, e);
+ }
+ }
+ } catch (LoginException e) {
+ log.error(
+ "Unable to get a resource
resolver to include for the login resource. Will redirect instead.");
+ } finally {
+ if (resourceResolver != null) {
+ resourceResolver.close();
+ }
+ }
+ }
+
+ HashMap<String, String> params = new HashMap<String, String>();
+ params.put(Authenticator.LOGIN_RESOURCE, resource);
+
+ // append indication of previous login failure
+ if (request.getAttribute(FAILURE_REASON) != null) {
+ final Object jReason =
request.getAttribute(FAILURE_REASON);
+ @SuppressWarnings("rawtypes")
+ final String reason = (jReason instanceof Enum) ?
((Enum) jReason).name() : jReason.toString();
+ params.put(FAILURE_REASON, reason);
+ }
+
+ try {
+ AuthUtil.sendRedirect(request, response,
request.getContextPath() + loginForm, params);
+ } catch (IOException e) {
+ log.error("Failed to redirect to the login form " +
loginForm, e);
+ }
+
+ return true;
+ }
+
+ /**
+ * Clears all authentication state which might have been prepared by
this
+ * authentication handler.
+ */
+ @Override
+ public void dropCredentials(HttpServletRequest request,
HttpServletResponse response) {
+ authStorage.clear(request, response);
+ }
+
+ // ---------- AuthenticationFeedbackHandler
+
+ /**
+ * Called after an unsuccessful login attempt. This implementation
makes sure
+ * the authentication data is removed either by removing the cookie or
by remove
+ * the HTTP Session attribute.
+ */
+ @Override
+ public void authenticationFailed(HttpServletRequest request,
HttpServletResponse response,
+ AuthenticationInfo authInfo) {
+
+ /*
+ * Note: This method is called if this handler provided
credentials which cause
+ * a login failure
+ */
+
+ // clear authentication data from Cookie or Http Session
+ authStorage.clear(request, response);
+
+ // signal the reason for login failure
+ request.setAttribute(FAILURE_REASON,
FormReason.INVALID_CREDENTIALS);
+ }
+
+ /**
+ * Called after successful login with the given authentication info.
This
+ * implementation ensures the authentication data is set in either the
cookie or
+ * the HTTP session with the correct security tokens.
+ * <p>
+ * If no authentication data already exists, it is created. Otherwise
if the
+ * data has expired the data is updated with a new security token and a
new
+ * expiry time.
+ * <p>
+ * If creating or updating the authentication data fails, it is
actually removed
+ * from the cookie or the HTTP session and future requests will not be
+ * authenticated any longer.
+ */
+ @Override
+ public boolean authenticationSucceeded(HttpServletRequest request,
HttpServletResponse response,
+ AuthenticationInfo authInfo) {
+
+ /*
+ * Note: This method is called if this handler provided
credentials which
+ * succeeded login into the repository
+ */
+
+ // ensure fresh authentication data
+ refreshAuthData(request, response, authInfo);
+
+ final boolean result;
+ // SLING-1847: only consider a resource redirect if this is a
POST request
+ // to the j_security_check URL
+ if (REQUEST_METHOD.equals(request.getMethod()) &&
request.getRequestURI().endsWith(REQUEST_URL_SUFFIX)) {
+
+ if
(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)) {
+ // terminate request, all done in the default
handler
+ result = false;
+ } else {
+ // check whether redirect is requested by the
resource parameter
+ final String targetResource =
AuthUtil.getLoginResource(request, null);
+ if (targetResource != null) {
+ try {
+ if (response.isCommitted()) {
+ throw new
IllegalStateException("Response is already committed");
+ }
+ response.resetBuffer();
+
+ StringBuilder b = new
StringBuilder();
+ if
(AuthUtil.isRedirectValid(request, targetResource)) {
+
b.append(targetResource);
+ } else if
(request.getContextPath().length() == 0) {
+ b.append("/");
+ } else {
+
b.append(request.getContextPath());
+ }
+
response.sendRedirect(b.toString());
+ } catch (IOException ioe) {
+ log.error("Failed to send
redirect to: " + targetResource, ioe);
+ }
+
+ // terminate request, all done
+ result = true;
+ } else {
+ // no redirect, hence continue
processing
+ result = false;
+ }
+ }
+ } else {
+ // no redirect, hence continue processing
+ result = false;
+ }
+
+ // no redirect
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Form Based Authentication Handler";
+ }
+
+ // --------- Force HTTP Basic Auth ---------
+
+ /**
+ * Returns <code>true</code> if this authentication handler should
ignore the
+ * call to {@link #requestCredentials(HttpServletRequest,
HttpServletResponse)}.
+ * <p>
+ * This method returns <code>true</code> if the {@link
#REQUEST_LOGIN_PARAMETER}
+ * is set to any value other than "Form" (HttpServletRequest.FORM_AUTH).
+ */
+ private boolean ignoreRequestCredentials(final HttpServletRequest
request) {
+ final String requestLogin =
request.getParameter(REQUEST_LOGIN_PARAMETER);
+ return requestLogin != null &&
!HttpServletRequest.FORM_AUTH.equals(requestLogin);
+ }
+
+ /**
+ * Ensures the authentication data is set (if not set yet) and the
expiry time
+ * is prolonged (if auth data already existed).
+ * <p>
+ * This method is intended to be called in case authentication
succeeded.
+ *
+ * @param request
+ * The current request
+ * @param response
+ * The current response
+ * @param authInfo
+ * The authentication info used to successful log in
+ */
+ private void refreshAuthData(final HttpServletRequest request, final
HttpServletResponse response,
+ final AuthenticationInfo authInfo) {
+
+ // get current authentication data, may be missing after first
login
+ String authData = getCookieAuthData(authInfo);
+
+ // check whether we have to "store" or create the data
+ final boolean refreshCookie = needsRefresh(authData,
this.sessionTimeout);
+
+ // add or refresh the stored auth hash
+ if (refreshCookie) {
+ long expires = System.currentTimeMillis() +
this.sessionTimeout;
+ try {
+ authData = null;
+ authData = tokenStore.encode(expires,
authInfo.getUser());
+ } catch (InvalidKeyException e) {
+ log.error(e.getMessage(), e);
+ } catch (IllegalStateException e) {
+ log.error(e.getMessage(), e);
+ } catch (UnsupportedEncodingException e) {
+ log.error(e.getMessage(), e);
+ } catch (NoSuchAlgorithmException e) {
+ log.error(e.getMessage(), e);
+ }
+
+ if (authData != null) {
+ authStorage.set(request, response, authData,
authInfo);
+ } else {
+ authStorage.clear(request, response);
+ }
+ }
+ }
+
+ // --------- Request Parameter Auth ---------
+
+ private AuthenticationInfo
extractRequestParameterAuthentication(HttpServletRequest request) {
+ AuthenticationInfo info = null;
+
+ // only consider login form parameters if this is a POST request
+ // to the j_security_check URL
+ if (REQUEST_METHOD.equals(request.getMethod()) &&
request.getRequestURI().endsWith(REQUEST_URL_SUFFIX)) {
+
+ String user = request.getParameter(PAR_J_USERNAME);
+ String pwd = request.getParameter(PAR_J_PASSWORD);
+
+ if (user != null && pwd != null) {
+ info = new
AuthenticationInfo(HttpServletRequest.FORM_AUTH, user, pwd.toCharArray());
+ info.put(AuthConstants.AUTH_INFO_LOGIN, new
Object());
+
+ // if this request is providing form
credentials, we have to
+ // make sure, that the request is redirected
after successful
+ // authentication, otherwise the request may be
processed
+ // as a POST request to the j_security_check
page (unless
+ // the j_validate parameter is set); but only
if this is not
+ // a validation request
+ if (!AuthUtil.isValidateRequest(request)) {
+
AuthUtil.setLoginResourceAttribute(request, request.getContextPath());
+ }
+ }
+ }
+
+ return info;
+ }
+
+ private AuthenticationInfo createAuthInfo(final String authData) {
+ final String userId = getUserId(authData);
+ if (userId == null) {
+ return null;
+ }
+
+ final AuthenticationInfo info = new
AuthenticationInfo(HttpServletRequest.FORM_AUTH, userId);
+
+ if (jaasHelper.enabled()) {
+ // JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS
+ info.put("user.jcr.credentials", new
FormCredentials(userId, authData));
+ } else {
+ info.put(attrCookieAuthData, authData);
+ }
+
+ return info;
+ }
+
+ private String getCookieAuthData(final AuthenticationInfo info) {
+ Object data = info.get(attrCookieAuthData);
+ if (data instanceof String) {
+ return (String) data;
+ }
+ return null;
+ }
+
+ // ---------- LoginModulePlugin support
+
+ private String getCookieAuthData(final Credentials credentials) {
+ if (credentials instanceof SimpleCredentials) {
+ Object data = ((SimpleCredentials)
credentials).getAttribute(attrCookieAuthData);
+ if (data instanceof String) {
+ return (String) data;
+ }
+ } else if (credentials instanceof FormCredentials) {
+ return ((FormCredentials) credentials).getAuthData();
+ }
+
+ // no SimpleCredentials or no valid attribute
+ return null;
+ }
+
+ boolean hasAuthData(final Credentials credentials) {
+ return getCookieAuthData(credentials) != null;
+ }
+
+ public boolean isValid(final Credentials credentials) {
+ String authData = getCookieAuthData(credentials);
+ if (authData != null) {
+ return tokenStore.isValid(authData);
+ }
+
+ // no authdata, not valid
+ return false;
+ }
+
+ // ---------- SCR Integration
----------------------------------------------
+
+ /**
+ * Called by SCR to activate the authentication handler.
+ *
+ * @throws InvalidKeyException
+ * @throws NoSuchAlgorithmException
+ * @throws IllegalStateException
+ * @throws UnsupportedEncodingException
+ */
+ protected void activate(FormAuthenticationHandlerConfig config,
ComponentContext componentContext)
+ throws InvalidKeyException, NoSuchAlgorithmException,
IllegalStateException, UnsupportedEncodingException {
+
+ this.jaasHelper = new JaasHelper(this,
componentContext.getBundleContext(), config);
+ this.loginForm = config.form_login_form();
+ log.info("Login Form URL {}", loginForm);
+
+ final String authName = config.form_auth_name();
+
+ String defaultCookieDomain =
config.form_default_cookie_domain();
+ if (defaultCookieDomain.length() == 0) {
+ defaultCookieDomain = null;
+ }
+
+ final String authStorage = config.form_auth_storage();
+ if
(FormAuthenticationHandlerConfig.AUTH_STORAGE_SESSION_ATTRIBUTE.equals(authStorage))
{
+ this.authStorage = new SessionStorage(authName);
+ log.info("Using HTTP Session store with attribute name
{}", authName);
+ } else {
+ this.authStorage = new CookieStorage(authName,
defaultCookieDomain);
+ log.info("Using Cookie store with name {}", authName);
+ }
+
+ this.attrCookieAuthData = config.form_credentials_name();
+ log.info("Setting Auth Data attribute name {}",
attrCookieAuthData);
+
+ int timeoutMinutes = config.form_auth_timeout();
+ if (timeoutMinutes < 1) {
+ timeoutMinutes =
FormAuthenticationHandlerConfig.DEFAULT_AUTH_TIMEOUT;
+ }
+ log.info("Setting session timeout {} minutes", timeoutMinutes);
+ this.sessionTimeout = MINUTES * timeoutMinutes;
+
+ final String tokenFileName = config.form_token_file();
+ final File tokenFile = getTokenFile(tokenFileName,
componentContext.getBundleContext());
+ final boolean fastSeed = config.form_token_fastseed();
+ log.info("Storing tokens in {}", tokenFile.getAbsolutePath());
+ this.tokenStore = new TokenStore(tokenFile, sessionTimeout,
fastSeed);
+
+ this.loginModule = null;
+ 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 = config.useInclude();
+
+ this.loginAfterExpire = config.form_onexpire_login();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (jaasHelper != null) {
+ jaasHelper.close();
+ jaasHelper = null;
+ }
+
+ if (loginModule != null) {
+ loginModule.unregister();
+ loginModule = null;
+ }
+ }
+
+ /**
+ * Returns an absolute file indicating the file to use to persist the
security
+ * tokens.
+ * <p>
+ * This method is not part of the API of this class and is package
private to
+ * enable unit tests.
+ *
+ * @param tokenFileName
+ * The configured file name, must not be null
+ * @param bundleContext
+ * The BundleContext to use to make an relative file absolute
+ * @return The absolute file
+ */
+ File getTokenFile(final String tokenFileName, final BundleContext
bundleContext) {
+ File tokenFile = new File(tokenFileName);
+ if (tokenFile.isAbsolute()) {
+ return tokenFile;
+ }
+
+ tokenFile = bundleContext.getDataFile(tokenFileName);
+ if (tokenFile == null) {
+ final String slingHome =
bundleContext.getProperty("sling.home");
+ if (slingHome != null) {
+ tokenFile = new File(slingHome, tokenFileName);
+ } else {
+ tokenFile = new File(tokenFileName);
+ }
+ }
+
+ return tokenFile.getAbsoluteFile();
+ }
+
+ /**
+ * Returns the user id from the authentication data. If the
authentication data
+ * is a non-<code>null</code> value with 3 fields separated by an @
sign, the
+ * value of the third field is returned. Otherwise <code>null</code> is
+ * returned.
+ * <p>
+ * This method is not part of the API of this class and is package
private to
+ * enable unit tests.
+ *
+ * @param authData
+ * @return
+ */
+ String getUserId(final String authData) {
+ if (authData != null) {
+ String[] parts = TokenStore.split(authData);
+ if (parts != null) {
+ return parts[2];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Refresh the cookie periodically.
+ *
+ * @param sessionTimeout
+ * time to live for the session
+ * @return true or false
+ */
+ private boolean needsRefresh(final String authData, final long
sessionTimeout) {
+ boolean updateCookie = false;
+ if (authData == null) {
+ updateCookie = true;
+ } else {
+ String[] parts = TokenStore.split(authData);
+ if (parts != null && parts.length == 3) {
+ long cookieTime =
Long.parseLong(parts[1].substring(1));
+ if (System.currentTimeMillis() +
(sessionTimeout / 2) > cookieTime) {
+ updateCookie = true;
+ }
+ }
+ }
+ return updateCookie;
+ }
+
+ /**
+ * The <code>AuthenticationStorage</code> interface abstracts the API
required
+ * to store the authentication data in an HTTP cookie or in an HTTP
Session. The
+ * concrete class -- {@link CookieStorage} or {@link SessionStorage} --
is
+ * selected using the {@link FormAuthenticationHandler#PAR_AUTH_STORAGE}
+ * configuration parameter, {@link CookieStorage} by default.
+ */
+ private static interface AuthenticationStorage {
+ String extractAuthenticationInfo(HttpServletRequest request);
+
+ void set(HttpServletRequest request, HttpServletResponse
response, String authData, AuthenticationInfo info);
+
+ void clear(HttpServletRequest request, HttpServletResponse
response);
+ }
+
+ /**
+ * The <code>CookieStorage</code> class supports storing the
authentication data
+ * in an HTTP Cookie.
+ */
+ private static class CookieStorage implements AuthenticationStorage {
+
+ /**
+ * The Set-Cookie header used to manage the login cookie.
+ *
+ * @see CookieStorage#setCookie(HttpServletRequest,
HttpServletResponse, String,
+ * String, int, String)
+ */
+ private static final String HEADER_SET_COOKIE = "Set-Cookie";
+
+ private final String cookieName;
+ private final String domainCookieName;
+ private final String defaultCookieDomain;
+
+ public CookieStorage(final String cookieName, final String
defaultCookieDomain) {
+ this.cookieName = cookieName;
+ this.domainCookieName = cookieName + "." +
COOKIE_DOMAIN;
+ this.defaultCookieDomain = defaultCookieDomain;
+ }
+
+ @Override
+ public String extractAuthenticationInfo(HttpServletRequest
request) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if
(this.cookieName.equals(cookie.getName())) {
+ // found the cookie, so try to
extract the credentials
+ // from it and reverse the
base64 encoding
+ String value =
cookie.getValue();
+ if (value.length() > 0) {
+ try {
+ return new
String(Base64.decodeBase64(value), "UTF-8");
+ } catch
(UnsupportedEncodingException e1) {
+ throw new
RuntimeException(e1);
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void set(HttpServletRequest request, HttpServletResponse
response, String authData,
+ AuthenticationInfo info) {
+ // base64 encode to handle any special characters
+ String cookieValue;
+ try {
+ cookieValue =
Base64.encodeBase64URLSafeString(authData.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e1) {
+ throw new RuntimeException(e1);
+ }
+
+ // send the cookie to the response
+ String cookieDomain = (String) info.get(COOKIE_DOMAIN);
+ if (cookieDomain == null || cookieDomain.length() == 0)
{
+ cookieDomain = defaultCookieDomain;
+ }
+ setCookie(request, response, this.cookieName,
cookieValue, -1, cookieDomain);
+
+ // send the cookie domain cookie if domain is not null
+ if (cookieDomain != null) {
+ setCookie(request, response,
this.domainCookieName, cookieDomain, -1, cookieDomain);
+ }
+ }
+
+ @Override
+ public void clear(HttpServletRequest request,
HttpServletResponse response) {
+ Cookie oldCookie = null;
+ String oldCookieDomain = null;
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if
(this.cookieName.equals(cookie.getName())) {
+ // found the cookie
+ oldCookie = cookie;
+ } else if
(this.domainCookieName.equals(cookie.getName())) {
+ oldCookieDomain =
cookie.getValue();
+ }
+ }
+ }
+
+ // remove the old cookie from the client
+ if (oldCookie != null) {
+ setCookie(request, response, this.cookieName,
"", 0, oldCookieDomain);
+ if (oldCookieDomain != null &&
oldCookieDomain.length() > 0) {
+ setCookie(request, response,
this.domainCookieName, "", 0, oldCookieDomain);
+ }
+ }
+ }
+
+ private void setCookie(final HttpServletRequest request, final
HttpServletResponse response, final String name,
+ final String value, final int age, final String
domain) {
+
+ final String ctxPath = request.getContextPath();
+ final String cookiePath = (ctxPath == null ||
ctxPath.length() == 0) ? "/" : ctxPath;
+
+ /*
+ * The Servlet Spec 2.5 does not allow us to set the
commonly used HttpOnly
+ * attribute on cookies (Servlet API 3.0 does) so we
create the Set-Cookie
+ * header manually. See
http://www.owasp.org/index.php/HttpOnly for information
+ * on what the HttpOnly attribute is used for.
+ */
+
+ final StringBuilder header = new StringBuilder();
+
+ // default setup with name, value, cookie path and
HttpOnly
+ header.append(name).append("=").append(value);
+ header.append("; Path=").append(cookiePath);
+ header.append("; HttpOnly"); // don't allow JS access
+
+ // set the cookie domain if so configured
+ if (domain != null) {
+ header.append("; Domain=").append(domain);
+ }
+
+ // Only set the Max-Age attribute to remove the cookie
+ if (age >= 0) {
+ header.append("; Max-Age=").append(age);
+ }
+
+ // ensure the cookie is secured if this is an https
request
+ if (request.isSecure()) {
+ header.append("; Secure");
+ }
+
+ response.addHeader(HEADER_SET_COOKIE,
header.toString());
+ }
+ }
+
+ /**
+ * The <code>SessionStorage</code> class provides support to store the
+ * authentication data in an HTTP Session.
+ */
+ private static class SessionStorage implements AuthenticationStorage {
+ private final String sessionAttributeName;
+
+ SessionStorage(final String sessionAttributeName) {
+ this.sessionAttributeName = sessionAttributeName;
+ }
+
+ @Override
+ public String extractAuthenticationInfo(HttpServletRequest
request) {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ Object attribute =
session.getAttribute(sessionAttributeName);
+ if (attribute instanceof String) {
+ return (String) attribute;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void set(HttpServletRequest request, HttpServletResponse
response, String authData,
+ AuthenticationInfo info) {
+ // store the auth hash as a session attribute
+ HttpSession session = request.getSession();
+ session.setAttribute(sessionAttributeName, authData);
+ }
+
+ @Override
+ public void clear(HttpServletRequest request,
HttpServletResponse response) {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ session.removeAttribute(sessionAttributeName);
+ }
+ }
+
+ }
}
\ No newline at end of file
diff --git
a/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerConfig.java
b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerConfig.java
new file mode 100644
index 0000000..cbc0d86
--- /dev/null
+++
b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerConfig.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
+
+/**
+ * The configuration for the <code>FormAuthenticationHandler</code>
+ *
+ * @see org.apache.sling.auth.form.impl.FormAuthenticationHandler
+ */
+@ObjectClassDefinition(name = "%auth.form.name", description =
"%auth.form.description")
+public @interface FormAuthenticationHandlerConfig {
+
+ public static final String AUTH_STORAGE_SESSION_ATTRIBUTE = "session";
+ public static final int DEFAULT_AUTH_TIMEOUT = 30;
+
+ @AttributeDefinition(defaultValue = "sling.formauth", name =
"%authName.name", description = "%authName.description")
+ String form_auth_name();
+
+ @AttributeDefinition(defaultValue = "cookie", options = { @Option(label
= "Cookie", value = "cookie"),
+ @Option(label = "Session Attribute", value =
AUTH_STORAGE_SESSION_ATTRIBUTE) }, name = "%authStorage.name", description =
"%authStorage.description")
+ String form_auth_storage();
+
+ @AttributeDefinition(defaultValue = "30", type = AttributeType.INTEGER,
name = "%authTimeout.name", description = "%authTimeout.description")
+ int form_auth_timeout();
+
+ @AttributeDefinition(defaultValue = "sling.formauth", name =
"%credentialsName.name", description = "%credentialsName.description")
+ String form_credentials_name();
+
+ @AttributeDefinition(name = "%defaultCookieDomain.name", description =
"%defaultCookieDomain.description")
+ String form_default_cookie_domain();
+
+ @AttributeDefinition(defaultValue =
AuthenticationFormServlet.SERVLET_PATH, name = "%loginForm.name", description =
"%loginForm.description")
+ String form_login_form();
+
+ @AttributeDefinition(defaultValue = "false", type =
AttributeType.BOOLEAN, name = "%onexpireLogin.name", description =
"%onexpireLogin.description")
+ boolean form_onexpire_login();
+
+ @AttributeDefinition(defaultValue = "false", type =
AttributeType.BOOLEAN, name = "%tokenFileFastseed.name", description =
"%tokenFileFastseed.description")
+ boolean form_token_fastseed();
+
+ @AttributeDefinition(defaultValue = "cookie-tokens.bin", name =
"%tokenFile.name", description = "%tokenFile.description")
+ String form_token_file();
+
+ @AttributeDefinition(defaultValue = "sufficient", options = {
@Option(label = "Optional", value = "optional"),
+ @Option(label = "Required", value = "required"),
@Option(label = "Requisite", value = "requisite"),
+ @Option(label = "Sufficient", value = "sufficient") },
name = "%jaasControlFlag.name", description = "%jaasControlFlag.description")
+ String jaas_controlFlag();
+
+ @AttributeDefinition(defaultValue = "1000", type =
AttributeType.INTEGER, name = "%jaasRanking.name", description =
"%jaasRanking.description")
+ int jaas_ranking();
+
+ @AttributeDefinition(defaultValue = "jackrabbit.oak", name =
"%jaasRealm.name", description = "%jaasRealm.description")
+ String jaas_realmName();
+
+ @AttributeDefinition(defaultValue = {
+ "/" }, cardinality = Integer.MAX_VALUE, name =
"%path.name", description = "%path.description")
+ String[] path();
+
+ @AttributeDefinition(defaultValue = "0", type = AttributeType.INTEGER,
name = "%service.ranking.name", description = "%service.ranking.description")
+ int service_ranking();
+
+ @AttributeDefinition(defaultValue = "false", type =
AttributeType.BOOLEAN, name = "%useInclude.name", description =
"%useInclude.description")
+ boolean useInclude();
+
+}
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
index bb5a1b5..9b619c1 100644
--- 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
@@ -20,11 +20,13 @@
package org.apache.sling.auth.form.impl.jaas;
import java.util.Dictionary;
+import java.util.Hashtable;
import javax.security.auth.spi.LoginModule;
import org.apache.felix.jaas.LoginModuleFactory;
import org.apache.sling.auth.form.impl.FormAuthenticationHandler;
+import org.apache.sling.auth.form.impl.FormAuthenticationHandlerConfig;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
@@ -40,19 +42,19 @@ public class JaasHelper {
/**
* login module service registration
*/
- private final ServiceRegistration factoryRegistration;
+ 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.
+ * @param config properties that contain the jaas related LMF service
properties.
*/
- public JaasHelper(FormAuthenticationHandler authHandler, BundleContext
ctx, Dictionary properties) {
+ public JaasHelper(FormAuthenticationHandler authHandler, BundleContext
ctx, FormAuthenticationHandlerConfig config) {
this.authHandler = authHandler;
// we dynamically register the LoginModuleFactory for the case we
detect a login module.
if (hasSSOLoginModule(ctx)) {
- factoryRegistration = registerLoginModuleFactory(ctx, properties);
+ factoryRegistration = registerLoginModuleFactory(ctx, config);
} else {
factoryRegistration = null;
}
@@ -77,17 +79,17 @@ public class JaasHelper {
}
}
- private ServiceRegistration registerLoginModuleFactory(BundleContext ctx,
Dictionary properties) {
- ServiceRegistration reg = null;
+ private ServiceRegistration<?> registerLoginModuleFactory(BundleContext
ctx, FormAuthenticationHandlerConfig config) {
+ ServiceRegistration<?> reg = null;
try {
- java.util.Properties props = new java.util.Properties();
+ Dictionary<String, Object> props = new Hashtable<String,Object>();
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));
+ props.put(LoginModuleFactory.JAAS_RANKING, config.jaas_ranking());
+ props.put(LoginModuleFactory.JAAS_CONTROL_FLAG,
config.jaas_controlFlag());
+ props.put(LoginModuleFactory.JAAS_REALM_NAME,
config.jaas_realmName());
reg = ctx.registerService(LoginModuleFactory.class.getName(),
new LoginModuleFactory() {
public LoginModule createLoginModule() {
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties
b/src/main/resources/OSGI-INF/l10n/org.apache.sling.auth.form.impl.FormAuthenticationHandlerConfig.properties
similarity index 68%
rename from src/main/resources/OSGI-INF/metatype/metatype.properties
rename to
src/main/resources/OSGI-INF/l10n/org.apache.sling.auth.form.impl.FormAuthenticationHandlerConfig.properties
index 7070cdb..7143726 100644
--- a/src/main/resources/OSGI-INF/metatype/metatype.properties
+++
b/src/main/resources/OSGI-INF/l10n/org.apache.sling.auth.form.impl.FormAuthenticationHandlerConfig.properties
@@ -31,34 +31,34 @@ path.description = Repository path for which this
authentication handler \
should be used by Sling. If this is empty, the authentication handler will \
be disabled.
-form.login.form.name = Login Form
-form.login.form.description = The URL (without any context path prefix) to \
+loginForm.name = Login Form
+loginForm.description = The URL (without any context path prefix) to \
redirect the client to to present the login form. The default value is \
"/system/sling/form/login".
-form.auth.storage.name = Hash Storage
-form.auth.storage.description = The type of storage used to provide the \
+authStorage.name = Hash Storage
+authStorage.description = The type of storage used to provide the \
authentication state. Valid values are cookie and session. The default value \
(cookie) also applies if any setting other than the supported values is \
configured.
-form.auth.name.name = Cookie/Attribute Name
-form.auth.name.description = The name of the Cookie or HTTP Session attribute \
+authName.name = Cookie/Attribute Name
+authName.description = The name of the Cookie or HTTP Session attribute \
providing the authentication state. The default value is "sling.formauth".
-form.credentials.name.name = Credentials Attribute
-form.credentials.name.description = The name of the SimpleCredentials \
+credentialsName.name = Credentials Attribute
+credentialsName.description = The name of the SimpleCredentials \
attribute used to provide the authentication data to the LoginModulePlugin. \
The default value is "sling.formauth".
-form.auth.timeout.name = Timeout
-form.auth.timeout.description = The number of minutes after which a login \
+authTimeout.name = Timeout
+authTimeout.description = The number of minutes after which a login \
session times out. This value is used as the expiry time set in the \
authentication data. The default value is 30 minutes. If the value is set \
a value less than 1, the default value is used instead.
-form.token.file.name = Security Token File
-form.token.file.description = The name of the file used to persist the \
+tokenFile.name = Security Token File
+tokenFile.description = The name of the file used to persist the \
security tokens. The default value is cookie-tokens.bin. This property \
currently refers to a file stored in the file system. If the path is a \
relative path, the file is either stored in the Authentication Handler bundle
\
@@ -67,8 +67,8 @@ form.token.file.description = The name of the file used to
persist the \
working directory. In the future this file may be store in the JCR Repository
\
to support clustering scenarios.
-form.token.fastseed.name = Fast Seed Generator
-form.token.fastseed.description = Defines whether the random number generator \
+tokenFileFastseed.name = Fast Seed Generator
+tokenFileFastseed.description = Defines whether the random number generator \
should be seeded with a fast but less secure method. If this property is not \
set or is set to "false" the seed generator of the Java platform will be \
used. By default the secure seed generator is used, which may block startup \
@@ -77,33 +77,33 @@ form.token.fastseed.description = Defines whether the
random number generator \
service.ranking.name = Ranking
service.ranking.description = The relative ranking of this service.
-form.use.include.name = Include Form
-form.use.include.description = If true, this authentication handler will
attempt \
+useInclude.name = Include Form
+useInclude.description = If true, this authentication handler will attempt \
to include a Servlet resource at the login form path. If false, a redirect
will \
be used instead.
-form.onexpire.login.name = On Login Expire, Re-login
-form.onexpire.login.description = If true, when the form login expires the
user \
+onexpireLogin.name = On Login Expire, Re-login
+onexpireLogin.description = If true, when the form login expires the user \
will be prompted to re-login. If false they become an anonymous user. The
default \
is false.
-form.default.cookie.domain.name = Default Cookie Domain
-form.default.cookie.domain.description = The domain on which authentication
cookies will \
+defaultCookieDomain.name = Default Cookie Domain
+defaultCookieDomain.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.
-jaas.controlFlag.name = JAAS Control Flag
-jaas.controlFlag.description = Property name specifying whether or not a
LoginModule is REQUIRED, REQUISITE, SUFFICIENT \
+jaasControlFlag.name = JAAS Control Flag
+jaasControlFlag.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 \
+jaasRealm.name = JAAS Realm
+jaasRealm.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 \
+jaasRanking.name = JAAS Ranking
+jaasRanking.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.
diff --git
a/src/test/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerTest.java
b/src/test/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerTest.java
index 2dfe487..7457cfc 100644
---
a/src/test/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerTest.java
+++
b/src/test/java/org/apache/sling/auth/form/impl/FormAuthenticationHandlerTest.java
@@ -36,7 +36,7 @@ import org.apache.sling.api.auth.Authenticator;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;
import org.hamcrest.Description;
-import org.hamcrest.text.StringStartsWith;
+import org.hamcrest.core.StringStartsWith;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Action;
--
To stop receiving notification emails like this one, please contact
[email protected].