/*******************************************************************************
* Copyright (c) 2008-2010 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Created by: Jordi Albornoz Mulligan ( <a href="mailto:jordi@cambridgesemantics.com">jordi@cambridgesemantics.com </a>)
*
* Contributors:
* Mort Bay Consulting Pty. Ltd. - the Jetty FormAuthenticator is the basis of the Cambridge Semantics EncryptedTokenAuthenticator.
* Cambridge Semantics Incorporated - modified to use encrypted token method to avoid using session state.
*
* This code is based on Jetty's org.eclipse.jetty.security.FormAuthenticator class
* as modified by Cambridge Semantics Incorporated to implement the encrypted
* token authentication as described at:
* http://www.openanzo.org/projects/openanzo/wiki/AnzoJsSessionKeyAuthenticationDesign
* The original copyright statement from the FormAuthenticator is reproduced below.
* The FormAuthenticator's authors were listed in the source as:
* Greg Wilkins (gregw) and dan@greening.name
* ========================================================================
* Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
* ------------------------------------------------------------------------
* 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 org.openanzo.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.time.DateUtils;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.rdf.utils.SerializationConstants;
import org.openanzo.security.keystore.ISecretKeystore;
import org.openanzo.services.AnzoPrincipal;
import org.openanzo.services.EncryptedTokenAuthenticatorConstants;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Encrypted Token Authentication Authenticator. This authenticator implements and authentication scheme similar to the typical J2EE Form-based authentication
* except that rather than depending on the session to record authentication credentials, it encrypts the credentials inside a cookie.
*
* The authentication scheme is described at: http://www.openanzo.org/projects/openanzo/wiki/AnzoJsSessionKeyAuthenticationDesign
*
* @author Jordi Albornoz Mulligan (<a href="mailto:jordi@cambridgesemantics.com">jordi@cambridgesemantics.com</a>)
*/
public class EncryptedTokenAuthenticator extends BasicContext implements EncryptedTokenAuthenticatorConstants {
private static final Logger log = LoggerFactory.getLogger(EncryptedTokenAuthenticator.class);
private static final long serialVersionUID = 1L;
private final static String AUTH_METHOD = "ANZO_ENCRYPTED_TOKEN";
final static String TOKEN_DELIMITER = ";";
/**
* Name of a request attribute that will be set if the authentication cookie should be refreshed. That is, authentication was valid and the refresh window
* has elapsed. The authenticator will set the cookie in this request attribute instead of directly adding the refreshed cookie to the response whenever the
* 'customTokenRefresh' config property is true. The value of the request attribute will be the actual {@link Cookie} object containing the refreshed
* cookie.
*
* This is mainly useful for situations where the authenticator is being asked to skip refreshing the cookie. For example, if the servlets want certain
* requests to not count as valid activity toward resetting the timeout. One useful scenario for that is cometd, which polls the server every 30 seconds or
* so. We'd like to timeout the user even in light of a bunch of empty poll responses.
*/
public final static String ANZO_REFRESH_COOKIE_ATTRIBUTE = "org.openanzo.standaloneEncryptedTokenAuthenticator.refreshCookie";
private String _formErrorPage;
private String _formErrorPath;
private String _formLoginPage;
private String _formLoginPath;
private ISecretKeystore secretKeyEncoder;
private long tokenTimeout = -1;
private long tokenRefreshWindow = -1;
/**
* @see #ANZO_REFRESH_COOKIE_ATTRIBUTE
*/
private Boolean customTokenRefresh;
private ServerRealm realm;
private Collection<PathSpec> protectedPathSpec = new ArrayList<PathSpec>();
/**
*
* create a new EncryptedTokenAuthenticator
*
* @param bundleContext
* bundle context
* @param securityConstraint
* securityConstraint
* @param realm
* security realm for authentication
* @param encoder
* keystore
* @param docRoot
* docroot for servlet
* @param pathSpec
* unprotected paths for servlet
* @param protectedPathSpec
* the protected paths for servlet
*/
public EncryptedTokenAuthenticator(BundleContext bundleContext, SecurityConstraint securityConstraint, ServerRealm realm, ISecretKeystore encoder, String docRoot, Collection<PathSpec> pathSpec, Collection<PathSpec> protectedPathSpec) {
this(bundleContext, securityConstraint, realm, encoder, docRoot, pathSpec, protectedPathSpec, false);
}
/**
*
* create a new EncryptedTokenAuthenticator
*
* @param bundleContext
* bundle context
* @param securityConstraint
* securityConstraint
* @param realm
* security realm for authentication
* @param encoder
* keystore
* @param docRoot
* docroot for servlet
* @param pathSpec
* unprotected paths for servlet
* @param protectedPathSpec
* the protected paths for servlet
* @param retrieveDir
* return directory resources
*/
public EncryptedTokenAuthenticator(BundleContext bundleContext, SecurityConstraint securityConstraint, ServerRealm realm, ISecretKeystore encoder, String docRoot, Collection<PathSpec> pathSpec, Collection<PathSpec> protectedPathSpec, boolean retrieveDir) {
super(bundleContext, securityConstraint, docRoot, retrieveDir);
if (encoder == null) {
throw new AnzoRuntimeException(ExceptionConstants.CORE.NULL_PARAMETER, "encoder");
}
this.secretKeyEncoder = encoder;
this.realm = realm;
//this.pathSpec = pathSpec;
this.protectedPathSpec = protectedPathSpec;
}
@Override
public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (super.handleSecurity(request, response)) {
return authenticate(request, response);
} else {
return false;
}
}
/**
* Set the login page
*
* @param path
* path to login page
*/
public void setLoginPage(String path) {
if (path == null || path.trim().length() == 0) {
_formLoginPage = "/authentication/login.html";
_formLoginPath = "/authentication/login.html";
} else {
if (!path.startsWith("/")) {
log.warn(LogUtils.LIFECYCLE_MARKER, "EncryptedTokenAuthenticator login page must start with /");
path = "/" + path;
}
_formLoginPage = path;
_formLoginPath = path;
if (_formLoginPath.indexOf('?') > 0)
_formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
}
}
/**
* Get the login page
*
* @return the login page
*/
public String getLoginPage() {
return _formLoginPage;
}
/**
* Set the login error page
*
* @param path
* the login error page
*/
public void setErrorPage(String path) {
if (path == null || path.trim().length() == 0) {
_formErrorPath = "/authentication/error.html";
_formErrorPage = "/authentication/error.html";
} else {
if (!path.startsWith("/")) {
log.warn(LogUtils.LIFECYCLE_MARKER, "EncryptedTokenAuthenticator error page must start with /");
path = "/" + path;
}
_formErrorPage = path;
_formErrorPath = path;
if (_formErrorPath != null && _formErrorPath.indexOf('?') > 0)
_formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
}
}
/**
* Get the login error page
*
* @return the login error page
*/
public String getErrorPage() {
return _formErrorPage;
}
/**
* Perform form authentication. Called from SecurityHandler.
*
* @param servletRequest
* request to authenticate
* @param response
* response to send response data
*
* @return UserPrincipal if authenticated else null.
* @throws IOException
*/
@SuppressWarnings("null")
public boolean authenticate(HttpServletRequest servletRequest, HttpServletResponse response) throws IOException {
// NOTE: Jetty will sometimes call this method with a null response parameter. In particular, from
// the org.eclipse.jetty.Request#getUserPrincipal() method.
boolean ret = false;
Request request = Request.getRequest(servletRequest);
String uri = request.getServletPath();
boolean protectedPath = false;
for (PathSpec spec : protectedPathSpec) {
if (spec.matches(uri)) {
protectedPath = true;
break;
}
}
// Setup some defaults. Unfortunately, this is the only place we really get to set these up since the
// Authenticator interface doesn't have an 'init'-like method.
if (tokenTimeout <= 0) {
tokenTimeout = 30 * DateUtils.MILLIS_PER_MINUTE;
}
if (tokenRefreshWindow <= 0) {
tokenRefreshWindow = 5 * DateUtils.MILLIS_PER_MINUTE;
}
if (customTokenRefresh == null) {
customTokenRefresh = Boolean.FALSE;
}
// Now handle the request
uri = request.getPathInfo();
if (uri.endsWith(LOGIN_URI_SUFFIX)) {
// Handle a request for authentication.
ret = false; // We will entirely handle the request here so return false to stop processing the request.
String username = request.getParameter(USERNAME_PARAMETER_NAME);
String password = request.getParameter(PASSWORD_PARAMETER_NAME);
if (username == null || password == null) {
log.error(LogUtils.SECURITY_MARKER, "Invalid anzo_authenticate request url:{} username:{} password:{}", new Object[] { uri, username, password == null ? null : "XXXObscuredNonNullPasswordXXX" });
}
AnzoPrincipal userPrincipal = null;
try {
userPrincipal = realm.authenticate(username, password, request);
} catch (Exception e) {
// No matter what sort of failure occurs in the realm we want to make sure to send the appropriate
// error response or redirect. So we can't all exceptions here.
log.debug(LogUtils.SECURITY_MARKER, "Failed authentication call to the realm.", e);
}
if (userPrincipal == null) {
if (log.isDebugEnabled()) {
log.debug(LogUtils.SECURITY_MARKER, "Authentication request FAILED for {}", StringUtil.printable(username));
}
request.setAuthentication(null);
if (response != null) {
if (_formErrorPage == null || isRequestSentByXmlHttpRequest(request)) {
if (log.isDebugEnabled()) {
log.debug(LogUtils.SECURITY_MARKER, "Sending 403 Forbidden error due to invalid credentials for user {}", username);
}
response.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
String redirectPath = response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage));
log.debug(LogUtils.SECURITY_MARKER, "Sending redirect to form error page {}", redirectPath);
response.setContentLength(0);
response.sendRedirect(redirectPath);
}
}
} else {
// Authenticated OK
if (log.isDebugEnabled()) {
log.debug(LogUtils.SECURITY_MARKER, "Authentication request OK for {}", StringUtil.printable(username));
}
request.setAuthentication(new BasicUserAuthorization(userPrincipal, AUTH_METHOD));
// Set the encrypted token
if (response != null) {
try {
String token = createEncryptedToken(username, request.getRemoteAddr());
Cookie tokenCookie = new Cookie(ANZO_TOKEN_COOKIE_NAME, token);
tokenCookie.setPath("/");
response.addCookie(tokenCookie);
if (isRequestSentByXmlHttpRequest(request)) {
// XMLHttpRequests just want a response with the cookie, no fancy redirects or anything like that.
// just send back 200 in text.(Need to send back something else firefox reports an error)
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.print(HttpServletResponse.SC_OK);
out.flush();
out.close();
} else {
// Redirect to the URL to user wanted to get to initially, or "/" if there isn't any such URL.
// We get the URL from a query parameter in the HTTP Referer (sic) header.
String referer = request.getHeader(HttpHeaders.REFERER);
String redirectPath = null;
if (referer != null) {
HttpURI refererUri = new HttpURI(referer);
MultiMap<String> queryParams = new MultiMap<String>();
refererUri.decodeQueryTo(queryParams, null);
String desiredUrl = (String) queryParams.getValue(ANZO_URL_QUERY_PARAM, 0);
if (desiredUrl != null) {
redirectPath = desiredUrl;
}
}
if (redirectPath == null) {
redirectPath = URIUtil.addPaths(request.getContextPath(), "/");
}
redirectPath = response.encodeRedirectURL(redirectPath);
log.debug(LogUtils.SECURITY_MARKER, "Sending redirect to root {} after successful login request.", redirectPath);
response.sendRedirect(redirectPath);
}
} catch (AnzoException cause) {
IOException ex = new IOException("Error creating encrypted authentication token.");
ex.initCause(cause);
throw ex;
}
}
}
} else if (isLoginOrErrorPage(uri)) {
// Don't authenticate authform or errorpage. Just let the system them out.
ret = true;
} else if (protectedPath) {
// This is a regular request for a protected resource, so check whether there is a valid
// encrypted token in the request.
AnzoPrincipal userPrincipal = null;
// Parse and validate the authentication token from the cookie
Token token = null;
long currentTime = System.currentTimeMillis();
Cookie[] cookies = request.getCookies();
Cookie tokenCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
if (ANZO_TOKEN_COOKIE_NAME.equals(cookieName)) {
tokenCookie = cookie;
try {
token = parseAnzoToken(cookie.getValue());
userPrincipal = validateAuthToken(token, realm, request.getRemoteAddr(), currentTime);
} catch (AnzoException e) {
log.debug(LogUtils.SECURITY_MARKER, "Error decrypting and parsing authentication token.", e);
}
break;
}
}
}
if (userPrincipal == null) {
// Invalid, expired, or non-existent token
ret = false; // Don't serve the resource
if (log.isDebugEnabled()) {
String msg = "Auth token ";
if (tokenCookie == null) {
msg += "MISSING";
} else {
msg += "INVALID";
}
log.debug(LogUtils.SECURITY_MARKER, msg + " for URL: {}", StringUtil.printable(request.getRequestURI()));
}
if (response != null) {
Cookie cookie = new Cookie(ANZO_TOKEN_COOKIE_NAME, "");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie); // adding a cookie with MaxAge=0 tells the client to delete the cookie.
if (_formLoginPage == null || isRequestSentByXmlHttpRequest(request)) {
if (log.isDebugEnabled()) {
log.debug(LogUtils.SECURITY_MARKER, "Sending 403 Forbidden error due to invalid auth token. Token: {}", token);
}
response.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
// We save the URL the user tried to access into a query parameter in the redirect to the login page.
// That way the login page can send the user to the page they wanted after they finish logging in.
// First we must reconstruct the URL the user accessed.
String requestUrl = uri;
if (request.getQueryString() != null) {
requestUrl += "?" + request.getQueryString();
}
requestUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + URIUtil.addPaths(request.getContextPath(), requestUrl);
// Now we add the requested URL as a query parameter to the login URL
MultiMap<String> loginPageUrlQueryParams = new MultiMap<String>();
loginPageUrlQueryParams.put(ANZO_URL_QUERY_PARAM, requestUrl);
String loginPageUrl = URIUtil.addPaths(request.getContextPath(), _formLoginPage);
try {
loginPageUrl = addQueryParametersToURI(loginPageUrl, loginPageUrlQueryParams);
} catch (URISyntaxException e) {
log.warn(LogUtils.SECURITY_MARKER, "Error creating login redirect URL. The user's attempted URL won't be saved for use after login.", e);
}
String redirectPath = response.encodeRedirectURL(loginPageUrl);
log.debug(LogUtils.SECURITY_MARKER, "Sending redirect to form login page {} after request without adequate credentials.", redirectPath);
response.setContentLength(0);
response.sendRedirect(redirectPath);
}
}
} else {
// Properly authenticated
if (log.isDebugEnabled()) {
log.debug(LogUtils.SECURITY_MARKER, "Auth token OK for '{}' for URL:{}", StringUtil.printable(userPrincipal.getName()), StringUtil.printable(request.getRequestURI()));
}
if (userPrincipal instanceof AnzoPrincipal) {
request.setAttribute(SerializationConstants.authenticationURI, (userPrincipal).getUserURI());
}
request.setAttribute(SerializationConstants.userPrincipal, userPrincipal);
request.setAuthentication(new BasicUserAuthorization(userPrincipal, AUTH_METHOD));
ret = true;
// Check if the token is older than the refresh window. If so, create a new cookie with an updated timestamp.
try {
if (currentTime < token.getTimestamp() || (currentTime - token.getTimestamp() >= tokenRefreshWindow)) { // if current time is less than the token's time, we'll issue a new cookie. That should only ever happen upon overflow of the number of milliseconds from the epoch.
String cookieval = createEncryptedToken(token.getUsername(), token.getRemoteAddress());
Cookie newTokenCookie = new Cookie(ANZO_TOKEN_COOKIE_NAME, cookieval);
newTokenCookie.setPath("/");
if (customTokenRefresh) {
request.setAttribute(ANZO_REFRESH_COOKIE_ATTRIBUTE, newTokenCookie);
} else {
response.addCookie(newTokenCookie);
}
}
} catch (AnzoException e) {
log.error(LogUtils.SECURITY_MARKER, "Could NOT update timestamp on authentication token. Authentication session may end prematurely.", e);
}
}
} else {
// This is NOT a protected resource so just let it be served.
ret = true;
}
log.debug(LogUtils.SECURITY_MARKER, "Returning from 'authenticate' with {} for path {}", ret, uri);
return ret;
}
/**
* Checks if the given token is a valid authentication token. If so, it returns the Principal for the user which the token authenticates.
*
* @param token
* the parsed token to validate
* @param realm
* used to obtain the principal using the token username
* @param requestRemoteAddress
* the IP address of the client which made supplied the token. It will be compared to the address embedded in the token
* @param currentTime
* the current time in milliseconds to compare to the token's timestamp for checking if the token is expired.
* @return the authenticated principal or null if the token is invalid.
*/
AnzoPrincipal validateAuthToken(Token token, ServerRealm realm, String requestRemoteAddress, long currentTime) {
AnzoPrincipal userPrincipal = null;
// Validate that the IP address for which the token was created is the same as the one in the current request.
if (token != null && token.getRemoteAddress().equals(requestRemoteAddress)) {
// Validate that the token isn't older than the timeout period
if (currentTime >= token.getTimestamp() && (currentTime - token.getTimestamp() < tokenTimeout)) {
// The token is valid. Let's lookup the user information as a final check.
userPrincipal = realm.getPrincipal(token.getUsername());
} else {
log.debug(LogUtils.SECURITY_MARKER, "Auth token timestamp is expired: - tokenTimestamp:{} currentTime:{} difference:{} timeout:", new Object[] { token.getTimestamp(), currentTime, currentTime - token.getTimestamp(), tokenTimeout });
}
} else {
log.debug(LogUtils.SECURITY_MARKER, "Auth token remote address does not match - tokenAddress:{} requestAddress:{}", (token != null) ? token.getRemoteAddress() : null, requestRemoteAddress);
}
return userPrincipal;
}
/**
* Creates an encrypted token by combining the username, remote IP address, and the current timestamp (in milliseconds from the epoch) and then encrypting
* the combined string. The string is of the format: "timestamp;remoteAddress;username". For example: <code>1208983023421;127.0.0.1;cooluser</code>
*
* @param username
* the username of the authenticated user
* @param remoteAddr
* the remote IP address from which the authentication request came.
* @return the base64 representation of the encrypted token string.
*/
String createEncryptedToken(String username, String remoteAddr) throws AnzoException {
String timestamp = Long.toString(System.currentTimeMillis());
StringBuilder str = new StringBuilder(username.length() + remoteAddr.length() + timestamp.length() + 2); // 2 for the colons
str.append(timestamp);
str.append(TOKEN_DELIMITER);
str.append(remoteAddr);
str.append(TOKEN_DELIMITER);
str.append(username);
String plaintoken = str.toString();
String cyphertoken = this.secretKeyEncoder.encryptAndBase64EncodeString(plaintoken);
return cyphertoken;
}
/**
* Decrypts and parses the given authentication token.
*
* @param token
* @return null if the string couldn't be parsed properly.
* @throws AnzoException
*/
Token parseAnzoToken(String token) throws AnzoException {
Token ret = null;
String plaintoken = this.secretKeyEncoder.decryptAndBase64DecodeString(token);
int firstDelimiter = plaintoken.indexOf(TOKEN_DELIMITER);
if (firstDelimiter > 0) {
String timestr = plaintoken.substring(0, firstDelimiter);
try {
long timestamp = Long.parseLong(timestr);
int secondDelimiter = plaintoken.indexOf(TOKEN_DELIMITER, firstDelimiter + 1);
if (secondDelimiter > firstDelimiter + 1) {
String remoteAddr = plaintoken.substring(firstDelimiter + 1, secondDelimiter);
if (secondDelimiter < plaintoken.length() - 1) {
String username = plaintoken.substring(secondDelimiter + 1);
ret = new Token(username, timestamp, remoteAddr);
}
}
} catch (NumberFormatException e) {
log.trace(LogUtils.SECURITY_MARKER, "Invalid timestamp in authentication token.");
}
}
return ret;
}
private boolean isLoginOrErrorPage(String pathInContext) {
return pathInContext != null && ((_formErrorPath != null && pathInContext.endsWith(_formErrorPath)) || (_formLoginPath != null && pathInContext.endsWith(_formLoginPath)));
}
private boolean isRequestSentByXmlHttpRequest(HttpServletRequest request) {
String val = request.getHeader("X-Requested-With");
return "XMLHttpRequest".equals(val);
}
static class Token {
private String username;
private long timestamp;
private String remoteAddress;
public Token(String username, long timestamp, String remoteAddress) {
this.username = username;
this.timestamp = timestamp;
this.remoteAddress = remoteAddress;
}
public String getUsername() {
return username;
}
public long getTimestamp() {
return timestamp;
}
public String getRemoteAddress() {
return remoteAddress;
}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this).append(username).append(timestamp).append(remoteAddress);
return builder.toString();
}
}
/**
* Get the authentication token timeout period.
*
* @return the token timeout in milliseconds.
*/
public long getTokenTimeout() {
return tokenTimeout;
}
/**
* Set the authentication token timeout period.
*
* @param tokenTimeout
* the token timeout in milliseconds to set.
*/
public void setTokenTimeout(long tokenTimeout) {
this.tokenTimeout = tokenTimeout;
}
/**
* Get the authentication token refresh timeout period. That is, the window of time when we avoid creating a new token to save on bandwidth and encryption
* operations.
*
* @return the authentication token refresh timeout period in milliseconds
*/
public long getTokenRefreshWindow() {
return tokenRefreshWindow;
}
/**
* Set the authentication token refresh timeout period. That is, the window of time when we avoid creating a new token to save on bandwidth and encryption
* operations.
*
* @param tokenRefreshWindow
* the authentication token refresh timeout period in milliseconds to set.
*/
public void setTokenRefreshWindow(long tokenRefreshWindow) {
this.tokenRefreshWindow = tokenRefreshWindow;
}
/**
* If true, the authenticator will not refresh the cookie when the refresh window elapses. Instead, it will place the cookie that would have been used to
* refresh the token in a request attribute called {@link #ANZO_REFRESH_COOKIE_ATTRIBUTE}.
*
* If false, the authenticator will refresh the authentication token by adding a cookie directly to the response and will not fill the
* {@link #ANZO_REFRESH_COOKIE_ATTRIBUTE} request attribute.
*
* Default is false. But will return null if it hasn't been set so that an explicit 'false' can be distinguished from a default 'false'.
*
* @see #ANZO_REFRESH_COOKIE_ATTRIBUTE
* @return true if a custom token refresh value should be used
*
*/
public Boolean getCustomTokenRefresh() {
return customTokenRefresh;
}
/**
* Sets the customTokenRefresh mode.
*
* @see #getCustomTokenRefresh()
* @param customTokenRefresh
* the customTokenRefresh mode to set
*/
public void setCustomTokenRefresh(Boolean customTokenRefresh) {
this.customTokenRefresh = customTokenRefresh;
}
/**
* @return the docRoot
*/
public String getDocRoot() {
return docRoot;
}
/**
* @param docRoot
* the docRoot to set
*/
public void setDocRoot(String docRoot) {
this.docRoot = docRoot;
}
/**
* Adds the give parameters to a URI string in the URI's query portion. It will add the '?' if needed, and will simply add the arguments if the URI already
* has a query portion. It will also allow URIs with fragment portions (ex. '#foo') and place the query fragment and parameters in the appropriate place. It
* will also escape any special URI characters in the parameter names or values.
*
* This method assumes that the query string is in x-www-form-urlencoded format.
*
* @param uri
* the URI string to modify
* @param parameters
* the map with the key/value parameters to add to the query portion of the URI
* @return a String URI with the parameters added to the given URI.
* @throws URISyntaxException
*/
public static String addQueryParametersToURI(String uri, MultiMap<String> parameters) throws URISyntaxException {
URI inUri = new URI(uri);
String paramStr = UrlEncoded.encode(parameters, null, false);
String newQuery = inUri.getQuery() == null ? paramStr : inUri.getQuery() + "&" + paramStr;
StringBuilder outUri = new StringBuilder();
if (inUri.getScheme() != null) {
outUri.append(inUri.getScheme());
outUri.append(':');
}
if (inUri.getRawAuthority() != null) {
outUri.append("//");
outUri.append(inUri.getRawAuthority());
}
if (inUri.getRawPath() != null) {
outUri.append(inUri.getRawPath());
}
if (StringUtils.isNotEmpty(newQuery)) {
outUri.append('?');
outUri.append(newQuery);
}
if (inUri.getRawFragment() != null) {
outUri.append("#");
outUri.append(inUri.getRawFragment());
}
return outUri.toString();
}
/**
* Log the request to DEBUG level. WARNING: This should be used sparingly, if at all. Logging the entire request may expose sensitive information such as
* passwords in the log files.
*
* @param request
*/
void logRequest(Request request) {
if (log.isDebugEnabled()) {
StringBuilder msg = new StringBuilder();
msg.append("Request for ");
msg.append(request.getRequestURL());
msg.append("\nHeaders:");
Enumeration<?> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
msg.append("\n");
msg.append(name);
msg.append(" : ");
Enumeration<?> headers = request.getHeaders(name);
while (headers.hasMoreElements()) {
msg.append((String) headers.nextElement());
if (headers.hasMoreElements()) {
msg.append(",");
}
}
}
msg.append("\nBody\n");
Reader reader = null;
try {
reader = request.getReader();
String body = IOUtils.toString(reader);
msg.append(body);
} catch (IOException e) {
msg.append(e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.debug(LogUtils.SECURITY_MARKER, "Error closing request reader while logging", e);
}
}
}
msg.append("\nDone logging request.");
log.debug(LogUtils.SECURITY_MARKER, msg.toString());
}
}
}