diff --git a/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java index c043e268aa..cfdc1bacf9 100644 --- a/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java +++ b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java @@ -16,6 +16,7 @@ package net.sf.acegisecurity; import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken; /** @@ -39,7 +40,7 @@ public class AuthenticationTrustResolverImpl //~ Instance fields ======================================================== private Class anonymousClass = AnonymousAuthenticationToken.class; - private Class rememberMeClass; + private Class rememberMeClass = RememberMeAuthenticationToken.class; //~ Methods ================================================================ diff --git a/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProvider.java new file mode 100644 index 0000000000..6445601956 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProvider.java @@ -0,0 +1,86 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.providers.rememberme; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.AuthenticationException; +import net.sf.acegisecurity.BadCredentialsException; +import net.sf.acegisecurity.providers.AuthenticationProvider; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; + +import org.springframework.util.Assert; + + +/** + * An {@link AuthenticationProvider} implementation that validates {@link + * net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken}s. + * + *
+ * To be successfully validated, the {@link{@link + * net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken#getKeyHash()} + * must match this class' {@link #getKey()}. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class RememberMeAuthenticationProvider implements AuthenticationProvider, + InitializingBean { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(RememberMeAuthenticationProvider.class); + + //~ Instance fields ======================================================== + + private String key; + + //~ Methods ================================================================ + + public void setKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public void afterPropertiesSet() throws Exception { + Assert.hasLength(key); + } + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + if (!supports(authentication.getClass())) { + return null; + } + + if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication) + .getKeyHash()) { + throw new BadCredentialsException( + "The presented RememberMeAuthenticationToken does not contain the expected key"); + } + + return authentication; + } + + public boolean supports(Class authentication) { + return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationToken.java new file mode 100644 index 0000000000..9917e8be5e --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationToken.java @@ -0,0 +1,139 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.providers.rememberme; + +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.AbstractAuthenticationToken; + +import java.io.Serializable; + + +/** + * Represents a rememberedAuthentication.
+ *
+ *
+ * A remembered Authentication must provide a fully valid
+ * Authentication, including the GrantedAuthority[]s
+ * that apply.
+ *
UserDetails)
+ * @param authorities the authorities granted to the principal
+ *
+ * @throws IllegalArgumentException if a null was passed
+ */
+ public RememberMeAuthenticationToken(String key, Object principal,
+ GrantedAuthority[] authorities) {
+ if ((key == null) || ("".equals(key)) || (principal == null)
+ || "".equals(principal) || (authorities == null)
+ || (authorities.length == 0)) {
+ throw new IllegalArgumentException(
+ "Cannot pass null or empty values to constructor");
+ }
+
+ for (int i = 0; i < authorities.length; i++) {
+ if (authorities[i] == null) {
+ throw new IllegalArgumentException("Granted authority element "
+ + i
+ + " is null - GrantedAuthority[] cannot contain any null elements");
+ }
+ }
+
+ this.keyHash = key.hashCode();
+ this.principal = principal;
+ this.authorities = authorities;
+ }
+
+ protected RememberMeAuthenticationToken() {
+ throw new IllegalArgumentException("Cannot use default constructor");
+ }
+
+ //~ Methods ================================================================
+
+ /**
+ * Ignored (always true).
+ *
+ * @param isAuthenticated ignored
+ */
+ public void setAuthenticated(boolean isAuthenticated) {
+ // ignored
+ }
+
+ /**
+ * Always returns true.
+ *
+ * @return true
+ */
+ public boolean isAuthenticated() {
+ return true;
+ }
+
+ public GrantedAuthority[] getAuthorities() {
+ return this.authorities;
+ }
+
+ /**
+ * Always returns an empty String
+ *
+ * @return an empty String
+ */
+ public Object getCredentials() {
+ return "";
+ }
+
+ public int getKeyHash() {
+ return this.keyHash;
+ }
+
+ public Object getPrincipal() {
+ return this.principal;
+ }
+
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+
+ if (obj instanceof RememberMeAuthenticationToken) {
+ RememberMeAuthenticationToken test = (RememberMeAuthenticationToken) obj;
+
+ if (this.getKeyHash() != test.getKeyHash()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/rememberme/package.html b/core/src/main/java/org/acegisecurity/providers/rememberme/package.html
new file mode 100644
index 0000000000..fa3a953b37
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/rememberme/package.html
@@ -0,0 +1,5 @@
+
+
+Authentication provider that processes RememberMeAuthenticationTokens.
+
+
diff --git a/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java
index e4ca68576f..9d45e1e89f 100644
--- a/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java
+++ b/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java
@@ -26,12 +26,16 @@ import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.providers.cas.ProxyUntrustedException;
+import net.sf.acegisecurity.ui.rememberme.NullRememberMeServices;
+import net.sf.acegisecurity.ui.rememberme.RememberMeServices;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
import java.io.IOException;
import javax.servlet.Filter;
@@ -106,6 +110,7 @@ public abstract class AbstractProcessingFilter implements Filter,
//~ Instance fields ========================================================
private AuthenticationManager authenticationManager;
+ private RememberMeServices rememberMeServices = new NullRememberMeServices();
/**
* Where to redirect the browser if authentication fails due to incorrect
@@ -194,6 +199,14 @@ public abstract class AbstractProcessingFilter implements Filter,
*/
public abstract String getDefaultFilterProcessesUrl();
+ public void setRememberMeServices(RememberMeServices rememberMeServices) {
+ this.rememberMeServices = rememberMeServices;
+ }
+
+ public RememberMeServices getRememberMeServices() {
+ return rememberMeServices;
+ }
+
/**
* Performs actual authentication.
*
@@ -306,6 +319,8 @@ public abstract class AbstractProcessingFilter implements Filter,
throw new IllegalArgumentException(
"authenticationManager must be specified");
}
+
+ Assert.notNull(this.rememberMeServices);
}
/**
@@ -370,7 +385,8 @@ public abstract class AbstractProcessingFilter implements Filter,
HttpServletResponse response) throws IOException {}
protected void onSuccessfulAuthentication(HttpServletRequest request,
- HttpServletResponse response) throws IOException {}
+ HttpServletResponse response, Authentication authResult)
+ throws IOException {}
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException {}
@@ -429,7 +445,9 @@ public abstract class AbstractProcessingFilter implements Filter,
+ targetUrl);
}
- onSuccessfulAuthentication(request, response);
+ onSuccessfulAuthentication(request, response, authResult);
+
+ rememberMeServices.loginSuccess(request, response, authResult);
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
@@ -481,6 +499,8 @@ public abstract class AbstractProcessingFilter implements Filter,
onUnsuccessfulAuthentication(request, response);
+ rememberMeServices.loginFail(request, response);
+
response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
+ failureUrl));
}
diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/NullRememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/NullRememberMeServices.java
new file mode 100644
index 0000000000..6299662bc0
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/ui/rememberme/NullRememberMeServices.java
@@ -0,0 +1,47 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.ui.rememberme;
+
+import net.sf.acegisecurity.Authentication;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Implementation of {@link NullRememberMeServices} that does nothing.
+ *
+ * + * Used as a default by several framework classes. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class NullRememberMeServices implements RememberMeServices { + //~ Methods ================================================================ + + public Authentication autoLogin(HttpServletRequest request, + HttpServletResponse response) { + return null; + } + + public void loginFail(HttpServletRequest request, + HttpServletResponse response) {} + + public void loginSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication successfulAuthentication) {} +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilter.java new file mode 100644 index 0000000000..347c559966 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilter.java @@ -0,0 +1,131 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.ui.rememberme; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.sf.acegisecurity.context.security.SecureContext; +import net.sf.acegisecurity.context.security.SecureContextUtils; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + + +/** + * Detects if there is noAuthentication object in the
+ * ContextHolder, and populates it with a remember-me
+ * authentication token if a {@link
+ * net.sf.acegisecurity.ui.rememberme.RememberMeServices} implementation so
+ * requests.
+ *
+ *
+ * Concrete RememberMeServices implementations will have their
+ * {@link
+ * net.sf.acegisecurity.ui.rememberme.RememberMeServices#autoLogin(HttpServletRequest,
+ * HttpServletResponse)} method called by this filter. The
+ * Authentication or null returned by that method
+ * will be placed into the ContextHolder.
+ *
+ * Do not use this class directly. Instead configure
+ * web.xml to use the {@link
+ * net.sf.acegisecurity.util.FilterToBeanProxy}.
+ *
+ * Acegi Security filters (namely {@link + * net.sf.acegisecurity.ui.AbstractProcessingFilter} and {@link + * net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter} will call + * the methods provided by an implementation of this interface. + *
+ * + *+ * Implementations may implement any type of remember-me capability they wish. + * Rolling cookies (as per http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice) + * can be used, as can simple implementations that don't require a persistent + * store. Implementations also determine the validity period of a remember-me + * cookie. This interface has been designed to accommodate any of these + * remember-me models. + *
+ * + *+ * This interface does not define how remember-me services should offer a + * "cancel all remember-me tokens" type capability, as this will be + * implementation specific and requires no hooks into Acegi Security. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface RememberMeServices { + //~ Methods ================================================================ + + /** + * This method will be called whenever theContextHolder does
+ * not contain an Authentication and the Acegi Security
+ * system wishes to provide an implementation with an opportunity to
+ * authenticate the request using remember-me capabilities. Acegi Security
+ * makes no attempt whatsoever to determine whether the browser has
+ * requested remember-me services or presented a vaild cookie. Such
+ * determinations are left to the implementation. If a browser has
+ * presented an unauthorised cookie for whatever reason, it should be
+ * silently ignored and invalidated using the
+ * HttpServletResponse object.
+ *
+ *
+ * The returned Authentication must be acceptable to {@link
+ * net.sf.acegisecurity.AuthenticationManager} or {@link
+ * net.sf.acegisecurity.providers.AuthenticationProvider} defined by the
+ * web application. It is recommended {@link
+ * net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken}
+ * be used in most cases, as it has a corresponding authentication
+ * provider.
+ *
null if the
+ * request should not be authenticated
+ */
+ public Authentication autoLogin(HttpServletRequest request,
+ HttpServletResponse response);
+
+ /**
+ * Called whenever an interactive authentication attempt was made, but the
+ * credentials supplied by the user were missing or otherwise invalid.
+ * Implementations should invalidate any and all remember-me tokens
+ * indicated in the HttpServletRequest.
+ *
+ * @param request that contained an invalid authentication request
+ * @param response to change, cancel or modify the remember-me token
+ */
+ public void loginFail(HttpServletRequest request,
+ HttpServletResponse response);
+
+ /**
+ * Called whenever an interactive authentication attempt is successful. An
+ * implementation may automatically set a remember-me token in the
+ * HttpServletResponse, although this is not recommended.
+ * Instead, implementations should typically look for a request parameter
+ * that indicates the browser has presented an explicit request for
+ * authentication to be remembered, such as the presence of a HTTP POST
+ * parameter.
+ *
+ * @param request that contained the valid authentication request
+ * @param response to change, cancel or modify the remember-me token
+ * @param successfulAuthentication representing the successfully
+ * authenticated principal
+ */
+ public void loginSuccess(HttpServletRequest request,
+ HttpServletResponse response, Authentication successfulAuthentication);
+}
diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java
new file mode 100644
index 0000000000..04ef52f45f
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java
@@ -0,0 +1,352 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.ui.rememberme;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.providers.dao.AuthenticationDao;
+import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
+import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import org.springframework.web.bind.RequestUtils;
+
+import java.util.Date;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Identifies previously remembered users by a Base-64 encoded cookie.
+ *
+ * + * This implementation does not rely on an external database, so is attractive + * for simple applications. The cookie will be valid for a specific period + * from the date of the last {@link #loginSuccess(HttpServletRequest, + * HttpServletResponse, Authentication)}. As per the interface contract, this + * method will only be called when the principal completes a successful + * interactive authentication. As such the time period commences from the last + * authentication attempt where they furnished credentials - not the time + * period they last logged in via remember-me. The implementation will only + * send a remember-me token if the parameter defined by {@link + * #setParameter(String)} is present. + *
+ * + *
+ * An {@link net.sf.acegisecurity.providers.dao.AuthenticationDao} is required
+ * by this implementation, so that it can construct a valid
+ * Authentication from the returned {@link
+ * net.sf.acegisecurity.UserDetails}. This is also necessary so that the
+ * user's password is available and can be checked as part of the encoded
+ * cookie.
+ *
+ * The cookie encoded by this implementation adopts the following form: + *
+ * + *
+ * username + ":" + expiryTime + ":" + Md5Hex(username + ":" +
+ * expiryTime + ":" + password + ":" + key) .
+ *
+ * As such, if the user changes their password any remember-me token will be + * invalidated. Equally, the system administrator may invalidate every + * remember-me token on issue by changing the key. This provides some + * reasonable approaches to recovering from a remember-me token being left on + * a public machine (eg kiosk system, Internet cafe etc). Most importantly, at + * no time is the user's password ever sent to the user agent, providing an + * important security safeguard. Unfortunately the username is necessary in + * this implementation (as we do not want to rely on a database for + * remember-me services) and as such high security applications should be + * aware of this occasionally undesired disclosure of a valid username. + *
+ * + *+ * This is a basic remember-me implementation which is suitable for many + * applications. However, we recommend a database-based implementation if you + * require a more secure remember-me approach. + *
+ * + *+ * By default the tokens will be valid for 14 days from the last successful + * authentication attempt. This can be changed using {@link + * #setTokenValiditySeconds(int)}. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class TokenBasedRememberMeServices implements RememberMeServices, + InitializingBean { + //~ Static fields/initializers ============================================= + + public static final String ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = "ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE"; + public static final String DEFAULT_PARAMETER = "_acegi_security_remember_me"; + protected static final Log logger = LogFactory.getLog(TokenBasedRememberMeServices.class); + + //~ Instance fields ======================================================== + + private AuthenticationDao authenticationDao; + private String key; + private String parameter = DEFAULT_PARAMETER; + private int tokenValiditySeconds = 1209600; // 14 days + + //~ Methods ================================================================ + + public void setAuthenticationDao(AuthenticationDao authenticationDao) { + this.authenticationDao = authenticationDao; + } + + public AuthenticationDao getAuthenticationDao() { + return authenticationDao; + } + + public void setKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public void setParameter(String parameter) { + this.parameter = parameter; + } + + public String getParameter() { + return parameter; + } + + public void setTokenValiditySeconds(int tokenValiditySeconds) { + this.tokenValiditySeconds = tokenValiditySeconds; + } + + public int getTokenValiditySeconds() { + return tokenValiditySeconds; + } + + public void afterPropertiesSet() throws Exception { + Assert.hasLength(key); + Assert.hasLength(parameter); + Assert.notNull(authenticationDao); + } + + public Authentication autoLogin(HttpServletRequest request, + HttpServletResponse response) { + Cookie[] cookies = request.getCookies(); + + if ((cookies == null) || (cookies.length == 0)) { + return null; + } + + for (int i = 0; i < cookies.length; i++) { + if (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY.equals( + cookies[i].getName())) { + String cookieValue = cookies[i].getValue(); + + if (Base64.isArrayByteBase64(cookieValue.getBytes())) { + if (logger.isDebugEnabled()) { + logger.debug("Remember-me cookie detected"); + } + + // Decode token from Base64 + // format of token is: + // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key) + String cookieAsPlainText = new String(Base64.decodeBase64( + cookieValue.getBytes())); + String[] cookieTokens = StringUtils + .delimitedListToStringArray(cookieAsPlainText, ":"); + + if (cookieTokens.length == 3) { + long tokenExpiryTime; + + try { + tokenExpiryTime = new Long(cookieTokens[1]) + .longValue(); + } catch (NumberFormatException nfe) { + cancelCookie(request, response, + "Cookie token[1] did not contain a valid number (contained '" + + cookieTokens[1] + "')"); + + return null; + } + + // Check it has not expired + if (tokenExpiryTime < System.currentTimeMillis()) { + cancelCookie(request, response, + "Cookie token[1] has expired (expired on '" + + new Date(tokenExpiryTime) + + "'; current time is '" + new Date() + "')"); + + return null; + } + + // Check the user exists + // Defer lookup until after expiry time checked, to possibly avoid expensive lookup + UserDetails userDetails; + + try { + userDetails = this.authenticationDao + .loadUserByUsername(cookieTokens[0]); + } catch (UsernameNotFoundException notFound) { + cancelCookie(request, response, + "Cookie token[0] contained username '" + + cookieTokens[0] + "' but was not found"); + + return null; + } + + // Check signature of token matches remaining details + // Must do this after user lookup, as we need the DAO-derived password + // If efficiency was a major issue, just add in a UserCache implementation, + // but recall this method is usually only called one per HttpSession + // (as if the token is valid, it will cause ContextHolder population, whilst + // if invalid, will cause the cookie to be cancelled) + String expectedTokenSignature = DigestUtils.md5Hex(userDetails + .getUsername() + ":" + tokenExpiryTime + ":" + + userDetails.getPassword() + ":" + this.key); + + if (!expectedTokenSignature.equals(cookieTokens[2])) { + cancelCookie(request, response, + "Cookie token[2] contained signature '" + + cookieTokens[2] + "' but expected '" + + expectedTokenSignature + "'"); + + return null; + } + + // By this stage we have a valid token + if (logger.isDebugEnabled()) { + logger.debug("Remember-me cookie accepted"); + } + + return new RememberMeAuthenticationToken(this.key, + userDetails, userDetails.getAuthorities()); + } else { + cancelCookie(request, response, + "Cookie token did not contain 3 tokens; decoded value was '" + + cookieAsPlainText + "'"); + + return null; + } + } else { + cancelCookie(request, response, + "Cookie token was not Base64 encoded; value was '" + + cookieValue + "'"); + + return null; + } + } + } + + return null; + } + + public void loginFail(HttpServletRequest request, + HttpServletResponse response) { + cancelCookie(request, response, + "Interactive authentication attempt was unsuccessful"); + } + + public void loginSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication successfulAuthentication) { + // Exit if the principal hasn't asked to be remembered + if (!RequestUtils.getBooleanParameter(request, parameter, false)) { + if (logger.isDebugEnabled()) { + logger.debug( + "Did not send remember-me cookie (principal did not set parameter '" + + this.parameter + "')"); + } + + return; + } + + // Determine username and password, ensuring empty strings + Assert.notNull(successfulAuthentication.getPrincipal()); + Assert.notNull(successfulAuthentication.getCredentials()); + + String username; + String password; + + if (successfulAuthentication.getPrincipal() instanceof UserDetails) { + username = ((UserDetails) successfulAuthentication.getPrincipal()) + .getUsername(); + password = ((UserDetails) successfulAuthentication.getPrincipal()) + .getPassword(); + } else { + username = successfulAuthentication.getPrincipal().toString(); + password = successfulAuthentication.getCredentials().toString(); + } + + Assert.hasLength(username); + Assert.hasLength(password); + + long expiryTime = System.currentTimeMillis() + + (tokenValiditySeconds * 1000); + + // construct token to put in cookie; format is: + // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key) + String signatureValue = new String(DigestUtils.md5Hex(username + ":" + + expiryTime + ":" + password + ":" + key)); + String tokenValue = username + ":" + expiryTime + ":" + signatureValue; + String tokenValueBase64 = new String(Base64.encodeBase64( + tokenValue.getBytes())); + response.addCookie(makeValidCookie(expiryTime, tokenValueBase64)); + + if (logger.isDebugEnabled()) { + logger.debug("Added remember-me cookie for user '" + username + + "', expiry: '" + new Date(expiryTime) + "'"); + } + } + + protected Cookie makeCancelCookie() { + Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + null); + cookie.setMaxAge(0); + + return cookie; + } + + protected Cookie makeValidCookie(long expiryTime, String tokenValueBase64) { + Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + tokenValueBase64); + cookie.setMaxAge(60 * 60 * 24 * 365 * 5); // 5 years + + return cookie; + } + + private void cancelCookie(HttpServletRequest request, + HttpServletResponse response, String reasonForLog) { + if ((reasonForLog != null) && logger.isDebugEnabled()) { + logger.debug("Cancelling cookie for reason: " + reasonForLog); + } + + response.addCookie(makeCancelCookie()); + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/package.html b/core/src/main/java/org/acegisecurity/ui/rememberme/package.html new file mode 100644 index 0000000000..b6d69a70ba --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/package.html @@ -0,0 +1,5 @@ + + +Support for remembering a user between different web sessions. + + diff --git a/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java b/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java index c1fc48c938..659859dd69 100644 --- a/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java +++ b/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java @@ -19,6 +19,7 @@ import junit.framework.TestCase; import net.sf.acegisecurity.providers.TestingAuthenticationToken; import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken; /** @@ -54,6 +55,16 @@ public class AuthenticationTrustResolverImplTests extends TestCase { new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")}))); } + public void testCorrectOperationIsRememberMe() { + AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl(); + assertTrue(trustResolver.isRememberMe( + new RememberMeAuthenticationToken("ignored", "ignored", + new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")}))); + assertFalse(trustResolver.isAnonymous( + new TestingAuthenticationToken("ignored", "ignored", + new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")}))); + } + public void testGettersSetters() { AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl(); @@ -62,6 +73,9 @@ public class AuthenticationTrustResolverImplTests extends TestCase { trustResolver.setAnonymousClass(String.class); assertEquals(String.class, trustResolver.getAnonymousClass()); - assertNull(trustResolver.getRememberMeClass()); + assertEquals(RememberMeAuthenticationToken.class, + trustResolver.getRememberMeClass()); + trustResolver.setRememberMeClass(String.class); + assertEquals(String.class, trustResolver.getRememberMeClass()); } } diff --git a/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java b/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java index 145d98e287..47b8d2945c 100644 --- a/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java +++ b/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java @@ -54,6 +54,7 @@ public class MockHttpServletRequest implements HttpServletRequest { private Map attribMap = new HashMap(); private Map headersMap = new HashMap(); private Map paramMap = new HashMap(); + private Map cookiesMap = new HashMap(); private Principal principal; private String contextPath = ""; private String pathInfo; // null for no extra path @@ -75,6 +76,15 @@ public class MockHttpServletRequest implements HttpServletRequest { this.queryString = queryString; } + public MockHttpServletRequest(Map headers, HttpSession session, String queryString, Cookie[] cookies) { + this.queryString = queryString; + this.headersMap = headers; + this.session = session; + for (int i = 0; i < cookies.length; i++) { + cookiesMap.put(cookies[i].getName(), cookies[i]); + } + } + public MockHttpServletRequest(Map headers, Principal principal, HttpSession session) { this.headersMap = headers; @@ -129,7 +139,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } public Cookie[] getCookies() { - throw new UnsupportedOperationException("mock method not implemented"); + return (Cookie[]) cookiesMap.values().toArray(new Cookie[] {}); } public long getDateHeader(String arg0) { diff --git a/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java b/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java index 5393c85fe5..d2fe755514 100644 --- a/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java +++ b/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java @@ -1,4 +1,4 @@ -/* Copyright 2004 Acegi Technology Pty Limited +/* Copyright 2004, 2005 Acegi Technology Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletResponse; public class MockHttpServletResponse implements HttpServletResponse { //~ Instance fields ======================================================== + private Map cookiesMap = new HashMap(); private Map headersMap = new HashMap(); private String errorMessage; private String redirect; @@ -72,6 +73,10 @@ public class MockHttpServletResponse implements HttpServletResponse { throw new UnsupportedOperationException("mock method not implemented"); } + public Cookie getCookieByName(String name) { + return (Cookie) cookiesMap.get(name); + } + public void setDateHeader(String arg0, long arg1) { throw new UnsupportedOperationException("mock method not implemented"); } @@ -131,7 +136,7 @@ public class MockHttpServletResponse implements HttpServletResponse { } public void addCookie(Cookie arg0) { - throw new UnsupportedOperationException("mock method not implemented"); + cookiesMap.put(arg0.getName(), arg0); } public void addDateHeader(String arg0, long arg1) { diff --git a/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProviderTests.java new file mode 100644 index 0000000000..586ca384d8 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProviderTests.java @@ -0,0 +1,122 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.providers.rememberme; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.BadCredentialsException; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.providers.TestingAuthenticationToken; + + +/** + * Tests {@link RememberMeAuthenticationProvider}. + * + * @author Ben Alex + * @version $Id$ + */ +public class RememberMeAuthenticationProviderTests extends TestCase { + //~ Constructors =========================================================== + + public RememberMeAuthenticationProviderTests() { + super(); + } + + public RememberMeAuthenticationProviderTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(RememberMeAuthenticationProviderTests.class); + } + + public void testDetectsAnInvalidKey() throws Exception { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + aap.setKey("qwerty"); + + RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("WRONG_KEY", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + try { + Authentication result = aap.authenticate(token); + fail("Should have thrown BadCredentialsException"); + } catch (BadCredentialsException expected) { + assertEquals("The presented RememberMeAuthenticationToken does not contain the expected key", + expected.getMessage()); + } + } + + public void testDetectsMissingKey() throws Exception { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + + try { + aap.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGettersSetters() throws Exception { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + aap.setKey("qwerty"); + aap.afterPropertiesSet(); + assertEquals("qwerty", aap.getKey()); + } + + public void testIgnoresClassesItDoesNotSupport() throws Exception { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + aap.setKey("qwerty"); + + TestingAuthenticationToken token = new TestingAuthenticationToken("user", + "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")}); + assertFalse(aap.supports(TestingAuthenticationToken.class)); + + // Try it anyway + assertNull(aap.authenticate(token)); + } + + public void testNormalOperation() throws Exception { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + aap.setKey("qwerty"); + + RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("qwerty", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + Authentication result = aap.authenticate(token); + + assertEquals(result, token); + } + + public void testSupports() { + RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider(); + assertTrue(aap.supports(RememberMeAuthenticationToken.class)); + assertFalse(aap.supports(TestingAuthenticationToken.class)); + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationTokenTests.java b/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationTokenTests.java new file mode 100644 index 0000000000..1544d9ae3e --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationTokenTests.java @@ -0,0 +1,190 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.providers.rememberme; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; + +import java.util.List; +import java.util.Vector; + + +/** + * Tests {@link RememberMeAuthenticationToken}. + * + * @author Ben Alex + * @version $Id$ + */ +public class RememberMeAuthenticationTokenTests extends TestCase { + //~ Constructors =========================================================== + + public RememberMeAuthenticationTokenTests() { + super(); + } + + public RememberMeAuthenticationTokenTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(RememberMeAuthenticationTokenTests.class); + } + + public void testConstructorRejectsNulls() { + try { + new RememberMeAuthenticationToken(null, "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new RememberMeAuthenticationToken("key", null, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new RememberMeAuthenticationToken("key", "Test", null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new RememberMeAuthenticationToken("key", "Test", + new GrantedAuthority[] {null}); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new RememberMeAuthenticationToken("key", "Test", + new GrantedAuthority[] {}); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testEqualsWhenEqual() { + List proxyList1 = new Vector(); + proxyList1.add("https://localhost/newPortal/j_acegi_cas_security_check"); + + RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + assertEquals(token1, token2); + } + + public void testGetters() { + RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + assertEquals("key".hashCode(), token.getKeyHash()); + assertEquals("Test", token.getPrincipal()); + assertEquals("", token.getCredentials()); + assertEquals("ROLE_ONE", token.getAuthorities()[0].getAuthority()); + assertEquals("ROLE_TWO", token.getAuthorities()[1].getAuthority()); + assertTrue(token.isAuthenticated()); + } + + public void testNoArgConstructor() { + try { + new RememberMeAuthenticationToken(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testNotEqualsDueToAbstractParentEqualsCheck() { + RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("key", + "DIFFERENT_PRINCIPAL", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + assertFalse(token1.equals(token2)); + } + + public void testNotEqualsDueToDifferentAuthenticationClass() { + RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test", + "Password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + token2.setAuthenticated(true); + + assertFalse(token1.equals(token2)); + } + + public void testNotEqualsDueToKey() { + RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("DIFFERENT_KEY", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + + assertFalse(token1.equals(token2)); + } + + public void testSetAuthenticatedIgnored() { + RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key", + "Test", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl( + "ROLE_TWO")}); + assertTrue(token.isAuthenticated()); + token.setAuthenticated(false); // ignored + assertTrue(token.isAuthenticated()); + } +} diff --git a/core/src/test/java/org/acegisecurity/ui/AbstractProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/AbstractProcessingFilterTests.java index 7d1e055621..d34acf9224 100644 --- a/core/src/test/java/org/acegisecurity/ui/AbstractProcessingFilterTests.java +++ b/core/src/test/java/org/acegisecurity/ui/AbstractProcessingFilterTests.java @@ -30,6 +30,7 @@ import net.sf.acegisecurity.context.ContextHolder; import net.sf.acegisecurity.context.security.SecureContextImpl; import net.sf.acegisecurity.context.security.SecureContextUtils; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices; import java.io.IOException; @@ -150,6 +151,11 @@ public class AbstractProcessingFilterTests extends TestCase { public void testGettersSetters() { AbstractProcessingFilter filter = new MockAbstractProcessingFilter(); + assertNotNull(filter.getRememberMeServices()); + filter.setRememberMeServices(new TokenBasedRememberMeServices()); + assertEquals(TokenBasedRememberMeServices.class, + filter.getRememberMeServices().getClass()); + filter.setAuthenticationFailureUrl("/x"); assertEquals("/x", filter.getAuthenticationFailureUrl()); diff --git a/core/src/test/java/org/acegisecurity/ui/rememberme/NullRememberMeServicesTests.java b/core/src/test/java/org/acegisecurity/ui/rememberme/NullRememberMeServicesTests.java new file mode 100644 index 0000000000..2dc9694306 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ui/rememberme/NullRememberMeServicesTests.java @@ -0,0 +1,51 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.ui.rememberme; + +import junit.framework.TestCase; + + +/** + * Tests {@link net.sf.acegisecurity.ui.rememberme.NullRememberMeServices}. + * + * @author Ben Alex + * @version $Id$ + */ +public class NullRememberMeServicesTests extends TestCase { + //~ Constructors =========================================================== + + public NullRememberMeServicesTests() { + super(); + } + + public NullRememberMeServicesTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public static void main(String[] args) { + junit.textui.TestRunner.run(NullRememberMeServicesTests.class); + } + + public void testAlwaysReturnsNull() { + NullRememberMeServices services = new NullRememberMeServices(); + assertNull(services.autoLogin(null,null)); + services.loginFail(null,null); + services.loginSuccess(null,null,null); + assertTrue(true); + } +} diff --git a/core/src/test/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilterTests.java new file mode 100644 index 0000000000..0b8337b30f --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ui/rememberme/RememberMeProcessingFilterTests.java @@ -0,0 +1,223 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.ui.rememberme; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.MockFilterConfig; +import net.sf.acegisecurity.MockHttpServletRequest; +import net.sf.acegisecurity.MockHttpServletResponse; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.security.SecureContext; +import net.sf.acegisecurity.context.security.SecureContextImpl; +import net.sf.acegisecurity.context.security.SecureContextUtils; +import net.sf.acegisecurity.providers.TestingAuthenticationToken; + + +/** + * Tests {@link RememberMeProcessingFilter}. + * + * @author Ben Alex + * @version $Id$ + */ +public class RememberMeProcessingFilterTests extends TestCase { + //~ Constructors =========================================================== + + public RememberMeProcessingFilterTests() { + super(); + } + + public RememberMeProcessingFilterTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public static void main(String[] args) { + junit.textui.TestRunner.run(RememberMeProcessingFilterTests.class); + } + + public void testDoFilterWithNonHttpServletRequestDetected() + throws Exception { + RememberMeProcessingFilter filter = new RememberMeProcessingFilter(); + + try { + filter.doFilter(null, new MockHttpServletResponse(), + new MockFilterChain()); + fail("Should have thrown ServletException"); + } catch (ServletException expected) { + assertEquals("Can only process HttpServletRequest", + expected.getMessage()); + } +} + + public void testDoFilterWithNonHttpServletResponseDetected() + throws Exception { + RememberMeProcessingFilter filter = new RememberMeProcessingFilter(); + + try { + filter.doFilter(new MockHttpServletRequest("dc"), null, + new MockFilterChain()); + fail("Should have thrown ServletException"); + } catch (ServletException expected) { + assertEquals("Can only process HttpServletResponse", + expected.getMessage()); + } +} + + public void testDetectsRememberMeServicesProperty() throws Exception { + RememberMeProcessingFilter filter = new RememberMeProcessingFilter(); + // check default is NullRememberMeServices + assertEquals(NullRememberMeServices.class, filter.getRememberMeServices().getClass()); + + // check getter/setter + filter.setRememberMeServices(new TokenBasedRememberMeServices()); + assertEquals(TokenBasedRememberMeServices.class, filter.getRememberMeServices().getClass()); + + // check detects if made null + filter.setRememberMeServices(null); + try { + filter.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testOperationWhenAuthenticationExistsInContextHolder() + throws Exception { + // Put an Authentication object into the ContextHolder + SecureContext sc = SecureContextUtils.getSecureContext(); + Authentication originalAuth = new TestingAuthenticationToken("user", + "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")}); + sc.setAuthentication(originalAuth); + ContextHolder.setContext(sc); + + // Setup our filter correctly + Authentication remembered = new TestingAuthenticationToken("remembered", + "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_REMEMBERED")}); + RememberMeProcessingFilter filter = new RememberMeProcessingFilter(); + filter.setRememberMeServices(new MockRememberMeServices(remembered)); + filter.afterPropertiesSet(); + + // Test + executeFilterInContainerSimulator(new MockFilterConfig(), filter, + new MockHttpServletRequest("x"), new MockHttpServletResponse(), + new MockFilterChain(true)); + + // Ensure filter didn't change our original object + assertEquals(originalAuth, + SecureContextUtils.getSecureContext().getAuthentication()); + } + + public void testOperationWhenNoAuthenticationInContextHolder() + throws Exception { + Authentication remembered = new TestingAuthenticationToken("remembered", + "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_REMEMBERED")}); + RememberMeProcessingFilter filter = new RememberMeProcessingFilter(); + filter.setRememberMeServices(new MockRememberMeServices(remembered)); + filter.afterPropertiesSet(); + + executeFilterInContainerSimulator(new MockFilterConfig(), filter, + new MockHttpServletRequest("x"), new MockHttpServletResponse(), + new MockFilterChain(true)); + + Authentication auth = SecureContextUtils.getSecureContext() + .getAuthentication(); + + // Ensure filter setup with our remembered authentication object + assertEquals(remembered, + SecureContextUtils.getSecureContext().getAuthentication()); + } + + protected void setUp() throws Exception { + super.setUp(); + ContextHolder.setContext(new SecureContextImpl()); + } + + protected void tearDown() throws Exception { + super.tearDown(); + ContextHolder.setContext(null); + } + + private void executeFilterInContainerSimulator(FilterConfig filterConfig, + Filter filter, ServletRequest request, ServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + filter.init(filterConfig); + filter.doFilter(request, response, filterChain); + filter.destroy(); + } + + //~ Inner Classes ========================================================== + + private class MockFilterChain implements FilterChain { + private boolean expectToProceed; + + public MockFilterChain(boolean expectToProceed) { + this.expectToProceed = expectToProceed; + } + + private MockFilterChain() { + super(); + } + + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + if (expectToProceed) { + assertTrue(true); + } else { + fail("Did not expect filter chain to proceed"); + } + } + } + + private class MockRememberMeServices implements RememberMeServices + { + private Authentication authToReturn; + + public MockRememberMeServices(Authentication authToReturn) { + this.authToReturn = authToReturn; + } + + public Authentication autoLogin(HttpServletRequest request, + HttpServletResponse response) { + return authToReturn; + } + public void loginFail(HttpServletRequest request, + HttpServletResponse response) { + } + public void loginSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication successfulAuthentication) { + } +} +} diff --git a/core/src/test/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServicesTests.java b/core/src/test/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServicesTests.java new file mode 100644 index 0000000000..d9c00be589 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServicesTests.java @@ -0,0 +1,412 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed 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 net.sf.acegisecurity.ui.rememberme; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.MockHttpServletRequest; +import net.sf.acegisecurity.MockHttpServletResponse; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.TestingAuthenticationToken; +import net.sf.acegisecurity.providers.dao.AuthenticationDao; +import net.sf.acegisecurity.providers.dao.User; +import net.sf.acegisecurity.providers.dao.UsernameNotFoundException; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; + +import org.springframework.dao.DataAccessException; + +import org.springframework.util.StringUtils; + +import java.util.Date; + +import javax.servlet.http.Cookie; + + +/** + * Tests {@link + * net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices}. + * + * @author Ben Alex + * @version $Id$ + */ +public class TokenBasedRememberMeServicesTests extends TestCase { + //~ Constructors =========================================================== + + public TokenBasedRememberMeServicesTests() { + super(); + } + + public TokenBasedRememberMeServicesTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public static void main(String[] args) { + junit.textui.TestRunner.run(TokenBasedRememberMeServicesTests.class); + } + + public void testAutoLoginIfDoesNotPresentAnyCookies() + throws Exception { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(null, true)); + services.afterPropertiesSet(); + + MockHttpServletRequest request = new MockHttpServletRequest("dc"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNull(returnedCookie); // shouldn't try to invalidate our cookie + } + + public void testAutoLoginIfDoesNotPresentRequiredCookie() + throws Exception { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(null, true)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie("unrelated_cookie", "foobar"); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNull(returnedCookie); // shouldn't try to invalidate our cookie + } + + public void testAutoLoginIfExpired() throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + generateCorrectCookieContentForToken(System.currentTimeMillis() + - 1000000, "someone", "password", "key")); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginIfMissingThreeTokensInCookieValue() + throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + new String(Base64.encodeBase64("x".getBytes()))); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginIfNotBase64Encoded() throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + "NOT_BASE_64_ENCODED"); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginIfSignatureBlocksDoesNotMatchExpectedValue() + throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + generateCorrectCookieContentForToken(System.currentTimeMillis() + + 1000000, "someone", "password", "WRONG_KEY")); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginIfTokenDoesNotContainANumberInCookieValue() + throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + new String(Base64.encodeBase64( + "username:NOT_A_NUMBER:signature".getBytes()))); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginIfUserNotFound() throws Exception { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(null, true)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + generateCorrectCookieContentForToken(System.currentTimeMillis() + + 1000000, "someone", "password", "key")); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNull(result); + + Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(returnedCookie); + assertEquals(0, returnedCookie.getMaxAge()); + } + + public void testAutoLoginWithValidToken() throws Exception { + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setKey("key"); + services.setAuthenticationDao(new MockAuthenticationDao(user, false)); + services.afterPropertiesSet(); + + Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + generateCorrectCookieContentForToken(System.currentTimeMillis() + + 1000000, "someone", "password", "key")); + MockHttpServletRequest request = new MockHttpServletRequest(null, null, + "null", new Cookie[] {cookie}); + MockHttpServletResponse response = new MockHttpServletResponse(); + + Authentication result = services.autoLogin(request, response); + + assertNotNull(result); + + UserDetails resultingUserDetails = (UserDetails) result.getPrincipal(); + + assertEquals(user, resultingUserDetails); + } + + public void testGettersSetters() { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + services.setAuthenticationDao(new MockAuthenticationDao(null, false)); + assertTrue(services.getAuthenticationDao() != null); + + services.setKey("d"); + assertEquals("d", services.getKey()); + + assertEquals(TokenBasedRememberMeServices.DEFAULT_PARAMETER, + services.getParameter()); + services.setParameter("some_param"); + assertEquals("some_param", services.getParameter()); + + services.setTokenValiditySeconds(12); + assertEquals(12, services.getTokenValiditySeconds()); + } + + public void testLoginFail() { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + MockHttpServletRequest request = new MockHttpServletRequest("fv"); + MockHttpServletResponse response = new MockHttpServletResponse(); + services.loginFail(request, response); + + Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(cookie); + assertEquals(0, cookie.getMaxAge()); + } + + public void testLoginSuccessIgnoredIfParameterNotSetOrFalse() { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + MockHttpServletRequest request = new MockHttpServletRequest("d"); + request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER, + "false"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + services.loginSuccess(request, response, + new TestingAuthenticationToken("someone", "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); + + Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNull(cookie); + } + + public void testLoginSuccessNormalWithNonUserDetailsBasedPrincipal() { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + MockHttpServletRequest request = new MockHttpServletRequest("d"); + request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER, + "true"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + services.loginSuccess(request, response, + new TestingAuthenticationToken("someone", "password", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); + + Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(cookie); + assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years + assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes())); + assertTrue(new Date().before( + new Date(determineExpiryTimeFromBased64EncodedToken( + cookie.getValue())))); + } + + public void testLoginSuccessNormalWithUserDetailsBasedPrincipal() { + TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); + MockHttpServletRequest request = new MockHttpServletRequest("d"); + request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER, + "true"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + UserDetails user = new User("someone", "password", true, true, true, + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); + services.loginSuccess(request, response, + new TestingAuthenticationToken(user, "ignored", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); + + Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + assertNotNull(cookie); + assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years + assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes())); + assertTrue(new Date().before( + new Date(determineExpiryTimeFromBased64EncodedToken( + cookie.getValue())))); + } + + private long determineExpiryTimeFromBased64EncodedToken(String validToken) { + String cookieAsPlainText = new String(Base64.decodeBase64( + validToken.getBytes())); + String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, + ":"); + + if (cookieTokens.length == 3) { + try { + return new Long(cookieTokens[1]).longValue(); + } catch (NumberFormatException nfe) {} + } + + return -1; + } + + private String generateCorrectCookieContentForToken(long expiryTime, + String username, String password, String key) { + // format is: + // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key) + String signatureValue = new String(DigestUtils.md5Hex(username + ":" + + expiryTime + ":" + password + ":" + key)); + String tokenValue = username + ":" + expiryTime + ":" + signatureValue; + String tokenValueBase64 = new String(Base64.encodeBase64( + tokenValue.getBytes())); + + return tokenValueBase64; + } + + //~ Inner Classes ========================================================== + + private class MockAuthenticationDao implements AuthenticationDao { + private UserDetails toReturn; + private boolean throwException; + + public MockAuthenticationDao(UserDetails toReturn, + boolean throwException) { + this.toReturn = toReturn; + this.throwException = throwException; + } + + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException, DataAccessException { + if (throwException) { + throw new UsernameNotFoundException("as requested by mock"); + } + + return toReturn; + } + } +} diff --git a/doc/docbook/acegi.xml b/doc/docbook/acegi.xml index 9d214c11d3..abe8215cd2 100644 --- a/doc/docbook/acegi.xml +++ b/doc/docbook/acegi.xml @@ -2645,7 +2645,7 @@ key: A private key to prevent modification of the nonce token /index.jsp=ROLE_ANONYMOUS,ROLE_USER /hello.htm=ROLE_ANONYMOUS,ROLE_USER /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER - /acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER + /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER /**=ROLE_USER </value> </property> @@ -2669,6 +2669,110 @@ key: A private key to prevent modification of the nonce token authentication mechanism. +