/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC
* LICENSE as published by the Free Software Foundation under
* version 3 of the License
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* LESSER GENERAL PUBLIC LICENSE v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.api.spring.security.rememberme;
import java.util.Arrays;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.security.Authentication;
import org.springframework.security.ui.rememberme.InvalidCookieException;
import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import com.abiquo.api.spring.security.AbiquoUserDetails;
import com.abiquo.api.spring.security.AbiquoUserDetailsService;
import com.abiquo.server.core.enterprise.User.AuthType;
/**
* Since Almost all code is done by Ben Alex, all credits go to him. This Class needs an
* {@link AbiquoUserDetailsService}.
*
* @author abiquo
* @version 0.1
*/
public class AbiquoTokenBasedRememberMe extends TokenBasedRememberMeServices
{
/**
* @see org.springframework.security.ui.rememberme.TokenBasedRememberMeServices#processAutoLoginCookie(java.lang.String[],
* javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public UserDetails processAutoLoginCookie(final String[] cookieTokens,
final HttpServletRequest request, final HttpServletResponse response)
{
if (cookieTokens.length != 4)
{
throw new InvalidCookieException("Cookie token did not contain " + 4
+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
}
long tokenExpiryTime;
try
{
tokenExpiryTime = new Long(cookieTokens[1]).longValue();
}
catch (NumberFormatException nfe)
{
throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '"
+ cookieTokens[1] + "')");
}
if (isTokenExpired(tokenExpiryTime))
{
throw new InvalidCookieException("Cookie token[1] has expired (expired on '"
+ new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')");
}
synchronized (this) // Concurrency might change the value of the authType before the read.
{
// Since UserDetails is an Abiquo Implementation we set the authType
try
{
AuthType authType = AuthType.valueOf(cookieTokens[3]);
getTargetObject(getUserDetailsService(), AbiquoUserDetailsService.class)
.setAuthType(authType);
}
catch (ClassCastException e)
{
throw new InvalidCookieException("UserDetailsService must be an AbiquoUserDetailsService'");
}
catch (Exception nfe)
{
throw new InvalidCookieException("Cookie token[3] did not contain a valid AuthType (contained '"
+ cookieTokens[3] + "')");
}
// Check the user exists.
// Defer lookup until after expiry time checked, to possibly avoid expensive database
// call.
UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
// 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 that this method is usually only called once per HttpSession - if the
// token is
// valid,
// it will cause SecurityContextHolder population, whilst if invalid, will cause the
// cookie
// to be cancelled.
String expectedTokenSignature =
makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
userDetails.getPassword());
if (!expectedTokenSignature.equals(cookieTokens[2]))
{
throw new InvalidCookieException("Cookie token[2] contained signature '"
+ cookieTokens[2] + "' but expected '" + expectedTokenSignature + "'");
}
return userDetails;
}
}
/**
* Return the actual class at runtime in case of Proxy.
*
* @param <T> Type.
* @param proxy object.
* @param targetClass actual class.
* @return runtime instance.
* @throws Exception T
*/
@SuppressWarnings("unchecked")
protected <T> T getTargetObject(final Object proxy, final Class<T> targetClass)
throws Exception
{
while (AopUtils.isJdkDynamicProxy(proxy))
{
return (T) ((Advised) proxy).getTargetSource().getTarget();
}
return (T) proxy; // expected to be cglib proxy then, which is simply a specialized
// class
}
/**
* @see org.springframework.security.ui.rememberme.TokenBasedRememberMeServices#onLoginSuccess(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse, org.springframework.security.Authentication)
*/
@Override
public void onLoginSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication successfulAuthentication)
{
String authType = retrieveAuthType(successfulAuthentication);
String username = retrieveUserName(successfulAuthentication);
String password = retrievePassword(successfulAuthentication);
// If unable to find a username and password, just abort as TokenBasedRememberMeServices is
// unable to construct a valid token in this case.
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)
&& AuthType.ABIQUO.name().equalsIgnoreCase(authType)
|| !StringUtils.hasLength(authType))
{
return;
}
int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
long expiryTime = System.currentTimeMillis() + 1000L * tokenLifetime;
String signatureValue = makeTokenSignature(expiryTime, username, password);
setCookie(new String[] {username, Long.toString(expiryTime), signatureValue, authType},
tokenLifetime, request, response);
if (logger.isTraceEnabled())
{
logger.trace("Added remember-me cookie for user '" + username + "', expiry: '"
+ new Date(expiryTime) + "'");
}
}
/**
* The mode of authentication.
*
* @param authentication object.
* @return String value of {@link AuthType }.
*/
protected String retrieveAuthType(final Authentication authentication)
{
if (isInstanceOfAbiquoUserDetails(authentication))
{
return ((AbiquoUserDetails) authentication.getPrincipal()).getAuthType();
}
else
{
return null;
}
}
/**
* Actual {@link AbiquoUserDetails.} or not.
*
* @param authentication login.
* @return true if is an instance of {@link AbiquoUserDetails }. False otherwise.
*/
private boolean isInstanceOfAbiquoUserDetails(final Authentication authentication)
{
return authentication.getPrincipal() instanceof AbiquoUserDetails;
}
}