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";
 
 }

Reply via email to