package org.constellation.token;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.constellation.configuration.AppProperty;
import org.constellation.configuration.Application;
public class TokenUtils {
private static final String TOKEN_SEPARATOR = "_";
private final static Logger LOGGER = LoggerFactory.getLogger(TokenUtils.class);
public static final long tokenHalfLife = initTokenHalfLife();
private static final Pattern TOKEN_PATTERN = Pattern.compile("(\\w+)_(\\d+)_(\\w+)_(\\d+)");
public static String createToken(String username, String secret) {
/* Expires in one hour */
long expires = System.currentTimeMillis() + tokenHalfLife * 2;
StringBuilder tokenBuilder = new StringBuilder();
tokenBuilder.append(username);
tokenBuilder.append(TOKEN_SEPARATOR);
tokenBuilder.append(expires);
tokenBuilder.append(TOKEN_SEPARATOR);
tokenBuilder.append(TokenUtils.computeSignature(username, expires, secret));
tokenBuilder.append(TOKEN_SEPARATOR);
tokenBuilder.append(tokenHalfLife);
return tokenBuilder.toString();
}
public static long getTokenLife() {
String tokenLifeInMinutesAsString = Application.getProperty(AppProperty.CSTL_TOKEN_LIFE, "60");
long tokenLifeInMinutes;
try {
tokenLifeInMinutes = Long.parseLong(tokenLifeInMinutesAsString);
} catch (NumberFormatException e) {
LOGGER.warn(e.getMessage(), e);
tokenLifeInMinutes = 60;
}
return tokenLifeInMinutes;
}
private static long initTokenHalfLife() {
long tokenLifeInMinutes = getTokenLife();
LOGGER.info("Token life set to " + tokenLifeInMinutes + " minutes");
return 500L * 60 * tokenLifeInMinutes;
}
public static boolean isExpired(String token) {
Matcher matcher = TOKEN_PATTERN.matcher(token);
if (matcher.matches()) {
String expireString = matcher.group(2);
long expire = Long.parseLong(expireString);
return expire < System.currentTimeMillis();
}
return true;
}
public static String computeSignature(String username, long expires, String secret) {
StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(username);
signatureBuilder.append(TOKEN_SEPARATOR);
signatureBuilder.append(expires);
signatureBuilder.append(TOKEN_SEPARATOR);
// signatureBuilder.append(userDetails.getPassword());
signatureBuilder.append(TOKEN_SEPARATOR);
signatureBuilder.append(secret);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No MD5 algorithm available!");
}
return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
}
public static String getUserNameFromToken(String access_token) {
if (null == access_token) {
return null;
}
String[] parts = access_token.split(TOKEN_SEPARATOR);
return parts[0];
}
public static boolean validateToken(String access_token, String secret) {
String[] parts = access_token.split(TOKEN_SEPARATOR);
if (parts.length < 4) {
LOGGER.warn("Token malformed: " + access_token);
return false;
}
String username = parts[0];
long expires = Long.parseLong(parts[1]);
String signature = parts[2];
if (expires < System.currentTimeMillis()) {
LOGGER.info("Token expired: " + access_token);
return false;
}
if (signature.equals(TokenUtils.computeSignature(username, expires, secret))) {
return true;
}
LOGGER.info("Token missmatch: " + access_token);
return false;
}
public static boolean shouldBeExtended(String access_token) {
String[] parts = access_token.split(TOKEN_SEPARATOR);
long expires = Long.parseLong(parts[1]);
return expires < System.currentTimeMillis() + tokenHalfLife;
}
/**
* Extract access_token from header, parameter or cookies.
* @param request
* @return
*/
public static String extractAccessToken(HttpServletRequest request) {
return extract(request, "access_token");
}
/**
* Extract value of header, parameter or cookie for a given name.
* @param request
* @param name
* @return
*/
public static String extract(HttpServletRequest request, String name) {
String value = headers(request, name);
if (value != null) {
LOGGER.debug("Extract token from header: " + value );
return value;
}
value = queryString(request, name);
if (value != null) {
LOGGER.debug("Extract token from query string: " + value );
return value;
}
return cookie(request, name);
}
private static String queryString(HttpServletRequest httpRequest, String name) {
/*
* If access_token not found get it from request query string 'name' parameter
*/
String queryString = httpRequest.getQueryString();
if (StringUtils.isNotBlank(queryString)) {
int tokenIndex = queryString.indexOf(name+"=");
if (tokenIndex != -1) {
tokenIndex += (name + "=").length();
int tokenEndIndex = queryString.indexOf('&', tokenIndex);
String access_token;
if (tokenEndIndex == -1)
access_token = queryString.substring(tokenIndex);
else
access_token = queryString.substring(tokenIndex, tokenEndIndex);
LOGGER.debug("QueryString: " + access_token + " (" + httpRequest.getRequestURI() + ")");
return access_token;
}
}
return null;
}
private static String cookie(HttpServletRequest httpRequest, String name) {
/* Extract from cookie */
Cookie[] cookies = httpRequest.getCookies();
if (cookies != null)
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
try {
String value = URLDecoder.decode(cookie.getValue(), "UTF-8");
LOGGER.debug("Cookie: " + value + " (" + httpRequest.getRequestURI() + ")");
if(StringUtils.isNotBlank(value))
return value;
} catch (UnsupportedEncodingException e) {
LOGGER.error(e.getMessage(), e);
}
}
}
LOGGER.debug("Fail to extract token ");
return null;
}
private static String headers(HttpServletRequest httpRequest, String name) {
String value = httpRequest.getHeader(name);
if (value != null) {
LOGGER.debug("Header: " + value + " (" + httpRequest.getRequestURI() + ")");
if(StringUtils.isNotBlank(value))
return value;
}
return null;
}
}