KNOX-2393 - Add a configurable list of paths  that SSOCookieProvider can ignore (#349)

diff --git a/gateway-provider-security-jwt/pom.xml b/gateway-provider-security-jwt/pom.xml
index f478601..e322e1c 100644
--- a/gateway-provider-security-jwt/pom.xml
+++ b/gateway-provider-security-jwt/pom.xml
@@ -72,6 +72,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.apache.knox</groupId>
             <artifactId>gateway-test-utils</artifactId>
             <scope>test</scope>
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
index 42baa30..01136a5 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
@@ -55,4 +55,7 @@
 
   @Message( level = MessageLevel.DEBUG, text = "Audience claim has been validated." )
   void jwtAudienceValidated();
+
+  @Message( level = MessageLevel.INFO, text = "Path {0} is configured as unauthenticated path, letting the request {1} through" )
+  void unauthenticatedPathBypass(String path, String uri);
 }
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
index dcecf4d..411dc67 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
@@ -17,6 +17,7 @@
  */
 package org.apache.knox.gateway.provider.federation.jwt.filter;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
@@ -39,7 +40,10 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
 
 public class SSOCookieFederationFilter extends AbstractJWTFilter {
   private static final JWTMessages LOGGER = MessagesFactory.get( JWTMessages.class );
@@ -59,9 +63,13 @@
   private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
   private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt";
 
+  /* A semicolon separated list of paths that need to bypass authentication */
+  private static final String SSO_UNAUTHENTICATED_PATHS_PARAM = "gateway.knox.sso.unauthenticated.path.list";
+  private static final String DEFAULT_SSO_UNAUTHENTICATED_PATHS_PARAM = "favicon.ico";
   private String cookieName;
   private String authenticationProviderUrl;
   private String gatewayPath;
+  private Set<String> unAuthenticatedPaths = new HashSet(20);
 
   @Override
   public void init( FilterConfig filterConfig ) throws ServletException {
@@ -92,6 +100,18 @@
       publicKey = CertificateUtils.parseRSAPublicKey(verificationPEM);
     }
 
+    /* get unauthenticated paths list */
+    String unAuthPathString = filterConfig.getInitParameter(SSO_UNAUTHENTICATED_PATHS_PARAM);
+    /* if no list specified use default value */
+    if (StringUtils.isBlank(unAuthPathString)) {
+      unAuthPathString = DEFAULT_SSO_UNAUTHENTICATED_PATHS_PARAM;
+    }
+
+    final StringTokenizer st = new StringTokenizer(unAuthPathString, ";,");
+    while (st.hasMoreTokens()) {
+      unAuthenticatedPaths.add(st.nextToken());
+    }
+
     // gateway path for deriving an idp url when missing
     setGatewayPath(filterConfig);
 
@@ -121,7 +141,18 @@
 
     List<Cookie> ssoCookies = CookieUtils.getCookiesForName(req, cookieName);
     if (ssoCookies.isEmpty()) {
-      if (req.getMethod().equals("OPTIONS")) {
+      /* check for unauthenticated paths to bypass */
+      for (final String path : unAuthenticatedPaths) {
+        if (req.getRequestURI().contains(path)) {
+          /* This path is configured as an unauthenticated path let the request through */
+          final Subject sub = new Subject();
+          sub.getPrincipals().add(new PrimaryPrincipal("anonymous"));
+          LOGGER.unauthenticatedPathBypass(path, req.getRequestURI());
+          continueWithEstablishedSecurityContext(sub, req, res, chain);
+        }
+      }
+
+      if ("OPTIONS".equals(req.getMethod())) {
         // CORS preflight requests to determine allowed origins and related config
         // must be able to continue without being redirected
         Subject sub = new Subject();