/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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 com.cinchapi.concourse.server.http.webserver; import java.io.IOException; import java.security.GeneralSecurityException; import javax.annotation.Nullable; import javax.servlet.Filter; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.session.SessionHandler; import com.cinchapi.common.reflect.Reflection; import com.cinchapi.concourse.server.GlobalState; import com.cinchapi.concourse.server.http.HttpAuthToken; import com.cinchapi.concourse.server.http.HttpRequests; import com.cinchapi.concourse.util.ObjectUtils; import com.cinchapi.concourse.util.Strings; import com.google.common.base.Throwables; import spark.webserver.NotConsumedException; /** * Simple Jetty Handler * * @author Per Wendel * @author Jeff Nelson */ public class ConcourseHttpHandler extends SessionHandler { /** * Search through the {@code request} for the value of the {@code name} * cookie, if it exists. * * @param name * @param request * @return the cookie value or {@code null} */ @Nullable private static String findCookieValue(String name, HttpServletRequest request) { if(request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { if(cookie.getName().equals(name)) { return cookie.getValue(); } } } return null; } /** * Given the {@code target} of the request, check to see if the user has * specified an environment that matches the environment embedded in the * access token cookie. If so, strip the environment from the target * * @param target * @param baseRequest * @param request */ private static void rewrite(String target, Request baseRequest, HttpServletRequest request) { String[] targetParts = target.split("/"); boolean rewrite = false; boolean requireAuth = true; if(targetParts.length >= 2) { String targetEnv = targetParts[1]; if(targetEnv.equals("login")) { // When route is /login, do not rewrite by dropping the declared // environment. Just set the request attribute to use the // DEFAULT ENVIRONMENT targetEnv = GlobalState.DEFAULT_ENVIRONMENT; requireAuth = false; } else if(targetParts.length >= 3 && targetParts[2].equals("login")) { // When route is /<environment>/login, rewrite without a // declared environment like we would all other requests, but // tell the request attribute to use the declared environment // instead of the one in the cookie. target = target.replaceFirst(targetEnv, "").replaceAll("//", "/"); rewrite = true; requireAuth = false; } else { // Rewrite all requests to drop the declared environment from // the path and use the request attributes to specify meta // information String token = ObjectUtils.firstNonNullOrNull( findCookieValue(GlobalState.HTTP_AUTH_TOKEN_COOKIE, request), request .getHeader(GlobalState.HTTP_AUTH_TOKEN_HEADER)); if(token != null) { try { HttpAuthToken auth = HttpRequests .decodeAuthToken(token); if(auth.getEnvironment().equals(targetEnv)) { target = target.replaceFirst(targetEnv, "") .replaceAll("//", "/"); rewrite = true; } else { targetEnv = auth.getEnvironment(); rewrite = true; } request.setAttribute( GlobalState.HTTP_ACCESS_TOKEN_ATTRIBUTE, auth.getAccessToken()); request.setAttribute( GlobalState.HTTP_FINGERPRINT_ATTRIBUTE, auth.getFingerprint()); } catch (Exception e) { if(e instanceof GeneralSecurityException || (e instanceof RuntimeException && e .getCause() != null & e.getCause() instanceof GeneralSecurityException)) {} else { throw Throwables.propagate(e); } } } } if(rewrite) { request.setAttribute(GlobalState.HTTP_ENVIRONMENT_ATTRIBUTE, targetEnv); Reflection.set("_requestURI", target, request); Reflection.set("_pathInfo", target, request); HttpURI uri = Reflection.get("_uri", request); Reflection.set("_rawString", target, uri); } } else { String token = ObjectUtils .firstNonNullOrNull( findCookieValue(GlobalState.HTTP_AUTH_TOKEN_COOKIE, request), request.getHeader(GlobalState.HTTP_AUTH_TOKEN_HEADER)); if(token != null) { try { HttpAuthToken auth = HttpRequests.decodeAuthToken(token); request.setAttribute( GlobalState.HTTP_ACCESS_TOKEN_ATTRIBUTE, auth.getAccessToken()); request.setAttribute( GlobalState.HTTP_ENVIRONMENT_ATTRIBUTE, auth.getEnvironment()); } catch (Exception e) { if(e instanceof GeneralSecurityException || (e instanceof RuntimeException && e.getCause() != null & e.getCause() instanceof GeneralSecurityException)) {} else { throw Throwables.propagate(e); } } } } // Get the transaction token String transaction = findCookieValue( GlobalState.HTTP_TRANSACTION_TOKEN_COOKIE, request); if(transaction != null) { request.setAttribute(GlobalState.HTTP_TRANSACTION_TOKEN_ATTRIBUTE, transaction); } request.setAttribute(GlobalState.HTTP_REQUIRE_AUTH_ATTRIBUTE, requireAuth); } /** * HTTP Access-Control-Allow-Headers header. */ private static String HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; /** * HTTP Access-Control-Allow-Methods header. */ private static String HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; /** * HTTP Access-Control-Allow-Origin header. */ private static String HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; /** * HTTP Vary header. */ private static String HEADER_VARY = "Vary"; private Filter filter; public ConcourseHttpHandler(Filter filter) { this.filter = filter; } @Override public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { rewrite(target, baseRequest, request); if(GlobalState.HTTP_ENABLE_CORS) { // CON-475: Support CORS if(GlobalState.HTTP_CORS_DEFAULT_ALLOW_ORIGIN.equals("*")) { response.addHeader("Access-Control-Allow-Headers", "*"); } else { String requestOrigin = request.getHeader("Origin"); if(Strings.isSubString(requestOrigin, GlobalState.HTTP_CORS_DEFAULT_ALLOW_ORIGIN)) { response.addHeader(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, requestOrigin); response.addHeader(HEADER_VARY, requestOrigin); } } if(GlobalState.HTTP_CORS_DEFAULT_ALLOW_HEADERS.equals("*")) { response.addHeader(HEADER_ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader("Access-Control-Request-Headers")); } else { response.addHeader(HEADER_ACCESS_CONTROL_ALLOW_HEADERS, GlobalState.HTTP_CORS_DEFAULT_ALLOW_HEADERS); } String requestMethod = request.getMethod(); if(GlobalState.HTTP_CORS_DEFAULT_ALLOW_METHODS.equals("*")) { response.addHeader(HEADER_ACCESS_CONTROL_ALLOW_METHODS, requestMethod); } else { response.addHeader(HEADER_ACCESS_CONTROL_ALLOW_METHODS, GlobalState.HTTP_CORS_DEFAULT_ALLOW_METHODS); } } filter.doFilter(request, response, null); baseRequest.setHandled(true); } catch (NotConsumedException ignore) { // TODO : Not use an exception in order to be faster. baseRequest.setHandled(false); } } }