diff options
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/filter')
7 files changed, 398 insertions, 12 deletions
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java index 9b799e3a68f..a0933484f4f 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java @@ -7,6 +7,7 @@ import com.yahoo.jdisc.http.Cookie; import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.http.HttpRequest.Version; +import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpRequest; import java.net.InetSocketAddress; import java.net.URI; @@ -36,7 +37,7 @@ public abstract class DiscFilterRequest { protected static final int DEFAULT_HTTP_PORT = 80; protected static final int DEFAULT_HTTPS_PORT = 443; - private final HttpRequest parent; + private final ServletOrJdiscHttpRequest parent; protected final Map<String, List<String>> untreatedParams; private final HeaderFields untreatedHeaders; private List<Cookie> untreatedCookies = null; @@ -44,7 +45,7 @@ public abstract class DiscFilterRequest { private String[] roles = null; private boolean overrideIsUserInRole = false; - public DiscFilterRequest(HttpRequest parent) { + public DiscFilterRequest(ServletOrJdiscHttpRequest parent) { this.parent = parent; // save untreated headers from parent diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java index c187a2f032f..fad0f46402d 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java @@ -1,10 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter; -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpResponse; - import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -12,6 +8,14 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; +import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpResponse; + +import com.yahoo.jdisc.HeaderFields; +import com.yahoo.jdisc.http.Cookie; + + +import com.yahoo.jdisc.http.HttpResponse; + /** * This class was made abstract from 5.27. Test cases that need * a concrete instance should create a {@link JdiscFilterResponse}. @@ -20,11 +24,11 @@ import java.util.List; */ public abstract class DiscFilterResponse { - private final HttpResponse parent; + private final ServletOrJdiscHttpResponse parent; private final HeaderFields untreatedHeaders; private final List<Cookie> untreatedCookies; - public DiscFilterResponse(HttpResponse parent) { + public DiscFilterResponse(ServletOrJdiscHttpResponse parent) { this.parent = parent; this.untreatedHeaders = new HeaderFields(); @@ -112,7 +116,12 @@ public abstract class DiscFilterResponse { /** * Return the parent HttpResponse */ - public HttpResponse getParentResponse() { return parent; } + public HttpResponse getParentResponse() { + if (parent instanceof HttpResponse) + return (HttpResponse)parent; + throw new UnsupportedOperationException( + "getParentResponse is not supported for " + parent.getClass().getName()); + } public void addCookie(JDiscCookieWrapper cookie) { if(cookie != null) { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java index f78c03f7af7..eaa02680d48 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java @@ -3,7 +3,7 @@ package com.yahoo.jdisc.http.filter; import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.server.jetty.RequestUtils; +import com.yahoo.jdisc.http.servlet.ServletRequest; import java.net.URI; import java.security.Principal; @@ -120,7 +120,7 @@ public class JdiscFilterRequest extends DiscFilterRequest { @Override public List<X509Certificate> getClientCertificateChain() { - return Optional.ofNullable(parent.context().get(RequestUtils.JDISC_REQUEST_X509CERT)) + return Optional.ofNullable(parent.context().get(ServletRequest.JDISC_REQUEST_X509CERT)) .map(X509Certificate[].class::cast) .map(Arrays::asList) .orElse(Collections.emptyList()); diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java index 3b035987c8a..ee2e1be3ebf 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java @@ -1,12 +1,15 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.filter; +import com.yahoo.jdisc.HeaderFields; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.Cookie; import com.yahoo.jdisc.http.CookieHelper; import com.yahoo.jdisc.http.HttpResponse; +import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpResponse; import java.util.List; +import java.util.Map; /** * JDisc implementation of a filter request. @@ -16,7 +19,8 @@ class JdiscFilterResponse extends DiscFilterResponse { private final Response parent; JdiscFilterResponse(Response parent) { - super((HttpResponse)parent); + // A separate adapter is required as DiscFilterResponse will invoke methods from ServletOrJdiscHttpResponse parameter in its constructor + super(parent instanceof HttpResponse ? (HttpResponse)parent : new Adapter(parent)); this.parent = parent; } @@ -64,4 +68,17 @@ class JdiscFilterResponse extends DiscFilterResponse { CookieHelper.encodeSetCookieHeader(parent.headers(), cookies); } + private static class Adapter implements ServletOrJdiscHttpResponse { + private final Response response; + + Adapter(Response response) { + this.response = response; + } + + @Override public void copyHeaders(HeaderFields target) { target.addAll(response.headers()); } + @Override public int getStatus() { return response.getStatus(); } + @Override public Map<String, Object> context() { return response.context(); } + @Override public List<Cookie> decodeSetCookieHeader() { return CookieHelper.decodeSetCookieHeader(response.headers()); } + } + } diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java new file mode 100644 index 00000000000..a0b9ec935cb --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java @@ -0,0 +1,108 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter; + +import com.google.common.annotations.Beta; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest.Method; +import com.yahoo.jdisc.http.servlet.ServletRequest; + +import com.yahoo.jdisc.http.servlet.ServletResponse; +import com.yahoo.jdisc.http.server.jetty.FilterInvoker; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Only intended for internal vespa use. + * + * Runs JDisc security filter without using JDisc request/response. + * Only intended to be used in a servlet context, as the error messages are tailored for that. + * + * Assumes that SecurityResponseFilters mutate DiscFilterResponse in the thread they are invoked from. + * + * @author Tony Vaagenes + */ +@Beta +public class SecurityFilterInvoker implements FilterInvoker { + + /** + * Returns the servlet request to be used in any servlets invoked after this. + */ + @Override + public HttpServletRequest invokeRequestFilterChain(RequestFilter requestFilterChain, + URI uri, HttpServletRequest httpRequest, + ResponseHandler responseHandler) { + + SecurityRequestFilterChain securityChain = cast(SecurityRequestFilterChain.class, requestFilterChain). + orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException); + + ServletRequest wrappedRequest = new ServletRequest(httpRequest, uri); + securityChain.filter(new ServletFilterRequest(wrappedRequest), responseHandler); + return wrappedRequest; + } + + @Override + public void invokeResponseFilterChain( + ResponseFilter responseFilterChain, + URI uri, + HttpServletRequest request, + HttpServletResponse response) { + + SecurityResponseFilterChain securityChain = cast(SecurityResponseFilterChain.class, responseFilterChain). + orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException); + + ServletFilterResponse wrappedResponse = new ServletFilterResponse(new ServletResponse(response)); + securityChain.filter(new ServletRequestView(uri, request), wrappedResponse); + } + + private static UnsupportedOperationException newUnsupportedOperationException() { + return new UnsupportedOperationException( + "Filter type not supported. If a request is handled by servlets or jax-rs, then any filters invoked for that request must be security filters."); + } + + private <T> Optional<T> cast(Class<T> securityFilterChainClass, Object filter) { + return (securityFilterChainClass.isInstance(filter))? + Optional.of(securityFilterChainClass.cast(filter)): + Optional.empty(); + } + + private static class ServletRequestView implements RequestView { + private final HttpServletRequest request; + private final URI uri; + + public ServletRequestView(URI uri, HttpServletRequest request) { + this.request = request; + this.uri = uri; + } + + @Override + public Object getAttribute(String name) { + return request.getAttribute(name); + } + + @Override + public List<String> getHeaders(String name) { + return Collections.unmodifiableList(Collections.list(request.getHeaders(name))); + } + + @Override + public Optional<String> getFirstHeader(String name) { + return getHeaders(name).stream().findFirst(); + } + + @Override + public Optional<Method> getMethod() { + return Optional.of(Method.valueOf(request.getMethod())); + } + + @Override + public URI getUri() { + return uri; + } + } + +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java new file mode 100644 index 00000000000..c27c0e56d30 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java @@ -0,0 +1,170 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter; + +import com.yahoo.jdisc.http.HttpHeaders; +import com.yahoo.jdisc.http.servlet.ServletRequest; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Servlet implementation for JDisc filter requests. + */ +class ServletFilterRequest extends DiscFilterRequest { + + private final ServletRequest parent; + + public ServletFilterRequest(ServletRequest parent) { + super(parent); + this.parent = parent; + } + + ServletRequest getServletRequest() { + return parent; + } + + @Deprecated + public void setUri(URI uri) { + parent.setUri(uri); + } + + @Override + public String getMethod() { + return parent.getRequest().getMethod(); + } + + @Override + public void setRemoteAddr(String remoteIpAddress) { + throw new UnsupportedOperationException( + "Setting remote address is not supported for " + this.getClass().getName()); + } + + @Override + public Enumeration<String> getAttributeNames() { + Set<String> names = new HashSet<>(Collections.list(super.getAttributeNames())); + names.addAll(Collections.list(parent.getRequest().getAttributeNames())); + return Collections.enumeration(names); + } + + @Override + public Object getAttribute(String name) { + Object jdiscAttribute = super.getAttribute(name); + return jdiscAttribute != null ? + jdiscAttribute : + parent.getRequest().getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + super.setAttribute(name, value); + parent.getRequest().setAttribute(name, value); + } + + @Override + public boolean containsAttribute(String name) { + return super.containsAttribute(name) + || parent.getRequest().getAttribute(name) != null; + } + + @Override + public void removeAttribute(String name) { + super.removeAttribute(name); + parent.getRequest().removeAttribute(name); + } + + @Override + public String getParameter(String name) { + return parent.getParameter(name); + } + + @Override + public Enumeration<String> getParameterNames() { + return parent.getParameterNames(); + } + + @Override + public void addHeader(String name, String value) { + parent.addHeader(name, value); + } + + @Override + public String getHeader(String name) { + return parent.getHeader(name); + } + + @Override + public Enumeration<String> getHeaderNames() { + return parent.getHeaderNames(); + } + + public List<String> getHeaderNamesAsList() { + return Collections.list(getHeaderNames()); + } + + @Override + public Enumeration<String> getHeaders(String name) { + return parent.getHeaders(name); + } + + @Override + public List<String> getHeadersAsList(String name) { + return Collections.list(getHeaders(name)); + } + + @Override + public void setHeaders(String name, String value) { + parent.setHeaders(name, value); + } + + @Override + public void setHeaders(String name, List<String> values) { + parent.setHeaders(name, values); + } + + @Override + public Principal getUserPrincipal() { + return parent.getUserPrincipal(); + } + + @Override + public void setUserPrincipal(Principal principal) { + parent.setUserPrincipal(principal); + } + + @Override + public List<X509Certificate> getClientCertificateChain() { + return Optional.ofNullable(parent.getRequest().getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT)) + .map(X509Certificate[].class::cast) + .map(Arrays::asList) + .orElse(Collections.emptyList()); + } + + @Override + public void removeHeaders(String name) { + parent.removeHeaders(name); + } + + @Override + public void clearCookies() { + parent.removeHeaders(HttpHeaders.Names.COOKIE); + } + + @Override + public void setCharacterEncoding(String encoding) { + super.setCharacterEncoding(encoding); + try { + parent.setCharacterEncoding(encoding); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Encoding not supported: " + encoding, e); + } + } +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java new file mode 100644 index 00000000000..b706e5a7ec6 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java @@ -0,0 +1,81 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter; + +import com.google.common.collect.Iterables; +import com.yahoo.jdisc.http.Cookie; +import com.yahoo.jdisc.http.HttpHeaders; +import com.yahoo.jdisc.http.servlet.ServletResponse; + +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.List; + +/** + * Servlet implementation for JDisc filter responses. + */ +class ServletFilterResponse extends DiscFilterResponse { + + private final ServletResponse parent; + + public ServletFilterResponse(ServletResponse parent) { + super(parent); + this.parent = parent; + } + + ServletResponse getServletResponse() { + return parent; + } + + public void setStatus(int status) { + parent.setStatus(status); + } + + @Override + public void setHeader(String name, String value) { + parent.setHeader(name, value); + } + + @Override + public void removeHeaders(String name) { + HttpServletResponse parentResponse = parent.getResponse(); + if (parentResponse instanceof org.eclipse.jetty.server.Response) { + org.eclipse.jetty.server.Response jettyResponse = (org.eclipse.jetty.server.Response)parentResponse; + jettyResponse.getHttpFields().remove(name); + } else { + throw new UnsupportedOperationException( + "Cannot remove headers for response of type " + parentResponse.getClass().getName()); + } + } + + // Why have a setHeaders that takes a single string? + @Override + public void setHeaders(String name, String value) { + parent.setHeader(name, value); + } + + @Override + public void setHeaders(String name, List<String> values) { + for (String value : values) + parent.addHeader(name, value); + } + + @Override + public void addHeader(String name, String value) { + parent.addHeader(name, value); + } + + @Override + public String getHeader(String name) { + Collection<String> headers = parent.getHeaders(name); + return headers.isEmpty() + ? null + : Iterables.getLast(headers); + } + + @Override + public void setCookies(List<Cookie> cookies) { + removeHeaders(HttpHeaders.Names.SET_COOKIE); + List<String> setCookieHeaders = Cookie.toSetCookieHeaders(cookies); + setCookieHeaders.forEach(cookie -> addHeader(HttpHeaders.Names.SET_COOKIE, cookie)); + } +} |