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 b55e9ce KNOX-2559: Added functionality to append headers in request
in Config… (#423)
b55e9ce is described below
commit b55e9ce16c46b630bf9295b011cdcfd2b250e5b3
Author: mudit-97 <[email protected]>
AuthorDate: Thu May 13 23:39:23 2021 +0530
KNOX-2559: Added functionality to append headers in request in Config…
(#423)
* KNOX-2559: Added functionality to append headers in request in
ConfigurableDispatch
* Addressed Comments
* Changed Append headers string format
Co-authored-by: mudit.sharma <[email protected]>
---
.../gateway/dispatch/ConfigurableDispatch.java | 53 ++++++-
.../gateway/dispatch/ConfigurableDispatchTest.java | 163 +++++++++++++++++++++
.../knox/gateway/audit/api/ActionOutcome.java | 1 +
3 files changed, 215 insertions(+), 2 deletions(-)
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 4aa7cdb..9bbcde7 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
@@ -17,6 +17,9 @@
*/
package org.apache.knox.gateway.dispatch;
+import org.apache.commons.collections.MapUtils;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.knox.gateway.audit.api.ActionOutcome;
import org.apache.knox.gateway.config.Configure;
import org.apache.knox.gateway.config.Default;
import org.apache.knox.gateway.util.StringUtils;
@@ -26,11 +29,13 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Optional;
+import java.util.Map;
import java.util.Set;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.HashMap;
import java.util.stream.Collectors;
/**
@@ -41,6 +46,7 @@ import java.util.stream.Collectors;
public class ConfigurableDispatch extends DefaultDispatch {
private Set<String> requestExcludeHeaders =
super.getOutboundRequestExcludeHeaders();
private Set<String> responseExcludeHeaders =
super.getOutboundResponseExcludeHeaders();
+ private Map<String, String> requestAppendHeaders = Collections.emptyMap();
private Set<String> responseExcludeSetCookieHeaderDirectives =
super.getOutboundResponseExcludedSetCookieHeaderDirectives();
private Boolean removeUrlEncoding = false;
@@ -48,6 +54,26 @@ public class ConfigurableDispatch extends DefaultDispatch {
return headers == null ? Collections.emptySet(): new
HashSet<>(Arrays.asList(headers.split("\\s*,\\s*")));
}
+ /**
+ * This function converts header string to a Map(String, String)
+ * Header String format: a:b,c:d --> this will translate to Map({a=b,c=d})
+ */
+ private Map<String, String> convertCommaDelimitedHeadersToMap(String
headers) {
+ if(null == headers){
+ return Collections.emptyMap();
+ }
+ try {
+ Map<String, String> extraHeaders = new HashMap<>();
+ Arrays.stream(headers.split("\\s*;\\s*"))
+ .forEach(keyValuePair ->
extraHeaders.putIfAbsent(keyValuePair.split("\\s*:\\s*")[0],
keyValuePair.split("\\s*:\\s*")[1]));
+ return extraHeaders;
+ } catch (Exception e) {
+ auditor.audit("deserialize headers", headers, "header",
+ ActionOutcome.WARNING, "Extra Headers are skipped because of
"+e.getMessage());
+ return Collections.emptyMap();
+ }
+ }
+
@Configure
protected void setRequestExcludeHeaders(@Default(" ") String headers) {
if(!" ".equals(headers)) {
@@ -64,6 +90,13 @@ public class ConfigurableDispatch extends DefaultDispatch {
}
}
+ @Configure
+ protected void setRequestAppendHeaders(@Default(" ") String extraHeaders) {
+ if(!" ".equals(extraHeaders)) {
+ this.requestAppendHeaders =
convertCommaDelimitedHeadersToMap(extraHeaders);
+ }
+ }
+
private void populateSetCookieHeaderDirectiveExlusions(final Set<String>
headerSet) {
final Optional<String> setCookieHeader = headerSet.stream().filter(s ->
StringUtils.startsWithIgnoreCase(s, SET_COOKIE)).findFirst();
if (setCookieHeader.isPresent()) {
@@ -91,6 +124,18 @@ public class ConfigurableDispatch extends DefaultDispatch {
}
@Override
+ public void copyRequestHeaderFields(HttpUriRequest outboundRequest,
+ HttpServletRequest inboundRequest) {
+ super.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ //If there are some headers to be appended, append them
+ Map<String, String> extraHeaders = getOutboundRequestAppendHeaders();
+ if(MapUtils.isNotEmpty(extraHeaders)){
+ extraHeaders.forEach(outboundRequest::addHeader);
+ }
+ }
+
+ @Override
public Set<String> getOutboundResponseExcludeHeaders() {
return responseExcludeHeaders == null ? Collections.emptySet() :
responseExcludeHeaders;
}
@@ -105,6 +150,10 @@ public class ConfigurableDispatch extends DefaultDispatch {
return requestExcludeHeaders == null ? Collections.emptySet() :
requestExcludeHeaders;
}
+ public Map<String, String> getOutboundRequestAppendHeaders() {
+ return requestAppendHeaders == null ? Collections.emptyMap() :
requestAppendHeaders;
+ }
+
public boolean getRemoveUrlEncoding() {
return removeUrlEncoding;
}
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 c518f03..f741347 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
@@ -282,6 +282,169 @@ public class ConfigurableDispatchTest {
assertThat(outboundResponse.getHeader(WWW_AUTHENTICATE), is("negotiate"));
}
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void testRequestAppendHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders("a:b;c:d");
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(4));
+ assertThat(outboundRequestHeaders[0].getName(), is(HttpHeaders.ACCEPT));
+ assertThat(outboundRequestHeaders[1].getName(), is("TEST"));
+ assertThat(outboundRequestHeaders[2].getName(), is("a"));
+ assertThat(outboundRequestHeaders[3].getName(), is("c"));
+ }
+
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void testRequestExcludeAndAppendHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders("a : b ; c : d");
+ dispatch.setRequestExcludeHeaders(String.join(",",
Arrays.asList(HttpHeaders.ACCEPT, "TEST")));
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(3));
+ assertThat(outboundRequestHeaders[0].getName(),
is(HttpHeaders.AUTHORIZATION));
+ assertThat(outboundRequestHeaders[1].getName(), is("a"));
+ assertThat(outboundRequestHeaders[2].getName(), is("c"));
+ }
+
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void testRequestExcludeAndAppendWithDifferentValueHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders("a:b; TEST :xyz=abc,def=ghi");
+ dispatch.setRequestExcludeHeaders(String.join(",",
Arrays.asList(HttpHeaders.ACCEPT, "TEST")));
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(3));
+ assertThat(outboundRequestHeaders[0].getName(),
is(HttpHeaders.AUTHORIZATION));
+ assertThat(outboundRequestHeaders[1].getName(), is("a"));
+ assertThat(outboundRequestHeaders[2].getName(), is("TEST"));
+ assertThat(outboundRequestHeaders[2].getValue(), is("xyz=abc,def=ghi"));
+ }
+
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void testInvalidRequestAppendHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders("a:;b;c:d");
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(2));
+ assertThat(outboundRequestHeaders[0].getName(), is(HttpHeaders.ACCEPT));
+ assertThat(outboundRequestHeaders[1].getName(), is("TEST"));
+ }
+
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void testRequestExcludeAndInvalidAppendHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders(" a :; b ; c : d ");
+ dispatch.setRequestExcludeHeaders(String.join(",",
Arrays.asList(HttpHeaders.ACCEPT, "TEST")));
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(1));
+ assertThat(outboundRequestHeaders[0].getName(),
is(HttpHeaders.AUTHORIZATION));
+ }
+
+ @Test( timeout = TestUtils.SHORT_TIMEOUT )
+ public void
testRequestExcludeAndInvalidAppendWithDifferentValueHeadersConfig() {
+ ConfigurableDispatch dispatch = new ConfigurableDispatch();
+ dispatch.setRequestAppendHeaders("a:b; TEST :xyz=abc;def=ghi");
+ dispatch.setRequestExcludeHeaders(String.join(",",
Arrays.asList(HttpHeaders.ACCEPT, "TEST")));
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put(HttpHeaders.AUTHORIZATION, "Basic ...");
+ headers.put(HttpHeaders.ACCEPT, "abc");
+ headers.put("TEST", "test");
+
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(inboundRequest.getHeaderNames()).andReturn(Collections.enumeration(headers.keySet())).anyTimes();
+ Capture<String> capturedArgument = Capture.newInstance();
+
EasyMock.expect(inboundRequest.getHeader(EasyMock.capture(capturedArgument)))
+ .andAnswer(() ->
headers.get(capturedArgument.getValue())).anyTimes();
+ EasyMock.replay(inboundRequest);
+
+ HttpUriRequest outboundRequest = new HttpGet();
+ dispatch.copyRequestHeaderFields(outboundRequest, inboundRequest);
+
+ Header[] outboundRequestHeaders = outboundRequest.getAllHeaders();
+ assertThat(outboundRequestHeaders.length, is(1));
+ assertThat(outboundRequestHeaders[0].getName(),
is(HttpHeaders.AUTHORIZATION));
+ }
+
@Test
public void shouldExcludeCertainPartsFromSetCookieHeader() throws Exception {
final Header[] headers = new Header[] { new BasicHeader(SET_COOKIE,
"Domain=localhost; Secure; HttpOnly; Max-Age=1") };
diff --git
a/gateway-util-common/src/main/java/org/apache/knox/gateway/audit/api/ActionOutcome.java
b/gateway-util-common/src/main/java/org/apache/knox/gateway/audit/api/ActionOutcome.java
index 8586ee0..8830ec2 100644
---
a/gateway-util-common/src/main/java/org/apache/knox/gateway/audit/api/ActionOutcome.java
+++
b/gateway-util-common/src/main/java/org/apache/knox/gateway/audit/api/ActionOutcome.java
@@ -31,5 +31,6 @@ public abstract class ActionOutcome {
public static final String SUCCESS = "success";
public static final String FAILURE = "failure";
public static final String UNAVAILABLE = "unavailable";
+ public static final String WARNING = "warning";
}