/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.picketlink.http.internal.cors; import org.picketlink.config.http.CORSConfiguration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.picketlink.log.BaseLog.HTTP_LOGGER; /** * Cross-Origin Resource Sharing (CORS) Class. * * <p> * The class intercepts incoming HTTP requests and applies the CORS policy as specified by the configuration parameters. The * actual CORS request is processed by this class. * * <p> * Supported configuration parameters: * * <ul> * <li>cors.allowOrigin {"*"|origin-list} defaults to {@code *}. * <li>cors.allowMethods {method-list} defaults to {@code "GET, POST, HEAD, OPTIONS"}. * <li>cors.allowHeaders {"*"|header-list} defaults to {@code *}. * <li>cors.exposedHeaders {header-list} defaults to empty list. * <li>cors.allowCredentials {true|false} defaults to {@code true}. * <li>cors.maxAge {int} defaults to {@code -1} (unspecified). * </ul> * * @author Giriraj Sharma */ public class CORS { public static final String ORIGIN = "Origin"; public static final String HOST = "Host"; public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1); public static final String DEFAULT_ALLOW_METHODS = "GET, POST, HEAD, OPTIONS"; public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; public static final String ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD = "*"; public static void handleActualRequest(CORSConfiguration corsConfiguration, HttpServletRequest request, HttpServletResponse response) { HTTP_LOGGER.debugf("Processing CORS Actual Request to path [%s].", request.getRequestURI()); final String requestOrigin = request.getHeader(ORIGIN); if (requestOrigin == null) { HTTP_LOGGER.debug("CORS origin header is null"); throw new RuntimeException("CORS origin header is null"); } Set<String> allowedOrigins = corsConfiguration.getAllowedOrigins(); if (allowedOrigins == null || (!allowedOrigins.contains(requestOrigin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD) && !corsConfiguration.isAllowAnyOrigin())) { HTTP_LOGGER.debug("CORS origin denied " + requestOrigin); throw new RuntimeException("CORS origin denied " + requestOrigin); } if (!corsConfiguration.isAllowAnyMethod()) { final String method = request.getMethod().toUpperCase(); Set<String> allowedMethods = corsConfiguration.getAllowedMethods(); if (!allowedMethods.contains(method)) { HTTP_LOGGER.debug("Unsupported HTTP method " + method); throw new RuntimeException("Unsupported HTTP method " + method); } } if (corsConfiguration.isAllowCredentials()) { response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, requestOrigin); } else { if (corsConfiguration.isAllowAnyOrigin()) { response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD); } else { response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, requestOrigin); } } Set<String> exposedHeaders = corsConfiguration.getExposedHeaders(); if (exposedHeaders != null && !exposedHeaders.isEmpty()) { response.addHeader(ACCESS_CONTROL_EXPOSE_HEADERS, CorsUtil.join(exposedHeaders)); } } public static void handlePreflightRequest(CORSConfiguration corsConfiguration, HttpServletRequest request, HttpServletResponse response) { HTTP_LOGGER.debugf("Processing CORS Preflight Request to path [%s].", request.getRequestURI()); final String requestOrigin = request.getHeader(ORIGIN); if (requestOrigin == null) { HTTP_LOGGER.debug("CORS origin header is null"); throw new RuntimeException("CORS origin header is null"); } boolean allowAnyOrigin = corsConfiguration.isAllowAnyOrigin(); if (!allowAnyOrigin) { Set<String> allowedOrigins = corsConfiguration.getAllowedOrigins(); if (allowedOrigins == null || (!allowedOrigins.contains(requestOrigin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD) && !allowAnyOrigin)) { HTTP_LOGGER.debug("CORS origin denied " + requestOrigin); throw new RuntimeException("CORS origin denied " + requestOrigin); } } Set<String> allowedMethods = corsConfiguration.getAllowedMethods(); if (!corsConfiguration.isAllowAnyMethod()) { String requestMethodHeader = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD); String requestedMethod = requestMethodHeader.toUpperCase(); if (!allowedMethods.contains(requestedMethod)) { HTTP_LOGGER.debug("Unsupported HTTP access control request method " + requestedMethod); throw new RuntimeException("Unsupported HTTP access control request method " + requestedMethod); } if (requestMethodHeader == null) { HTTP_LOGGER.debug("Invalid preflight CORS request: Missing Access-Control-Request-Method header"); throw new RuntimeException("Invalid preflight CORS request: Missing Access-Control-Request-Method header"); } } // Parse the requested author (custom) headers final String rawRequestHeadersString = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); final String[] requestHeaderValues = CorsUtil.parseMultipleHeaderValues(rawRequestHeadersString); final String[] requestHeaders = new String[requestHeaderValues.length]; for (int i = 0; i < requestHeaders.length; i++) { try { requestHeaders[i] = CorsUtil.formatCanonical(requestHeaderValues[i]); } catch (IllegalArgumentException e) { // Invalid header name HTTP_LOGGER.debug("Invalid preflight CORS request: Bad request header value " + requestHeaderValues[i]); throw new RuntimeException("Invalid preflight CORS request: Bad request header value " + requestHeaderValues[i]); } } // Author request headers check Set<String> allowedHeaders = corsConfiguration.getAllowedHeaders(); if (!corsConfiguration.isAllowAnyHeader()) { for (String requestHeader : requestHeaders) { if (!allowedHeaders.contains(requestHeader)) { HTTP_LOGGER.debug("Unsupported HTTP access control request header " + requestHeader); throw new RuntimeException("Unsupported HTTP access control request header " + requestHeader); } } } if (corsConfiguration.isAllowCredentials()) { response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, requestOrigin); } else { if (allowAnyOrigin) { response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD); } else { response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, requestOrigin); } } long maxAge = corsConfiguration.getMaxAge(); if (Long.valueOf(maxAge) != null && maxAge > 0) { response.addHeader(ACCESS_CONTROL_MAX_AGE, String.valueOf(maxAge)); } else { response.addHeader(ACCESS_CONTROL_MAX_AGE, String.valueOf(DEFAULT_MAX_AGE)); } if (allowedMethods != null && !allowedMethods.isEmpty()) { response.addHeader(ACCESS_CONTROL_ALLOW_METHODS, CorsUtil.join(allowedMethods)); } else { response.addHeader(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS); } if (corsConfiguration.isAllowAnyHeader() && rawRequestHeadersString != null) { response.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, rawRequestHeadersString); } else if (allowedHeaders != null && !allowedHeaders.isEmpty()) { response.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, CorsUtil.join(allowedHeaders)); } } }