You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2019/10/03 15:15:15 UTC
[knox] branch master updated: KNOX-2015 - Allow end-users to
exclude only certain directives of the SET-COOKIE HTTP header (#154)
This is an automated email from the ASF dual-hosted git repository.
more pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new b98e77e KNOX-2015 - Allow end-users to exclude only certain directives of the SET-COOKIE HTTP header (#154)
b98e77e is described below
commit b98e77ee906dc9c6326b7d8703870faf523694ae
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Thu Oct 3 17:15:10 2019 +0200
KNOX-2015 - Allow end-users to exclude only certain directives of the SET-COOKIE HTTP header (#154)
---
.../apache/knox/gateway/storm/StormDispatch.java | 3 +-
.../gateway/dispatch/ConfigurableDispatch.java | 47 +++++++++++++++-----
.../knox/gateway/dispatch/DefaultDispatch.java | 51 ++++++++++++++++------
.../gateway/dispatch/ConfigurableDispatchTest.java | 29 ++++++++++--
4 files changed, 102 insertions(+), 28 deletions(-)
diff --git a/gateway-service-storm/src/main/java/org/apache/knox/gateway/storm/StormDispatch.java b/gateway-service-storm/src/main/java/org/apache/knox/gateway/storm/StormDispatch.java
index e09f6e2..1eb2fbb 100644
--- a/gateway-service-storm/src/main/java/org/apache/knox/gateway/storm/StormDispatch.java
+++ b/gateway-service-storm/src/main/java/org/apache/knox/gateway/storm/StormDispatch.java
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.storm;
import org.apache.knox.gateway.dispatch.DefaultDispatch;
+import java.util.Collections;
import java.util.Set;
/**
@@ -29,7 +30,7 @@ public class StormDispatch extends DefaultDispatch {
@Override
public Set<String> getOutboundResponseExcludeHeaders() {
- return null;
+ return Collections.emptySet();
}
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/ConfigurableDispatch.java b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/ConfigurableDispatch.java
index 1611307..09c02da 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/ConfigurableDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/ConfigurableDispatch.java
@@ -27,7 +27,9 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Extends DefaultDispatch to:
@@ -37,29 +39,49 @@ import java.util.Set;
public class ConfigurableDispatch extends DefaultDispatch {
private Set<String> requestExcludeHeaders = super.getOutboundRequestExcludeHeaders();
private Set<String> responseExcludeHeaders = super.getOutboundResponseExcludeHeaders();
+ private Set<String> responseExcludeSetCookieHeaderDirectives = super.getOutboundResponseExcludedSetCookieHeaderDirectives();
private Boolean removeUrlEncoding = false;
- private Set<String> handleCommaSeparatedHeaders(String headers) {
- if(headers != null) {
- return new HashSet<>(Arrays.asList(headers.split(",")));
- }
- return Collections.emptySet();
+ private Set<String> convertCommaDelimitedHeadersToSet(String headers) {
+ return headers == null ? Collections.emptySet(): new HashSet<>(Arrays.asList(headers.split(",")));
}
@Configure
protected void setRequestExcludeHeaders(@Default(" ") String headers) {
if(!" ".equals(headers)) {
- this.requestExcludeHeaders = handleCommaSeparatedHeaders(headers);
+ this.requestExcludeHeaders = convertCommaDelimitedHeadersToSet(headers);
}
}
@Configure
protected void setResponseExcludeHeaders(@Default(" ") String headers) {
- if(!" ".equals(headers)) {
- this.responseExcludeHeaders = handleCommaSeparatedHeaders(headers);
+ if (!" ".equals(headers)) {
+ final Set<String> headerSet = convertCommaDelimitedHeadersToSet(headers);
+ populateSetCookieHeaderDirectiveExlusions(headerSet);
+ populateHttpHeaderExlusionsOtherThanSetCookie(headerSet);
+ }
+ }
+
+ private void populateSetCookieHeaderDirectiveExlusions(final Set<String> headerSet) {
+ final Optional<String> setCookieHeader = headerSet.stream().filter(s -> s.startsWith(SET_COOKIE)).findFirst();
+ if (setCookieHeader.isPresent()) {
+ final String[] setCookieHeaderParts = setCookieHeader.get().split(":");
+ responseExcludeSetCookieHeaderDirectives = setCookieHeaderParts.length > 1
+ ? new HashSet<>(Arrays.asList(setCookieHeaderParts[1].split(";"))).stream().map(e -> e.trim()).collect(Collectors.toSet())
+ : Collections.emptySet();
+ } else {
+ responseExcludeSetCookieHeaderDirectives = Collections.emptySet();
+ }
+ }
+
+ private void populateHttpHeaderExlusionsOtherThanSetCookie(final Set<String> headerSet) {
+ final Set<String> excludedHeadersOthenThanSetCookie = headerSet.stream().filter(s -> !s.startsWith(SET_COOKIE)).collect(Collectors.toSet());
+ if (!excludedHeadersOthenThanSetCookie.isEmpty()) {
+ this.responseExcludeHeaders = excludedHeadersOthenThanSetCookie;
}
}
+
@Configure
protected void setRemoveUrlEncoding(@Default("false") String removeUrlEncoding) {
this.removeUrlEncoding = Boolean.parseBoolean(removeUrlEncoding);
@@ -67,12 +89,17 @@ public class ConfigurableDispatch extends DefaultDispatch {
@Override
public Set<String> getOutboundResponseExcludeHeaders() {
- return responseExcludeHeaders;
+ return responseExcludeHeaders == null ? Collections.emptySet() : responseExcludeHeaders;
+ }
+
+ @Override
+ public Set<String> getOutboundResponseExcludedSetCookieHeaderDirectives() {
+ return responseExcludeSetCookieHeaderDirectives == null ? Collections.emptySet() : responseExcludeSetCookieHeaderDirectives;
}
@Override
public Set<String> getOutboundRequestExcludeHeaders() {
- return requestExcludeHeaders;
+ return requestExcludeHeaders == null ? Collections.emptySet() : requestExcludeHeaders;
}
public boolean getRemoveUrlEncoding() {
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
index 77d38a1..0f5d1f3 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
@@ -52,9 +52,12 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
public class DefaultDispatch extends AbstractGatewayDispatch {
@@ -66,7 +69,9 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
protected static final Auditor auditor = AuditServiceFactory.getAuditService().getAuditor(AuditConstants.DEFAULT_AUDITOR_NAME,
AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME);
- private Set<String> outboundResponseExcludeHeaders = new HashSet<>(Arrays.asList(SET_COOKIE, WWW_AUTHENTICATE));
+ protected static final String EXCLUDE_ALL = "*";
+ private Set<String> outboundResponseExcludeHeaders = new HashSet<>(Arrays.asList(WWW_AUTHENTICATE));
+ private Set<String> outboundResponseExcludedSetCookieHeaderDirectives = new HashSet<>(Arrays.asList(EXCLUDE_ALL));
//Buffer size in bytes
private int replayBufferSize = -1;
@@ -310,23 +315,43 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
}
public void copyResponseHeaderFields(HttpServletResponse outboundResponse, HttpResponse inboundResponse) {
- Header[] headers = inboundResponse.getAllHeaders();
- Set<String> excludeHeaders = getOutboundResponseExcludeHeaders();
- boolean hasExcludeHeaders = false;
- if ((excludeHeaders != null) && !(excludeHeaders.isEmpty())) {
- hasExcludeHeaders = true;
- }
- for ( Header header : headers ) {
- String name = header.getName();
- if (hasExcludeHeaders && excludeHeaders.contains(name.toUpperCase(Locale.ROOT))) {
+ final Map<String, Set<String>> excludedHeaderDirectives = getOutboundResponseExcludeHeaders().stream().collect(Collectors.toMap(k -> k, v -> new HashSet<>(Arrays.asList(EXCLUDE_ALL))));
+ excludedHeaderDirectives.put(SET_COOKIE, getOutboundResponseExcludedSetCookieHeaderDirectives());
+
+ for ( Header header : inboundResponse.getAllHeaders() ) {
+ final String responseHeaderValue = calculateResponseHeaderValue(header, excludedHeaderDirectives);
+ if (responseHeaderValue.isEmpty()) {
continue;
}
- String value = header.getValue();
- outboundResponse.addHeader(name, value);
+ outboundResponse.addHeader(header.getName(), responseHeaderValue);
}
}
+ private String calculateResponseHeaderValue(Header headerToCheck, Map<String, Set<String>> excludedHeaderDirectives) {
+ final String headerNameToCheck = headerToCheck.getName();
+ if (excludedHeaderDirectives != null && excludedHeaderDirectives.containsKey(headerNameToCheck.toUpperCase(Locale.ROOT))) {
+ final Set<String> excludedHeaderValues = excludedHeaderDirectives.get(headerNameToCheck.toUpperCase(Locale.ROOT));
+ if (!excludedHeaderValues.isEmpty()) {
+ if (excludedHeaderValues.stream().filter(e -> e.equals(EXCLUDE_ALL)).findAny().isPresent()) {
+ return ""; // we should exclude all -> there should not be any value added with this header
+ } else {
+ final String separator = SET_COOKIE.equalsIgnoreCase(headerNameToCheck) ? "; " : " ";
+ Set<String> headerValuesToCheck = new HashSet<>(Arrays.asList(headerToCheck.getValue().trim().split("\\s+")));
+ headerValuesToCheck = headerValuesToCheck.stream().map(h -> h.replaceAll(separator.trim(), "")).collect(Collectors.toSet());
+ headerValuesToCheck.removeAll(excludedHeaderValues);
+ return headerValuesToCheck.isEmpty() ? "" : headerValuesToCheck.stream().collect(Collectors.joining(separator));
+ }
+ }
+ }
+
+ return headerToCheck.getValue();
+ }
+
public Set<String> getOutboundResponseExcludeHeaders() {
- return outboundResponseExcludeHeaders;
+ return outboundResponseExcludeHeaders == null ? Collections.emptySet() : outboundResponseExcludeHeaders;
+ }
+
+ public Set<String> getOutboundResponseExcludedSetCookieHeaderDirectives() {
+ return outboundResponseExcludedSetCookieHeaderDirectives == null ? Collections.emptySet() : outboundResponseExcludedSetCookieHeaderDirectives;
}
}
diff --git a/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/ConfigurableDispatchTest.java b/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/ConfigurableDispatchTest.java
index 3e6d2af..07b98a8 100644
--- a/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/ConfigurableDispatchTest.java
+++ b/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/ConfigurableDispatchTest.java
@@ -212,14 +212,15 @@ public class ConfigurableDispatchTest {
assertThat(outboundRequestHeaders[0].getName(), is(HttpHeaders.AUTHORIZATION));
}
- @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ @Test
public void testResponseExcludeHeadersDefault() {
ConfigurableDispatch dispatch = new ConfigurableDispatch();
Header[] headers = new Header[]{
new BasicHeader(SET_COOKIE, "abc"),
new BasicHeader(WWW_AUTHENTICATE, "negotiate"),
- new BasicHeader("TEST", "testValue")
+ new BasicHeader("TEST", "testValue"),
+ new BasicHeader("Accept", "applcation/json")
};
HttpResponse inboundResponse = EasyMock.createNiceMock(HttpResponse.class);
@@ -229,11 +230,12 @@ public class ConfigurableDispatchTest {
HttpServletResponse outboundResponse = new MockHttpServletResponse();
dispatch.copyResponseHeaderFields(outboundResponse, inboundResponse);
- assertThat(outboundResponse.getHeaderNames().size(), is(1));
+ assertThat(outboundResponse.getHeaderNames().size(), is(2));
assertThat(outboundResponse.getHeader("TEST"), is("testValue"));
+ assertThat(outboundResponse.getHeader("Accept"), is("applcation/json"));
}
- @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ @Test
public void testResponseExcludeHeadersConfig() {
ConfigurableDispatch dispatch = new ConfigurableDispatch();
dispatch.setResponseExcludeHeaders(String.join(",", Collections.singletonList("TEST")));
@@ -255,4 +257,23 @@ public class ConfigurableDispatchTest {
assertThat(outboundResponse.getHeader(SET_COOKIE), is("abc"));
assertThat(outboundResponse.getHeader(WWW_AUTHENTICATE), is("negotiate"));
}
+
+ @Test
+ public void shouldExcludeCertainPartsFromSetCookieHeader() throws Exception {
+ final Header[] headers = new Header[] { new BasicHeader(SET_COOKIE, "Domain=localhost; Secure; HttpOnly; Max-Age=1") };
+ final HttpResponse inboundResponse = EasyMock.createNiceMock(HttpResponse.class);
+ EasyMock.expect(inboundResponse.getAllHeaders()).andReturn(headers).anyTimes();
+ EasyMock.replay(inboundResponse);
+
+ final ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ final String setCookieExludeHeaders = SET_COOKIE + ": Domain=localhost; HttpOnly";
+ dispatch.setResponseExcludeHeaders(setCookieExludeHeaders);
+
+ final HttpServletResponse outboundResponse = new MockHttpServletResponse();
+ dispatch.copyResponseHeaderFields(outboundResponse, inboundResponse);
+
+ assertThat(outboundResponse.getHeaderNames().size(), is(1));
+ assertThat(outboundResponse.getHeader(SET_COOKIE), is("Secure; Max-Age=1"));
+ }
+
}