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 92b1afe  KNOX-1191 - Azure AD support for KnoxSSO
92b1afe is described below

commit 92b1afe7d200c16d21f711b4bd1fe2678136ae5e
Author: Sandeep MorĂ© <[email protected]>
AuthorDate: Fri Feb 22 16:00:06 2019 -0500

    KNOX-1191 - Azure AD support for KnoxSSO
    
    KNOX-1191  Azure AD support for KnoxSSO
---
 .../pac4j/filter/Pac4jDispatcherFilter.java        | 51 +++++++++++++---
 .../gateway/pac4j/session/KnoxSessionStore.java    | 69 +++++++++++++++++++---
 2 files changed, 103 insertions(+), 17 deletions(-)

diff --git 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
index c2eeb81..6393672 100644
--- 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
+++ 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
@@ -32,11 +32,14 @@ import org.pac4j.core.client.Client;
 import org.pac4j.core.config.Config;
 import org.pac4j.core.context.session.J2ESessionStore;
 import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
 import org.pac4j.core.util.CommonHelper;
 import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
 import 
org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
 import org.pac4j.j2e.filter.CallbackFilter;
 import org.pac4j.j2e.filter.SecurityFilter;
+import org.pac4j.oidc.client.AzureAdClient;
+import org.pac4j.saml.client.SAML2Client;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -75,12 +78,20 @@ public class Pac4jDispatcherFilter implements Filter {
 
   public static final String PAC4J_CALLBACK_PARAMETER = "pac4jCallback";
 
+  public static final String PAC4J_OICD_TYPE_AZURE = "azure";
+
+  public static final String URL_PATH_SEPARATOR = "/";
+
   private static final String PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM = 
"pac4j.cookie.domain.suffix";
 
   private static final String PAC4J_CONFIG = "pac4j.config";
 
   private static final String PAC4J_SESSION_STORE = "pac4j.session.store";
 
+  private static final String PAC4J_CLIENT_NAME_PARAM = "clientName";
+
+  private static final String PAC4J_OIDC_TYPE = "oidc.type";
+
   private CallbackFilter callbackFilter;
 
   private SecurityFilter securityFilter;
@@ -122,17 +133,30 @@ public class Pac4jDispatcherFilter implements Filter {
       log.ssoAuthenticationProviderUrlRequired();
       throw new ServletException("Required pac4j callback URL is missing.");
     }
-    // add the callback parameter to know it's a callback
-    pac4jCallbackUrl = CommonHelper.addParameter(pac4jCallbackUrl, 
PAC4J_CALLBACK_PARAMETER, "true");
 
-    final Config config;
-    final String clientName;
     // client name from servlet parameter (mandatory)
-    final String clientNameParameter = 
filterConfig.getInitParameter("clientName");
+    final String clientNameParameter = 
filterConfig.getInitParameter(PAC4J_CLIENT_NAME_PARAM);
     if (clientNameParameter == null) {
       log.clientNameParameterRequired();
       throw new ServletException("Required pac4j clientName parameter is 
missing.");
     }
+
+    final String oidcType = filterConfig.getInitParameter(PAC4J_OIDC_TYPE);
+    /*
+       add the callback parameter to know it's a callback,
+       Azure AD does not honor query param so we add callback param as path 
element.
+    */
+    if (AzureAdClient.class.getSimpleName().equals(clientNameParameter) || (
+        !StringUtils.isBlank(oidcType) && PAC4J_OICD_TYPE_AZURE
+            .equals(oidcType))) {
+      pac4jCallbackUrl = pac4jCallbackUrl + URL_PATH_SEPARATOR + 
PAC4J_CALLBACK_PARAMETER;
+    } else {
+      pac4jCallbackUrl = CommonHelper.addParameter(pac4jCallbackUrl, 
PAC4J_CALLBACK_PARAMETER, "true");
+    }
+
+    final Config config;
+    final String clientName;
+
     if (TEST_BASIC_AUTH.equalsIgnoreCase(clientNameParameter)) {
       // test configuration
       final IndirectBasicAuthClient indirectBasicAuthClient = new 
IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
@@ -160,8 +184,17 @@ public class Pac4jDispatcherFilter implements Filter {
       } else {
         clientName = clientNameParameter;
       }
+
+      /* special handling for Azure AD, use path separators instead of query 
params */
+      clients.forEach( client -> {
+        
if(client.getName().equalsIgnoreCase(AzureAdClient.class.getSimpleName())) {
+          ((AzureAdClient)client).setCallbackUrlResolver(new 
PathParameterCallbackUrlResolver());
+        }
+      });
+
     }
 
+
     callbackFilter = new CallbackFilter();
     callbackFilter.init(filterConfig);
     callbackFilter.setConfigOnly(config);
@@ -186,13 +219,12 @@ public class Pac4jDispatcherFilter implements Filter {
 
   private void addDefaultConfig(String clientNameParameter, Map<String, 
String> properties) {
     // add default saml params
-    if (clientNameParameter.contains("SAML2Client")) {
+    if (clientNameParameter.contains(SAML2Client.class.getSimpleName())) {
       properties.put(PropertiesConfigFactory.SAML_KEYSTORE_PATH,
           keystoreService.getKeystorePath());
 
       properties.put(PropertiesConfigFactory.SAML_KEYSTORE_PASSWORD,
           new String(masterService.getMasterSecret()));
-
       // check for provisioned alias for private key
       char[] gip = null;
       try {
@@ -218,10 +250,11 @@ public class Pac4jDispatcherFilter implements Filter {
 
     final HttpServletRequest request = (HttpServletRequest) servletRequest;
     request.setAttribute(PAC4J_CONFIG, securityFilter.getConfig());
-//    final J2EContext context = new J2EContext(request, response, 
securityFilter.getConfig().getSessionStore());
 
     // it's a callback from an identity provider
-    if (request.getParameter(PAC4J_CALLBACK_PARAMETER) != null) {
+    if (request.getParameter(PAC4J_CALLBACK_PARAMETER) != null || (
+        request.getContextPath() != null && request.getRequestURI()
+            .contains(PAC4J_CALLBACK_PARAMETER))) {
       // apply CallbackFilter
       callbackFilter.doFilter(servletRequest, servletResponse, filterChain);
     } else {
diff --git 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
index bce82ac..3a7f6dd 100644
--- 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
+++ 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
@@ -19,14 +19,18 @@ package org.apache.knox.gateway.pac4j.session;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.IOUtils;
+import org.apache.knox.gateway.pac4j.filter.Pac4jDispatcherFilter;
 import org.apache.knox.gateway.services.security.CryptoService;
 import org.apache.knox.gateway.services.security.EncryptionResult;
 import org.apache.knox.gateway.util.Urls;
 import org.pac4j.core.context.ContextHelper;
 import org.pac4j.core.context.Cookie;
+import org.pac4j.core.context.J2EContext;
+import org.pac4j.core.context.Pac4jConstants;
 import org.pac4j.core.context.WebContext;
 import org.pac4j.core.context.session.SessionStore;
 import org.pac4j.core.exception.TechnicalException;
+import org.pac4j.core.profile.CommonProfile;
 import org.pac4j.core.util.JavaSerializationHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,6 +72,7 @@ public class KnoxSessionStore implements SessionStore {
         this.domainSuffix = domainSuffix;
     }
 
+
     @Override
     public String getOrCreateSessionId(WebContext context) {
         return null;
@@ -78,10 +83,10 @@ public class KnoxSessionStore implements SessionStore {
             byte[] bytes = Base64.decodeBase64(v);
             EncryptionResult result = EncryptionResult.fromByteArray(bytes);
             byte[] clear = cryptoService.decryptForCluster(this.clusterName,
-                    PAC4J_PASSWORD,
-                    result.cipher,
-                    result.iv,
-                    result.salt);
+                PAC4J_PASSWORD,
+                result.cipher,
+                result.iv,
+                result.salt);
             if (clear != null) {
                 try {
                     return 
javaSerializationHelper.unserializeFromBytes(unCompress(clear));
@@ -130,10 +135,23 @@ public class KnoxSessionStore implements SessionStore {
 
     @Override
     public void set(WebContext context, String key, Object value) {
-        logger.debug("Save in session: {} = {}", key, value);
-        final Cookie cookie = new Cookie(PAC4J_SESSION_PREFIX + key, 
compressEncryptBase64(value));
+        Object profile = value;
+        Cookie cookie;
+
+        if (value == null) {
+            cookie = new Cookie(PAC4J_SESSION_PREFIX + key, null);
+        } else {
+            if (key.contentEquals(Pac4jConstants.USER_PROFILES)) {
+                /* trim the profile object */
+                profile = clearUserProfile(value);
+            }
+            logger.debug("Save in session: {} = {}", key, profile);
+            cookie = new Cookie(PAC4J_SESSION_PREFIX + key,
+                compressEncryptBase64(profile));
+        }
         try {
-            String domain = Urls.getDomainName(context.getFullRequestURL(), 
this.domainSuffix);
+            String domain = Urls
+                .getDomainName(context.getFullRequestURL(), this.domainSuffix);
             if (domain == null) {
                 domain = context.getServerName();
             }
@@ -143,6 +161,22 @@ public class KnoxSessionStore implements SessionStore {
         }
         cookie.setHttpOnly(true);
         cookie.setSecure(ContextHelper.isHttpsOrSecure(context));
+
+        /**
+         *  set the correct path for setting pac4j profile cookie.
+         *  This is because, Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER in 
the path
+         *  indicates callback when ? cannot be used.
+         */
+        if (context.getPath() != null && context.getPath()
+            .contains(Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER)) {
+
+            final String[] parts = ((J2EContext) 
context).getRequest().getRequestURI()
+                .split(
+                    "websso"+ Pac4jDispatcherFilter.URL_PATH_SEPARATOR + 
Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER);
+
+            cookie.setPath(parts[0]);
+
+        }
         context.addResponseCookie(cookie);
     }
 
@@ -155,7 +189,7 @@ public class KnoxSessionStore implements SessionStore {
     private static byte[] compress(final byte[] data) throws IOException {
         try (ByteArrayOutputStream byteStream = new 
ByteArrayOutputStream(data.length)) {
             try(GZIPOutputStream gzip = new GZIPOutputStream(byteStream)) {
-              gzip.write(data);
+                gzip.write(data);
             }
             return byteStream.toByteArray();
         }
@@ -176,6 +210,25 @@ public class KnoxSessionStore implements SessionStore {
         }
     }
 
+    /**
+     * Keep only the fileds that are needed for Pac4J.
+     * Used to reduce the cookie size.
+     * @param value profile object
+     * @return trimmed profile object
+     * @since 1.3.0
+     */
+    private Object clearUserProfile(final Object value) {
+        if(value instanceof Map<?,?>) {
+            final Map<String, CommonProfile> profiles = (Map<String, 
CommonProfile>) value;
+            profiles.forEach((name, profile) -> profile.clearSensitiveData());
+            return profiles;
+        } else {
+            final CommonProfile profile = (CommonProfile) value;
+            profile.clearSensitiveData();
+            return profile;
+        }
+    }
+
     @Override
     public SessionStore buildFromTrackableSession(WebContext arg0, Object 
arg1) {
         return null;

Reply via email to