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;