summaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'jdisc-security-filters/src/main')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsLogic.java56
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsPreflightSecurityRequestFilter.java61
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityRequestFilterBase.java81
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityResponseFilter.java38
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/package-info.java8
-rw-r--r--jdisc-security-filters/src/main/resources/configdefinitions/cors-security-filter.def4
6 files changed, 248 insertions, 0 deletions
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsLogic.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsLogic.java
new file mode 100644
index 00000000000..3f1aeff9e95
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsLogic.java
@@ -0,0 +1,56 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filters.cors;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.jdisc.HeaderFields;
+import com.yahoo.jdisc.Response;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * @author bjorncs
+ */
+class CorsLogic {
+ private CorsLogic() {}
+
+ static final String CORS_PREFLIGHT_REQUEST_CACHE_TTL = Long.toString(Duration.ofDays(7).getSeconds());
+
+ static final String ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
+
+ static final Map<String, String> ACCESS_CONTROL_HEADERS = ImmutableMap.of(
+ "Access-Control-Max-Age", CORS_PREFLIGHT_REQUEST_CACHE_TTL,
+ "Access-Control-Allow-Headers", "Origin,Content-Type,Accept,Yahoo-Principal-Auth",
+ "Access-Control-Allow-Methods", "OPTIONS,GET,PUT,DELETE,POST",
+ "Access-Control-Allow-Credentials", "true"
+ );
+
+ static Map<String, String> createCorsResponseHeaders(String requestOriginHeader,
+ Set<String> allowedOrigins) {
+ if (requestOriginHeader == null) return Collections.emptyMap();
+ TreeMap<String, String> headers = new TreeMap<>();
+ allowedOrigins.stream()
+ .filter(allowedUrl -> matchesRequestOrigin(requestOriginHeader, allowedUrl))
+ .findAny()
+ .ifPresent(allowedOrigin -> headers.put(ALLOW_ORIGIN_HEADER, allowedOrigin));
+ ACCESS_CONTROL_HEADERS.forEach(headers::put);
+ return headers;
+ }
+
+ static Map<String, String> createCorsPreflightResponseHeaders(String requestOriginHeader,
+ Set<String> allowedOrigins) {
+ TreeMap<String, String> headers = new TreeMap<>();
+ if (allowedOrigins.contains(requestOriginHeader))
+ headers.put(ALLOW_ORIGIN_HEADER, requestOriginHeader);
+ ACCESS_CONTROL_HEADERS.forEach(headers::put);
+ return headers;
+ }
+
+ private static boolean matchesRequestOrigin(String requestOrigin, String allowedUrl) {
+ return allowedUrl.equals("*") || requestOrigin.startsWith(allowedUrl);
+ }
+}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsPreflightSecurityRequestFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsPreflightSecurityRequestFilter.java
new file mode 100644
index 00000000000..d162fff53b1
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsPreflightSecurityRequestFilter.java
@@ -0,0 +1,61 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filters.cors;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpResponse;
+import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+import com.yahoo.yolean.chain.Provides;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.OPTIONS;
+import static com.yahoo.jdisc.http.filters.cors.CorsLogic.createCorsPreflightResponseHeaders;
+
+/**
+ * <p>
+ * This filter makes sure we respond as quickly as possible to CORS pre-flight requests
+ * which browsers transmit before the Hosted Vespa dashboard code is allowed to send a "real" request.
+ * </p>
+ * <p>
+ * An "Access-Control-Max-Age" header is added so that the browser will cache the result of this pre-flight request,
+ * further improving the responsiveness of the Hosted Vespa dashboard application.
+ * </p>
+ * <p>
+ * Runs after all standard security request filters, but before BouncerFilter, as the browser does not send
+ * credentials with pre-flight requests.
+ * </p>
+ *
+ * @author andreer
+ * @author gv
+ * @author bjorncs
+ */
+@Provides("CorsPreflightSecurityRequestFilter")
+public class CorsPreflightSecurityRequestFilter implements SecurityRequestFilter {
+ private final Set<String> allowedUrls;
+
+ @Inject
+ public CorsPreflightSecurityRequestFilter(CorsSecurityFilterConfig config) {
+ this.allowedUrls = new HashSet<>(config.allowedUrls());
+ }
+
+ @Override
+ public void filter(DiscFilterRequest discFilterRequest, ResponseHandler responseHandler) {
+ String origin = discFilterRequest.getHeader("Origin");
+
+ if (!discFilterRequest.getMethod().equals(OPTIONS.name()))
+ return;
+
+ HttpResponse response = HttpResponse.newInstance(Response.Status.OK);
+
+ createCorsPreflightResponseHeaders(origin, allowedUrls)
+ .forEach(response.headers()::put);
+
+ ContentChannel cc = responseHandler.handleResponse(response);
+ cc.close(null);
+ }
+}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityRequestFilterBase.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityRequestFilterBase.java
new file mode 100644
index 00000000000..fd64aa37875
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityRequestFilterBase.java
@@ -0,0 +1,81 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filters.cors;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.handler.FastContentWriter;
+import com.yahoo.jdisc.handler.ResponseDispatch;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.yahoo.jdisc.http.filters.cors.CorsLogic.createCorsResponseHeaders;
+
+/**
+ * Security request filters should extend this base class to ensure that CORS header are included in the response of a rejected request.
+ * This is required as response filter chains are not executed when a request is rejected in a request filter.
+ *
+ * @author bjorncs
+ */
+public abstract class CorsSecurityRequestFilterBase implements SecurityRequestFilter {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ private final Set<String> allowedUrls;
+
+ protected CorsSecurityRequestFilterBase(CorsSecurityFilterConfig config) {
+ this(new HashSet<>(config.allowedUrls()));
+ }
+
+ protected CorsSecurityRequestFilterBase(Set<String> allowedUrls) {
+ this.allowedUrls = allowedUrls;
+ }
+
+ @Override
+ public final void filter(DiscFilterRequest request, ResponseHandler handler) {
+ filter(request)
+ .ifPresent(errorResponse -> sendErrorResponse(request, errorResponse, handler));
+ }
+
+ protected abstract Optional<ErrorResponse> filter(DiscFilterRequest request);
+
+ private void sendErrorResponse(DiscFilterRequest request,
+ ErrorResponse errorResponse,
+ ResponseHandler responseHandler) {
+ Response response = new Response(errorResponse.statusCode);
+ addHeaders(request, response);
+ writeResponse(errorResponse, responseHandler, response);
+ }
+
+ private void addHeaders(DiscFilterRequest request, Response response) {
+ createCorsResponseHeaders(request.getHeader("Origin"), allowedUrls)
+ .forEach(response.headers()::add);
+ response.headers().add("Content-Type", "application/json");
+ }
+
+ private void writeResponse(ErrorResponse errorResponse, ResponseHandler responseHandler, Response response) {
+ ObjectNode errorMessage = mapper.createObjectNode();
+ errorMessage.put("message", errorResponse.message);
+ try (FastContentWriter writer = ResponseDispatch.newInstance(response).connectFastWriter(responseHandler)) {
+ writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static class ErrorResponse {
+ final int statusCode;
+ final String message;
+
+ public ErrorResponse(int statusCode, String message) {
+ this.statusCode = statusCode;
+ this.message = message;
+ }
+ }
+}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityResponseFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityResponseFilter.java
new file mode 100644
index 00000000000..bf288661bc4
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/CorsSecurityResponseFilter.java
@@ -0,0 +1,38 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filters.cors;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.http.filter.DiscFilterResponse;
+import com.yahoo.jdisc.http.filter.RequestView;
+import com.yahoo.jdisc.http.filter.SecurityResponseFilter;
+import com.yahoo.yolean.chain.Provides;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.yahoo.jdisc.http.filters.cors.CorsLogic.createCorsResponseHeaders;
+
+
+/**
+ * @author gv
+ * @author Tony Vaagenes
+ * @author bjorncs
+ */
+@Provides("CorsSecurityResponseFilter")
+public class CorsSecurityResponseFilter extends AbstractResource implements SecurityResponseFilter {
+
+ private final Set<String> allowedUrls;
+
+ @Inject
+ public CorsSecurityResponseFilter(CorsSecurityFilterConfig config) {
+ this.allowedUrls = new HashSet<>(config.allowedUrls());
+ }
+
+ @Override
+ public void filter(DiscFilterResponse response, RequestView request) {
+ createCorsResponseHeaders(request.getFirstHeader("Origin").orElse(null), allowedUrls)
+ .forEach(response::setHeader);
+ }
+
+}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/package-info.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/package-info.java
new file mode 100644
index 00000000000..6357acc4e99
--- /dev/null
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filters/cors/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+@ExportPackage
+package com.yahoo.jdisc.http.filters.cors;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/jdisc-security-filters/src/main/resources/configdefinitions/cors-security-filter.def b/jdisc-security-filters/src/main/resources/configdefinitions/cors-security-filter.def
new file mode 100644
index 00000000000..71d4b998b99
--- /dev/null
+++ b/jdisc-security-filters/src/main/resources/configdefinitions/cors-security-filter.def
@@ -0,0 +1,4 @@
+# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=jdisc.http.filters.cors
+
+allowedUrls[] string