package org.openstack.atlas.api.filters;
import org.openstack.atlas.util.debug.Debug;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.AnnotationIntrospector;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
import org.openstack.atlas.api.auth.AuthInfo;
import org.openstack.atlas.api.auth.AuthTokenValidator;
import org.openstack.atlas.cfg.PublicApiServiceConfigurationKeys;
import org.openstack.atlas.cfg.RestApiConfiguration;
import org.openstack.atlas.api.exceptions.MalformedUrlException;
import org.openstack.atlas.api.filters.wrappers.HeadersRequestWrapper;
import org.openstack.atlas.api.helpers.UrlAccountIdExtractor;
import org.openstack.atlas.docs.loadbalancers.api.v1.faults.LoadBalancerFault;
import org.openstack.atlas.util.b64aes.Base64;
import org.openstack.atlas.util.b64aes.PaddingException;
import org.openstack.atlas.util.simplecache.CacheEntry;
import org.openstack.atlas.util.simplecache.SimpleCache;
import org.openstack.identity.client.fault.IdentityFault;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.openstack.atlas.api.filters.helpers.StringUtilities.getExtendedStackTrace;
public class AuthenticationFilter implements Filter {
private final Log LOG = LogFactory.getLog(AuthenticationFilter.class);
private static final String X_AUTH_TENANT_ID = "X-Tenant-Name";
private static final String X_AUTH_USER_NAME = "X-PP-User";
private static final String X_AUTH_TOKEN = "X-Auth-Token";
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BYPASS_AUTH = "bypass_auth";
private UrlAccountIdExtractor accountIdExtractor;
private AuthTokenValidator authTokenValidator;
private RestApiConfiguration configuration;
private FilterConfig filterConfig = null;
private SimpleCache<AuthInfo> userCache;
public AuthenticationFilter(UrlAccountIdExtractor urlAccountIdExtractor) {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {// Not implemented...
}
@Override
public void destroy() {// Not implemented...
}
public AuthenticationFilter(AuthTokenValidator authTokenValidator, UrlAccountIdExtractor urlAccountIdExtractor) {
this.authTokenValidator = authTokenValidator;
this.accountIdExtractor = urlAccountIdExtractor;
}
public String requestInfo(ServletRequest servletRequest) {
String msg;
try {
HttpServletRequest hreq = (HttpServletRequest) servletRequest;
String method = hreq.getMethod();
String uri = hreq.getRequestURI().toString();
msg = String.format("Requesting URL: %s Method=%s\n", uri, method);
} catch (Exception ex) {
String exMsg = Debug.getExtendedStackTrace(ex);
msg = String.format(String.format("String exception fetching URI from request: %s\n", exMsg, exMsg));
LOG.error(msg, ex);
}
return msg;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
LOG.info(requestInfo(servletRequest));
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
handleAuthenticationRequest(httpServletRequest, httpServletResponse, filterChain);
}
}
private void handleAuthenticationRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException {
String token = null;
if (httpServletRequest.getHeader(X_AUTH_TOKEN) != null) {
token = httpServletRequest.getHeader(X_AUTH_TOKEN);
}
//Rewrite headers to include only the username, no subs or quality at this time..
String username = (httpServletRequest.getHeader(X_AUTH_USER_NAME) != null
? httpServletRequest.getHeader(X_AUTH_USER_NAME).split(";")[0]
: null);
String accountId = null;
if (httpServletRequest.getHeader(X_AUTH_TENANT_ID) != null) {
accountId = httpServletRequest.getHeader(X_AUTH_TENANT_ID);
}
String authorization = null;
if (httpServletRequest.getHeader(AUTHORIZATION_HEADER) != null) {
authorization = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
}
try {
if (username != null && accountId != null && token != null && authorization != null) {
String decoded = Base64.decode(authorization.split(" ")[1]);
if (decoded.equals(configuration.getString(PublicApiServiceConfigurationKeys.basic_auth_user)
+ ":" + configuration.getString(PublicApiServiceConfigurationKeys.basic_auth_key))) {
HeadersRequestWrapper enhancedHttpRequest = new HeadersRequestWrapper(httpServletRequest);
enhancedHttpRequest.overideHeader(X_AUTH_USER_NAME);
enhancedHttpRequest.addHeader(X_AUTH_USER_NAME, username);
LOG.info(String.format("Request successfully authenticated, passing control to the servlet. Account: %s Username: %s", accountId, username));
filterChain.doFilter(enhancedHttpRequest, httpServletResponse);
return;
}
} else if (httpServletRequest.getRequestURL().toString().contains("application.wadl")) {
//TODO:Handle un-authorized access here when we use query param for wadl
handleWadlRequest(httpServletRequest, httpServletResponse);
} else {
LOG.debug("Not a WADL nor Repose request.. attempt to validate the user with provided credentials");
handleInternalAuthenticationRequest(httpServletRequest, httpServletResponse, filterChain);
}
} catch (RuntimeException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 500, e);
} catch (ServletException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 500, e);
} catch (PaddingException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 401, e);
} catch (UnsupportedEncodingException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 401, e);
} catch (IOException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 500, e);
}
}
private void handleInternalAuthenticationRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
String INVALID_TOKEN_MESSAGE = "Invalid authentication credentials. Please review request and try again with valid credentials";
String AUTH_FAULT_MESSAGE = "There was an error while authenticating, please contact support.";
String authToken = httpServletRequest.getHeader("X-AUTH-TOKEN");
String MISSING_TOKEN_MESSAGE = "Missing authentication token.";
String username = null;
Integer accountId;
int purged;
if (isByPassAuth(httpServletRequest)) {
HeadersRequestWrapper enhancedHttpRequest = new HeadersRequestWrapper(httpServletRequest);
enhancedHttpRequest.overideHeader(X_AUTH_USER_NAME);
enhancedHttpRequest.addHeader(X_AUTH_USER_NAME, "bypassed-auth");
try {
LOG.info("RequestAuth bypassed Sending request to next filter\n");
filterChain.doFilter(enhancedHttpRequest, httpServletResponse);
return;
} catch (RuntimeException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 500, e);
}
return;
}
purged = userCache.cleanExpiredByCount(); // Prevent unchecked entries from Living forever
if (purged > 0) {
LOG.debug(String.format("cleaning auth userCache: purged %d stale entries", purged));
}
if (authToken == null || authToken.isEmpty()) {
sendUnauthorizedResponse(httpServletRequest, httpServletResponse, MISSING_TOKEN_MESSAGE);
return;
}
try {
accountId = accountIdExtractor.getAccountId(httpServletRequest.getRequestURL().toString());
} catch (MalformedUrlException exception) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 404, exception);
return;
}
try {
LOG.debug(String.format("Before calling validate on account: %s", accountId));
String accountStr = String.format("%d", accountId);
CacheEntry<AuthInfo> ce = userCache.getEntry(accountStr);
AuthInfo authInfo = null;
if (ce == null || ce.isExpired()) {
userCache.remove(accountStr);
} else {
authInfo = ce.getVal();
LOG.debug(String.format("Cache hit %s expires in %d secs", accountStr, ce.expiresIn()));
}
if (authInfo == null || !authInfo.getAuthToken().equals(authToken)) {
LOG.info(String.format("Attempting to contact the auth service for account %s", accountId));
username = authTokenValidator.validate(authToken, String.valueOf(accountId)).getUser().getName();
if (username == null) {
sendUnauthorizedResponse(httpServletRequest, httpServletResponse, INVALID_TOKEN_MESSAGE);
return;
}
LOG.info(String.format("Successfully retrieved users info from the auth service for account: %s returned username: %s", accountId, username));
authInfo = new AuthInfo(username, authToken);
LOG.debug(String.format("insert %s-%s into userCache", accountStr, username));
userCache.put(accountStr, authInfo);
} else {
username = authInfo.getUserName();
}
} catch (IdentityFault kex) {
String exceptMsg = getExtendedStackTrace(kex);
if (kex.code == 401 || kex.code == 404) {
LOG.error(String.format("Error while authenticating user %s-%s: ERROR CODE: %d Message: %s Full-Stack: %s\n", accountId, username, kex.code, kex.message, exceptMsg));
sendUnauthorizedResponse(httpServletRequest, httpServletResponse, INVALID_TOKEN_MESSAGE);
return;
} else {
LOG.error(String.format("Error while authenticating user %s-%s: ERROR CODE: %d Message: %s Details: %s Full-Stack: %s\n", accountId, username, kex.code, kex.message, kex.details, exceptMsg));
sendUnauthorizedResponse(httpServletRequest, httpServletResponse, AUTH_FAULT_MESSAGE);
return;
}
} catch (Exception e) {
String exceptMsg = getExtendedStackTrace(e);
LOG.error(String.format("Error while authenticating user %s-%s:%s\n", accountId, username, exceptMsg));
httpServletResponse.sendError(500, e.getMessage());
return;
}
HeadersRequestWrapper enhancedHttpRequest = new HeadersRequestWrapper(httpServletRequest);
enhancedHttpRequest.overideHeader(X_AUTH_USER_NAME);
enhancedHttpRequest.addHeader(X_AUTH_USER_NAME, username);
try {
LOG.info(String.format("Request successfully authenticated, passing control to the servlet. Account: %s Username: %s", accountId, username));
filterChain.doFilter(enhancedHttpRequest, httpServletResponse);
return;
} catch (IllegalArgumentException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 404, e);
} catch (RuntimeException e) {
handleErrorReposnse(httpServletRequest, httpServletResponse, 500, e);
}
}
private void handleWadlRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
//Temp for fix in Repose to handle query params horrible things happen here
LOG.info("WADL request, forwarding to CXF to produce the WADL.");
if (httpServletRequest.getRequestURL().toString().contains("application.wadl")
|| httpServletRequest.getQueryString().contains("wadl")) {
HeadersRequestWrapper enhancedHttpRequest = new HeadersRequestWrapper(httpServletRequest);
String root = httpServletRequest.getRequestURI().split("/application.wadl")[0];
RequestDispatcher dispatcher = enhancedHttpRequest.getRequestDispatcher(
"/00000/loadbalancers?_wadl"); //have to forward to resource other then root...
dispatcher.forward(enhancedHttpRequest, httpServletResponse);
}
}
private void handleErrorReposnse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, int errorCode, Exception e) throws IOException {
final String UNEXPECTED = "Something unexpected happened. Please contact support.";
final String UNAUTHENTICATED = "User not authenticated, please retry the request with valid auth credentials. ";
final String ILLEGALARG = "Illegal argument exception. Check documentation for proper usage.";
if (errorCode == 500) {
String exceptMsg = getExtendedStackTrace(e);
LOG.error(String.format("Error in filterChain:%s\n", exceptMsg));
httpServletResponse.sendError(errorCode, UNEXPECTED);
} else if (errorCode == 401) {
LOG.error(String.format("Error in filterChain:%s\n", e.getLocalizedMessage()));
sendUnauthorizedResponse(httpServletRequest, httpServletResponse, UNAUTHENTICATED);
} else if (errorCode == 404) {
LOG.error(String.format("Error in filterChain:%s\n", e.getLocalizedMessage()));
httpServletResponse.sendError(errorCode, ILLEGALARG);
} else {
LOG.error(String.format("Error in filterChain:%s\n", e.getLocalizedMessage()));
httpServletResponse.sendError(errorCode, e.getMessage());
}
}
private void sendUnauthorizedResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String message) throws IOException {
String contentType = accountIdExtractor.getContentType(httpServletRequest.getRequestURL().toString());
LoadBalancerFault unauthorized = new LoadBalancerFault();
unauthorized.setCode(401);
unauthorized.setMessage(message);
httpServletResponse.setStatus(SC_UNAUTHORIZED);
if (contentType.equals("xml") || !contentType.equals("json") && httpServletRequest.getContentType() != null && httpServletRequest.getContentType().equals("application/xml")) {
try {
httpServletResponse.setContentType("application/xml; charset=UTF-8");
Marshaller marshaller = JAXBContext.newInstance(unauthorized.getClass()).createMarshaller();
marshaller.marshal(unauthorized, httpServletResponse.getWriter());
} catch (JAXBException e) {
String ErrorMsg = getExtendedStackTrace(e);
LOG.error("Marshalling failed", e);
httpServletResponse.sendError(SC_INTERNAL_SERVER_ERROR);
}
} else {
httpServletResponse.setContentType("application/json; charset=UTF-8");
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper.getDeserializationConfig().setAnnotationIntrospector(introspector);
mapper.getSerializationConfig().setAnnotationIntrospector(introspector);
mapper.writeValue(httpServletResponse.getWriter(), unauthorized);
}
}
public void startConfig() {
//Init
}
public void setUserCache(SimpleCache userCache) {
this.userCache = userCache;
}
public SimpleCache getUserCache() {
return userCache;
}
public void setConfiguration(RestApiConfiguration configuration) {
this.configuration = configuration;
}
public RestApiConfiguration getConfiguration() {
return configuration;
}
private boolean isByPassAuth(HttpServletRequest httpServletRequest) {
String allow_bypassauth = configuration.getString(PublicApiServiceConfigurationKeys.allow_bypassauth);
String bypass_auth = httpServletRequest.getHeader("bypass-auth");
if (allow_bypassauth == null) {
return false;
}
if (!allow_bypassauth.equalsIgnoreCase("true")) {
return false;
}
if (bypass_auth == null) {
return false;
}
if (!bypass_auth.equalsIgnoreCase("true")) {
return false;
}
return true;
}
}