/**
* Copyright 2016 StreamSets Inc.
*
* Licensed under the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.streamsets.lib.security.http;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.streamsets.datacollector.util.Configuration;
import com.streamsets.pipeline.api.impl.Utils;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.server.Authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
public class SSOUserAuthenticator extends AbstractSSOAuthenticator {
private static final Logger LOG = LoggerFactory.getLogger(SSOUserAuthenticator.class);
private final static Set<String> TOKEN_PARAM_SET =
ImmutableSet.of(SSOConstants.USER_AUTH_TOKEN_PARAM, SSOConstants.REPEATED_REDIRECT_PARAM);
public static final String HTTP_LOAD_BALANCER_URL = "http.load.balancer.url";
private Configuration conf;
private String loadBalancerURL;
private boolean loadBalancerSecure;
public SSOUserAuthenticator(SSOService ssoService, Configuration conf) {
super(ssoService);
this.conf = conf;
if (this.conf != null) {
this.loadBalancerURL = conf.get(HTTP_LOAD_BALANCER_URL, null);
if (this.loadBalancerURL != null && this.loadBalancerURL.trim().toLowerCase().startsWith("https")) {
this.loadBalancerSecure = true;
}
}
}
@Override
protected Logger getLog() {
return LOG;
}
StringBuffer getRequestUrl(HttpServletRequest request, Set<String> queryStringParamsToRemove) {
StringBuffer requestUrl;
if (this.loadBalancerURL != null) {
requestUrl = new StringBuffer(this.loadBalancerURL);
requestUrl.append(request.getRequestURI());
} else {
requestUrl = new StringBuffer(request.getRequestURL());
}
String qs = request.getQueryString();
if (qs != null) {
String qsSeparator = "?";
for (String paramArg : Splitter.on("&").split(qs)) {
String[] paramArgArr = paramArg.split("=", 2);
if (!queryStringParamsToRemove.contains(paramArgArr[0])) {
requestUrl.append(qsSeparator).append(paramArg);
qsSeparator = "&";
}
}
}
return requestUrl;
}
String getRequestUrl(HttpServletRequest request) {
return getRequestUrl(request, Collections.<String>emptySet()).toString();
}
String getRequestUrlWithoutToken(HttpServletRequest request) {
return getRequestUrl(request, TOKEN_PARAM_SET).toString();
}
/*
* Creates Login url with request URL as parameter for a callback redirection on a successful login.
*/
String getLoginUrl(HttpServletRequest request, boolean repeatedRedirect) {
String requestUrl = getRequestUrl(request, TOKEN_PARAM_SET).toString();
return getSsoService().createRedirectToLoginUrl(requestUrl, repeatedRedirect);
}
/*
* Removes the token from request URL, redirects to the modified URL, returns the token as a header
*/
Authentication redirectToSelf(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServerAuthException {
String authToken = httpReq.getParameter(SSOConstants.USER_AUTH_TOKEN_PARAM);
String urlWithoutToken = getRequestUrlWithoutToken(httpReq);
httpRes.setHeader(SSOConstants.X_USER_AUTH_TOKEN, authToken);
try {
LOG.debug("Redirecting to self without token '{}'", urlWithoutToken);
httpRes.sendRedirect(urlWithoutToken);
return Authentication.SEND_CONTINUE;
} catch (IOException ex) {
throw new ServerAuthException(Utils.format("Could not redirect to '{}': {}", urlWithoutToken, ex.toString(), ex));
}
}
Authentication redirectToLogin(HttpServletRequest httpReq, HttpServletResponse httpRes) throws ServerAuthException {
boolean repeatedRedirect = httpReq.getParameter(SSOConstants.REPEATED_REDIRECT_PARAM) != null;
String urlToLogin = getLoginUrl(httpReq, repeatedRedirect);
try {
LOG.debug("Redirecting to login '{}'", urlToLogin);
httpRes.sendRedirect(urlToLogin);
return Authentication.SEND_CONTINUE;
} catch (IOException ex) {
throw new ServerAuthException(Utils.format("Could not redirect to '{}': {}", urlToLogin, ex.toString(), ex));
}
}
Authentication redirectToLogout(HttpServletResponse httpRes) throws ServerAuthException {
String urlToLogout = getSsoService().getLogoutUrl();
try {
LOG.debug("Redirecting to logout '{}'", urlToLogout);
httpRes.sendRedirect(urlToLogout);
return Authentication.SEND_SUCCESS;
} catch (IOException ex) {
throw new ServerAuthException(Utils.format("Could not redirect to '{}': {}", urlToLogout, ex.toString(), ex));
}
}
/*
* Terminates the request with an HTTP Unauthorized response or redirects to login for page requests
*/
@Override
protected Authentication returnUnauthorized(
HttpServletRequest httpReq, HttpServletResponse httpRes, String principalId, String logMessageTemplate
) throws ServerAuthException {
Authentication ret;
httpRes.addCookie(createAuthCookie(httpReq, "", 0));
if (httpReq.getHeader(SSOConstants.X_REST_CALL) != null) {
ret = super.returnUnauthorized(httpReq, httpRes, null, logMessageTemplate);
} else {
redirectToLogin(httpReq, httpRes);
ret = Authentication.SEND_FAILURE;
}
return ret;
}
Cookie createAuthCookie(HttpServletRequest httpReq, String authToken, long expiresMillis) {
Cookie authCookie = new Cookie(getAuthCookieName(httpReq), authToken);
authCookie.setPath("/");
// if positive it is a persistent session, else a transient one and we don't have to set the cookie age
if (expiresMillis > 0) {
int secondsToLive = (int) ((expiresMillis - System.currentTimeMillis()) / 1000);
authCookie.setMaxAge(secondsToLive);
} else if (expiresMillis == 0) {
// to delete the cookie
authCookie.setMaxAge(0);
}
authCookie.setSecure(httpReq.isSecure() || loadBalancerSecure);
return authCookie;
}
void setAuthCookieIfNecessary(HttpServletRequest req, HttpServletResponse res, String authToken, long expiresMillis) {
if (!authToken.equals(getAuthTokenFromCookie(req))) {
res.addCookie(createAuthCookie(req, authToken, expiresMillis));
}
}
boolean isLogoutRequest( HttpServletRequest httpReq) {
String logoutPath = httpReq.getContextPath() + "/logout";
return httpReq.getMethod().equals("GET") && httpReq.getRequestURI().equals(logoutPath);
}
private boolean isCORSOptionsRequest(HttpServletRequest httpReq) {
return "OPTIONS".equals(httpReq.getMethod());
}
String getAuthCookieName(HttpServletRequest httpReq) {
return SSOConstants.AUTHENTICATION_COOKIE_PREFIX + httpReq.getServerPort();
}
Cookie getAuthCookie(HttpServletRequest httpReq) {
Cookie[] cookies = httpReq.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(getAuthCookieName(httpReq))) {
return cookie;
}
}
}
return null;
}
String getAuthTokenFromCookie(HttpServletRequest httpReq) {
Cookie cookie = getAuthCookie(httpReq);
return (cookie == null) ? null : cookie.getValue();
}
boolean isAuthTokenInQueryString(HttpServletRequest httpReq) {
return httpReq.getParameter(SSOConstants.USER_AUTH_TOKEN_PARAM) != null;
}
String getAuthTokenFromRequest(HttpServletRequest httpReq) {
String authToken = httpReq.getParameter(SSOConstants.USER_AUTH_TOKEN_PARAM);
if (authToken == null) {
authToken = httpReq.getHeader(SSOConstants.X_USER_AUTH_TOKEN);
if (authToken == null) {
authToken = getAuthTokenFromCookie(httpReq);
}
}
return authToken;
}
@Override
public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory)
throws ServerAuthException {
HttpServletRequest httpReq = (HttpServletRequest) request;
HttpServletResponse httpRes = (HttpServletResponse) response;
String authToken = getAuthTokenFromRequest(httpReq);
Authentication ret = null;
if (LOG.isTraceEnabled()) {
LOG.trace("Request: {}", getRequestInfoForLogging(httpReq, SSOUtils.tokenForLog(authToken)));
}
if (isCORSOptionsRequest(httpReq)) {
httpRes.setStatus(HttpServletResponse.SC_OK);
httpRes.setHeader("Access-Control-Allow-Origin", conf.get(CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN,
CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN_DEFAULT));
httpRes.setHeader("Access-Control-Allow-Headers", conf.get(CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_HEADERS,
CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_HEADERS_DEFAULT));
httpRes.setHeader("Access-Control-Allow-Methods", conf.get(CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_METHODS,
CORSConstants.HTTP_ACCESS_CONTROL_ALLOW_METHODS_DEFAULT));
return Authentication.SEND_SUCCESS;
}
if (!mandatory) {
ret = Authentication.NOT_CHECKED;
} else {
if (authToken != null) {
try {
SSOPrincipal principal = getSsoService().validateUserToken(authToken);
if (principal != null) {
SSOAuthenticationUser user = new SSOAuthenticationUser(principal);
if (isLogoutRequest(httpReq)) {
if (LOG.isTraceEnabled()) {
LOG.trace("Principal '{}' Logout", principal.getPrincipalId());
}
getSsoService().invalidateUserToken(authToken);
ret = redirectToLogout(httpRes);
} else {
setAuthCookieIfNecessary(httpReq, httpRes, authToken, user.getSSOUserPrincipal().getExpires());
if (isAuthTokenInQueryString(httpReq)) {
if (LOG.isTraceEnabled()) {
LOG.trace(
"Redirection to self, principal '{}' request: {}",
principal.getPrincipalId(),
getRequestInfoForLogging(httpReq, SSOUtils.tokenForLog(authToken))
);
}
ret = redirectToSelf(httpReq, httpRes);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(
"Principal '{}' request: {}",
principal.getPrincipalId(),
getRequestInfoForLogging(httpReq, SSOUtils.tokenForLog(authToken))
);
}
ret = user;
}
}
}
} catch (ForbiddenException fex) {
ret = returnUnauthorized(httpReq, httpRes, fex.getErrorInfo(), null, "Request: {}");
}
}
}
if (ret == null) {
ret = returnUnauthorized(httpReq, httpRes, SSOUtils.tokenForLog(authToken), "Could not authenticate: {}");
}
return ret;
}
}