You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2016/01/15 23:06:48 UTC
knox git commit: KNOX-641 - Support CAS / OAuth / OpenID C / SAML protocols using pac4j - (Jérôme Leleu via lmccay)
Repository: knox
Updated Branches:
refs/heads/master 3158bc842 -> 447b8fd7a
KNOX-641 - Support CAS / OAuth / OpenID C / SAML protocols using pac4j - (Jérôme Leleu via lmccay)
Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/447b8fd7
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/447b8fd7
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/447b8fd7
Branch: refs/heads/master
Commit: 447b8fd7a328304c6660391ba8941d1d0e8f03d4
Parents: 3158bc8
Author: Larry McCay <lm...@hortonworks.com>
Authored: Fri Jan 15 17:06:34 2016 -0500
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Fri Jan 15 17:06:34 2016 -0500
----------------------------------------------------------------------
.../federation/SSOCookieProviderTest.java | 17 +-
gateway-provider-security-pac4j/pom.xml | 100 +++++++++++
.../hadoop/gateway/pac4j/Pac4jMessages.java | 43 +++++
.../Pac4jFederationProviderContributor.java | 77 +++++++++
.../pac4j/filter/Pac4jDispatcherFilter.java | 172 +++++++++++++++++++
.../pac4j/filter/Pac4jIdentityAdapter.java | 132 ++++++++++++++
.../gateway/pac4j/session/KnoxSessionStore.java | 120 +++++++++++++
...gateway.deploy.ProviderDeploymentContributor | 19 ++
.../gateway/pac4j/MockHttpServletRequest.java | 88 ++++++++++
.../gateway/pac4j/MockHttpServletResponse.java | 82 +++++++++
.../hadoop/gateway/pac4j/Pac4jProviderTest.java | 149 ++++++++++++++++
gateway-release/pom.xml | 4 +
.../gateway/service/knoxsso/WebSSOResource.java | 54 ++----
.../service/knoxsso/WebSSOResourceTest.java | 33 ----
.../services/security/token/impl/JWTToken.java | 13 +-
.../org/apache/hadoop/gateway/util/Urls.java | 42 +++++
.../apache/hadoop/gateway/util/UrlsTest.java | 57 ++++++
pom.xml | 10 +-
18 files changed, 1120 insertions(+), 92 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
index cfa1b37..c613869 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
@@ -394,19 +394,20 @@ public class SSOCookieProviderTest {
protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey)
throws Exception {
- JWTClaimsSet claimsSet = new JWTClaimsSet();
- claimsSet.setSubject(sub);
- claimsSet.setIssueTime(new Date(new Date().getTime()));
- claimsSet.setIssuer("https://c2id.com");
- claimsSet.setCustomClaim("scope", "openid");
- claimsSet.setExpirationTime(expires);
List<String> aud = new ArrayList<String>();
aud.add("bar");
- claimsSet.setAudience("bar");
+
+ JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .issuer("https://c2id.com")
+ .subject(sub)
+ .audience(aud)
+ .expirationTime(expires)
+ .claim("scope", "openid")
+ .build();
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
- SignedJWT signedJWT = new SignedJWT(header, claimsSet);
+ SignedJWT signedJWT = new SignedJWT(header, claims);
Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput());
JWSSigner signer = new RSASSASigner(privateKey);
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/pom.xml b/gateway-provider-security-pac4j/pom.xml
new file mode 100644
index 0000000..08d3751
--- /dev/null
+++ b/gateway-provider-security-pac4j/pom.xml
@@ -0,0 +1,100 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>gateway-provider-security-pac4j</artifactId>
+
+ <name>gateway-provider-security-pac4j</name>
+ <description>An extension of the gateway integrating pac4j as an authentication provider.</description>
+
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <properties>
+ <j2e-pac4j.version>1.2.1</j2e-pac4j.version>
+ <pac4j.version>1.8.3</pac4j.version>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>${gateway-group}</groupId>
+ <artifactId>gateway-server</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.pac4j</groupId>
+ <artifactId>j2e-pac4j</artifactId>
+ <version>${j2e-pac4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.pac4j</groupId>
+ <artifactId>pac4j-http</artifactId>
+ <version>${pac4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.pac4j</groupId>
+ <artifactId>pac4j-config</artifactId>
+ <version>${pac4j.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xalan</groupId>
+ <artifactId>xalan</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-test-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
new file mode 100644
index 0000000..b3526ae
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/Pac4jMessages.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j;
+
+import org.apache.hadoop.gateway.i18n.messages.Message;
+import org.apache.hadoop.gateway.i18n.messages.MessageLevel;
+import org.apache.hadoop.gateway.i18n.messages.Messages;
+
+/**
+ * Logging messages for the pac4j provider.
+ *
+ * @since 0.8.0
+ */
+@Messages(logger="org.apache.hadoop.gateway.pac4j")
+public interface Pac4jMessages {
+
+ @Message( level = MessageLevel.ERROR, text = "pac4j callback URL required")
+ public void ssoAuthenticationProviderUrlRequired();
+
+ @Message( level = MessageLevel.ERROR, text = "At least one pac4j client must be defined")
+ public void atLeastOnePac4jClientMustBeDefined();
+
+ @Message( level = MessageLevel.ERROR, text = "Crypto service, alias service and cluster name required")
+ public void cryptoServiceAndAliasServiceAndClusterNameRequired();
+
+ @Message( level = MessageLevel.ERROR, text = "Unable to generate a password for encryption")
+ public void unableToGenerateAPasswordForEncryption(Exception e);
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
new file mode 100644
index 0000000..c673ed5
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/deploy/Pac4jFederationProviderContributor.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j.deploy;
+
+import org.apache.hadoop.gateway.deploy.DeploymentContext;
+import org.apache.hadoop.gateway.deploy.ProviderDeploymentContributorBase;
+import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
+import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
+import org.apache.hadoop.gateway.topology.Provider;
+import org.apache.hadoop.gateway.topology.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Descriptor of the pac4j provider. This module must be deployed for the KnoxSSO service.
+ *
+ * @since 0.8.0
+ */
+public class Pac4jFederationProviderContributor extends ProviderDeploymentContributorBase {
+
+ private static final String ROLE = "federation";
+ private static final String NAME = "pac4j";
+ private static final String DISPATCHER_FILTER_CLASSNAME = "org.apache.hadoop.gateway.pac4j.filter.Pac4jDispatcherFilter";
+ private static final String IDENTITY_ADAPTER_CLASSNAME = "org.apache.hadoop.gateway.pac4j.filter.Pac4jIdentityAdapter";
+
+ @Override
+ public String getRole() {
+ return ROLE;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public void initializeContribution(DeploymentContext context) {
+ super.initializeContribution(context);
+ }
+
+ @Override
+ public void contributeProvider(DeploymentContext context, Provider provider) {
+ }
+
+ @Override
+ public void contributeFilter(DeploymentContext context, Provider provider, Service service,
+ ResourceDescriptor resource, List<FilterParamDescriptor> params) {
+ // blindly add all the provider params as filter init params
+ if (params == null) {
+ params = new ArrayList<FilterParamDescriptor>();
+ }
+ Map<String, String> providerParams = provider.getParams();
+ for(Entry<String, String> entry : providerParams.entrySet()) {
+ params.add( resource.createFilterParam().name( entry.getKey() ).value( entry.getValue() ) );
+ }
+ resource.addFilter().name( getName() ).role( getRole() ).impl( DISPATCHER_FILTER_CLASSNAME ).params( params );
+ resource.addFilter().name( getName() ).role( getRole() ).impl( IDENTITY_ADAPTER_CLASSNAME ).params( params );
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
new file mode 100644
index 0000000..a9506cf
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jDispatcherFilter.java
@@ -0,0 +1,172 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j.filter;
+
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.pac4j.Pac4jMessages;
+import org.apache.hadoop.gateway.pac4j.session.KnoxSessionStore;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.gateway.services.security.AliasServiceException;
+import org.apache.hadoop.gateway.services.security.CryptoService;
+import org.pac4j.config.client.PropertiesConfigFactory;
+import org.pac4j.core.client.Client;
+import org.pac4j.core.client.Clients;
+import org.pac4j.core.config.Config;
+import org.pac4j.core.config.ConfigSingleton;
+import org.pac4j.core.context.J2EContext;
+import org.pac4j.core.context.Pac4jConstants;
+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.RequiresAuthenticationFilter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>This is the main filter for the pac4j provider. The pac4j provider module heavily relies on the j2e-pac4j library (https://github.com/pac4j/j2e-pac4j).</p>
+ * <p>This filter dispatches the HTTP calls between the j2e-pac4j filters:</p>
+ * <ul>
+ * <li>to the {@link CallbackFilter} if the <code>client_name</code> parameter exists: it finishes the authentication process</li>
+ * <li>to the {@link RequiresAuthenticationFilter} otherwise: it starts the authentication process (redirection to the identity provider) if the user is not authenticated</li>
+ * </ul>
+ * <p>It uses the {@link KnoxSessionStore} to manage session data. The generated cookies are defined on a domain name
+ * which can be configured via the domain suffix parameter: <code>pac4j.cookie.domain.suffix</code>.</p>
+ * <p>The callback url must be defined to the current protected url (KnoxSSO service for example) via the parameter: <code>pac4j.callbackUrl</code>.</p>
+ *
+ * @since 0.8.0
+ */
+public class Pac4jDispatcherFilter implements Filter {
+
+ private static Pac4jMessages log = MessagesFactory.get(Pac4jMessages.class);
+
+ public static final String TEST_BASIC_AUTH = "testBasicAuth";
+
+ public static final String PAC4J_CALLBACK_URL = "pac4j.callbackUrl";
+
+ private static final String PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM = "pac4j.cookie.domain.suffix";
+
+ private CallbackFilter callbackFilter;
+
+ private RequiresAuthenticationFilter requiresAuthenticationFilter;
+
+ @Override
+ public void init( FilterConfig filterConfig ) throws ServletException {
+ // JWT service
+ final ServletContext context = filterConfig.getServletContext();
+ CryptoService cryptoService = null;
+ AliasService aliasService = null;
+ String clusterName = null;
+ if (context != null) {
+ GatewayServices services = (GatewayServices) context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+ clusterName = (String) context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+ if (services != null) {
+ cryptoService = (CryptoService) services.getService(GatewayServices.CRYPTO_SERVICE);
+ aliasService = (AliasService) services.getService(GatewayServices.ALIAS_SERVICE);
+ }
+ }
+ // crypto service, alias service and cluster name are mandatory
+ if (cryptoService == null || aliasService == null || clusterName == null) {
+ log.cryptoServiceAndAliasServiceAndClusterNameRequired();
+ throw new ServletException("The crypto service, alias service and cluster name are required.");
+ }
+ try {
+ aliasService.getPasswordFromAliasForCluster(clusterName, KnoxSessionStore.PAC4J_PASSWORD, true);
+ } catch (AliasServiceException e) {
+ log.unableToGenerateAPasswordForEncryption(e);
+ throw new ServletException("Unable to generate a password for encryption.");
+ }
+
+ // url to SSO authentication provider
+ final String pac4jCallbackUrl = filterConfig.getInitParameter(PAC4J_CALLBACK_URL);
+ if (pac4jCallbackUrl == null) {
+ log.ssoAuthenticationProviderUrlRequired();
+ throw new ServletException("Required pac4j callback URL is missing.");
+ }
+
+ final Config config;
+ final String clientName;
+ // client name from servlet parameter (if defined)
+ final String clientNameParameter = filterConfig.getInitParameter(Pac4jConstants.CLIENT_NAME);
+ if (TEST_BASIC_AUTH.equalsIgnoreCase(clientNameParameter)) {
+ // test configuration
+ final IndirectBasicAuthClient indirectBasicAuthClient = new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
+ indirectBasicAuthClient.setRealmName("Knox TEST");
+ config = new Config(pac4jCallbackUrl, indirectBasicAuthClient);
+ clientName = "IndirectBasicAuthClient";
+ } else {
+ // get clients from the init parameters
+ final Map<String, String> properties = new HashMap<>();
+ final Enumeration<String> names = filterConfig.getInitParameterNames();
+ while (names.hasMoreElements()) {
+ final String key = names.nextElement();
+ properties.put(key, filterConfig.getInitParameter(key));
+ }
+ final PropertiesConfigFactory propertiesConfigFactory = new PropertiesConfigFactory(pac4jCallbackUrl, properties);
+ config = propertiesConfigFactory.build();
+ final List<Client> clients = config.getClients().getClients();
+ if (clients == null || clients.size() ==0) {
+ log.atLeastOnePac4jClientMustBeDefined();
+ throw new ServletException("At least one pac4j client must be defined.");
+ }
+ if (CommonHelper.isBlank(clientNameParameter)) {
+ clientName = clients.get(0).getName();
+ } else {
+ clientName = clientNameParameter;
+ }
+ }
+
+ callbackFilter = new CallbackFilter();
+ requiresAuthenticationFilter = new RequiresAuthenticationFilter();
+ requiresAuthenticationFilter.setClientName(clientName);
+ requiresAuthenticationFilter.setConfig(config);
+
+ final String domainSuffix = context.getInitParameter(PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM);
+ config.setSessionStore(new KnoxSessionStore(cryptoService, clusterName, domainSuffix));
+ ConfigSingleton.setConfig(config);
+ }
+
+ @Override
+ public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+
+ final HttpServletRequest request = (HttpServletRequest) servletRequest;
+ final HttpServletResponse response = (HttpServletResponse) servletResponse;
+ final J2EContext context = new J2EContext(request, response, ConfigSingleton.getConfig().getSessionStore());
+
+ // it's a callback from an identity provider
+ if (request.getParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER) != null) {
+ // apply CallbackFilter
+ callbackFilter.doFilter(servletRequest, servletResponse, filterChain);
+ } else {
+ // otherwise just apply security and requires authentication
+ // apply RequiresAuthenticationFilter
+ requiresAuthenticationFilter.doFilter(servletRequest, servletResponse, filterChain);
+ }
+ }
+
+ @Override
+ public void destroy() { }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
new file mode 100644
index 0000000..52fad88
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/filter/Pac4jIdentityAdapter.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j.filter;
+
+import org.apache.hadoop.gateway.audit.api.*;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.filter.AbstractGatewayFilter;
+import org.apache.hadoop.gateway.security.PrimaryPrincipal;
+import org.pac4j.core.config.ConfigSingleton;
+import org.pac4j.core.context.J2EContext;
+import org.pac4j.core.profile.ProfileManager;
+import org.pac4j.core.profile.UserProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+/**
+ * <p>This filter retrieves the authenticated user saved by the pac4j provider and injects it into the J2E HTTP request.</p>
+ *
+ * @since 0.8.0
+ */
+public class Pac4jIdentityAdapter implements Filter {
+
+ private final static Logger logger = LoggerFactory.getLogger(Pac4jIdentityAdapter.class);
+
+ private static AuditService auditService = AuditServiceFactory.getAuditService();
+ private static Auditor auditor = auditService.getAuditor(
+ AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
+ AuditConstants.KNOX_COMPONENT_NAME );
+
+ private String testIdentifier;
+
+ @Override
+ public void init( FilterConfig filterConfig ) throws ServletException {
+ }
+
+ public void destroy() {
+ }
+
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
+ throws IOException, ServletException {
+
+ final HttpServletRequest request = (HttpServletRequest) servletRequest;
+ final HttpServletResponse response = (HttpServletResponse) servletResponse;
+ final J2EContext context = new J2EContext(request, response, ConfigSingleton.getConfig().getSessionStore());
+ final ProfileManager manager = new ProfileManager(context);
+ final UserProfile profile = manager.get(true);
+ logger.debug("User authenticated as: {}", profile);
+ manager.remove(true);
+ final String id = profile.getId();
+ testIdentifier = id;
+ PrimaryPrincipal pp = new PrimaryPrincipal(id);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(pp);
+ auditService.getContext().setUsername(id);
+ String sourceUri = (String)request.getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
+ auditor.audit(Action.AUTHENTICATION, sourceUri, ResourceType.URI, ActionOutcome.SUCCESS);
+
+ doAs(request, response, chain, subject);
+ }
+
+ private void doAs(final ServletRequest request,
+ final ServletResponse response, final FilterChain chain, Subject subject)
+ throws IOException, ServletException {
+ try {
+ Subject.doAs(
+ subject,
+ new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ chain.doFilter(request, response);
+ return null;
+ }
+ }
+ );
+ }
+ catch (PrivilegedActionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ else if (t instanceof ServletException) {
+ throw (ServletException) t;
+ }
+ else {
+ throw new ServletException(t);
+ }
+ }
+ }
+
+ /**
+ * For tests only.
+ */
+ public static void setAuditService(AuditService auditService) {
+ Pac4jIdentityAdapter.auditService = auditService;
+ }
+
+ /**
+ * For tests only.
+ */
+ public static void setAuditor(Auditor auditor) {
+ Pac4jIdentityAdapter.auditor = auditor;
+ }
+
+ /**
+ * For tests only.
+ */
+ public String getTestIdentifier() {
+ return testIdentifier;
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
new file mode 100644
index 0000000..24b6b6b
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/java/org/apache/hadoop/gateway/pac4j/session/KnoxSessionStore.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j.session;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.hadoop.gateway.services.security.CryptoService;
+import org.apache.hadoop.gateway.services.security.EncryptionResult;
+import org.apache.hadoop.gateway.util.Urls;
+import org.pac4j.core.context.ContextHelper;
+import org.pac4j.core.context.Cookie;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.exception.TechnicalException;
+import org.pac4j.core.util.JavaSerializationHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+
+/**
+ * Specific session store where data are saved into cookies (and not in memory).
+ * Each data is encrypted and base64 encoded before being saved as a cookie (for security reasons).
+ *
+ * @since 0.8.0
+ */
+public class KnoxSessionStore implements SessionStore {
+
+ private final static Logger logger = LoggerFactory.getLogger(KnoxSessionStore.class);
+
+ public final static String PAC4J_PASSWORD = "pac4j.password";
+
+ public final static String PAC4J_SESSION_PREFIX = "pac4j.session.";
+
+ private final JavaSerializationHelper javaSerializationHelper;
+
+ private final CryptoService cryptoService;
+
+ private final String clusterName;
+
+ private final String domainSuffix;
+
+ public KnoxSessionStore(final CryptoService cryptoService, final String clusterName, final String domainSuffix) {
+ javaSerializationHelper = new JavaSerializationHelper();
+ this.cryptoService = cryptoService;
+ this.clusterName = clusterName;
+ this.domainSuffix = domainSuffix;
+ }
+
+ public String getOrCreateSessionId(WebContext context) {
+ return null;
+ }
+
+ private Serializable decryptBase64(final String v) {
+ if (v != null && v.length() > 0) {
+ byte[] bytes = Base64.decodeBase64(v);
+ EncryptionResult result = EncryptionResult.fromByteArray(bytes);
+ byte[] clear = cryptoService.decryptForCluster(this.clusterName,
+ PAC4J_PASSWORD,
+ result.cipher,
+ result.iv,
+ result.salt);
+ if (clear != null) {
+ return javaSerializationHelper.unserializeFromBytes(clear);
+ }
+ }
+ return null;
+ }
+
+ public Object get(WebContext context, String key) {
+ final Cookie cookie = ContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key);
+ Object value = null;
+ if (cookie != null) {
+ value = decryptBase64(cookie.getValue());
+ }
+ logger.debug("Get from session: {} = {}", key, value);
+ return value;
+ }
+
+ private String encryptBase64(final Object o) {
+ if (o == null) {
+ return null;
+ } else {
+ final byte[] bytes = javaSerializationHelper.serializeToBytes((Serializable) o);
+ EncryptionResult result = cryptoService.encryptForCluster(this.clusterName, PAC4J_PASSWORD, bytes);
+ return Base64.encodeBase64String(result.toByteAray());
+ }
+ }
+
+ 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, encryptBase64(value));
+ try {
+ String domain = Urls.getDomainName(context.getFullRequestURL(), this.domainSuffix);
+ if (domain == null) {
+ domain = context.getServerName();
+ }
+ cookie.setDomain(domain);
+ } catch (final Exception e) {
+ throw new TechnicalException(e);
+ }
+ cookie.setHttpOnly(true);
+ cookie.setSecure(ContextHelper.isHttpsOrSecure(context));
+ context.addResponseCookie(cookie);
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor b/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
new file mode 100644
index 0000000..8cde74f
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
@@ -0,0 +1,19 @@
+##########################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##########################################################################
+
+org.apache.hadoop.gateway.pac4j.deploy.Pac4jFederationProviderContributor
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
new file mode 100644
index 0000000..b09adc9
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletRequest.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j;
+
+import javax.servlet.http.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+public class MockHttpServletRequest extends HttpServletRequestWrapper {
+
+ private String requestUrl;
+ private Cookie[] cookies;
+ private String serverName;
+ private Map<String, String> parameters = new HashMap<>();
+ private Map<String, String> headers = new HashMap<>();
+
+ public MockHttpServletRequest() {
+ super(mock(HttpServletRequest.class));
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return cookies;
+ }
+
+ public void setCookies(final Cookie[] cookies) {
+ this.cookies = cookies;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return new StringBuffer(requestUrl);
+ }
+
+ public void setRequestURL(final String requestUrl) {
+ this.requestUrl = requestUrl;
+ }
+
+ @Override
+ public String getServerName() {
+ return serverName;
+ }
+
+ public void setServerName(final String serverName) {
+ this.serverName = serverName;
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return parameters.get(name);
+ }
+
+ public void addParameter(String key, String value) {
+ parameters.put(key, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ public void addHeader(String key, String value) {
+ headers.put(key, value);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
new file mode 100644
index 0000000..11d104c
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/MockHttpServletResponse.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.gateway.pac4j;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+public class MockHttpServletResponse extends HttpServletResponseWrapper {
+
+ private List<Cookie> cookies = new ArrayList<>();
+ private String location;
+ private int status = 0;
+ private Map<String, String> headers = new HashMap<>();
+
+ public MockHttpServletResponse() {
+ super(mock(HttpServletResponse.class));
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ headers.put(name, value);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ headers.put(name, value);
+ }
+
+ public Map<String, String> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public void addCookie(Cookie cookie) {
+ cookies.add(cookie);
+ }
+
+ public List<Cookie> getCookies() {
+ return cookies;
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException {
+ setStatus(302);
+ setHeader("Location", location);
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ status = sc;
+ }
+
+ @Override
+ public int getStatus() {
+ return status;
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
new file mode 100644
index 0000000..e2cee83
--- /dev/null
+++ b/gateway-provider-security-pac4j/src/test/java/org/apache/hadoop/gateway/pac4j/Pac4jProviderTest.java
@@ -0,0 +1,149 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.pac4j;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.pac4j.filter.Pac4jDispatcherFilter;
+import org.apache.hadoop.gateway.pac4j.filter.Pac4jIdentityAdapter;
+import org.apache.hadoop.gateway.pac4j.session.KnoxSessionStore;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.gateway.services.security.impl.DefaultCryptoService;
+import org.junit.Test;
+import org.pac4j.core.client.Clients;
+import org.pac4j.core.context.Pac4jConstants;
+import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+/**
+ * This class simulates a full authentication process using pac4j.
+ */
+public class Pac4jProviderTest {
+
+ private final static String LOCALHOST = "127.0.0.1";
+ private final static String HADOOP_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/sandox/webhdfs/v1/tmp?op=LISTSTATUS";
+ private final static String KNOXSSO_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/idp/api/v1/websso";
+ private final static String PAC4J_CALLBACK_URL = KNOXSSO_SERVICE_URL;
+ private final static String ORIGINAL_URL = "originalUrl";
+ private final static String CLUSTER_NAME = "knox";
+ private final static String PAC4J_PASSWORD = "pwdfortest";
+ private final static String CLIENT_CLASS = IndirectBasicAuthClient.class.getSimpleName();
+ private final static String USERNAME = "jleleu";
+
+ @Test
+ public void test() throws Exception {
+ final AliasService aliasService = mock(AliasService.class);
+ when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD, true)).thenReturn(PAC4J_PASSWORD.toCharArray());
+ when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD)).thenReturn(PAC4J_PASSWORD.toCharArray());
+
+ final DefaultCryptoService cryptoService = new DefaultCryptoService();
+ cryptoService.setAliasService(aliasService);
+
+ final GatewayServices services = mock(GatewayServices.class);
+ when(services.getService(GatewayServices.CRYPTO_SERVICE)).thenReturn(cryptoService);
+ when(services.getService(GatewayServices.ALIAS_SERVICE)).thenReturn(aliasService);
+
+ final ServletContext context = mock(ServletContext.class);
+ when(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).thenReturn(services);
+ when(context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).thenReturn(CLUSTER_NAME);
+
+ final FilterConfig config = mock(FilterConfig.class);
+ when(config.getServletContext()).thenReturn(context);
+ when(config.getInitParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_URL)).thenReturn(PAC4J_CALLBACK_URL);
+ when(config.getInitParameter(Pac4jConstants.CLIENT_NAME)).thenReturn(Pac4jDispatcherFilter.TEST_BASIC_AUTH);
+
+ final Pac4jDispatcherFilter dispatcher = new Pac4jDispatcherFilter();
+ dispatcher.init(config);
+ final Pac4jIdentityAdapter adapter = new Pac4jIdentityAdapter();
+ adapter.init(config);
+ adapter.setAuditor(mock(Auditor.class));
+ final AuditService auditService = mock(AuditService.class);
+ when(auditService.getContext()).thenReturn(mock(AuditContext.class));
+ adapter.setAuditService(auditService);
+
+ // step 1: call the KnoxSSO service with an original url pointing to an Hadoop service (redirected by the SSOCookieProvider)
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL);
+ request.setCookies(new Cookie[0]);
+ request.setServerName(LOCALHOST);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ FilterChain filterChain = mock(FilterChain.class);
+ dispatcher.doFilter(request, response, filterChain);
+ // it should be a redirection to the identity provider
+ assertEquals(302, response.getStatus());
+ assertEquals(PAC4J_CALLBACK_URL + "?" + Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS, response.getHeaders().get("Location"));
+ // we should have one cookie for the saved requested url
+ List<Cookie> cookies = response.getCookies();
+ assertEquals(1, cookies.size());
+ final Cookie requestedUrlCookie = cookies.get(0);
+ assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.REQUESTED_URL, requestedUrlCookie.getName());
+
+ // step 2: send credentials to the callback url (callback from the identity provider)
+ request = new MockHttpServletRequest();
+ request.setCookies(new Cookie[]{requestedUrlCookie});
+ request.setRequestURL(PAC4J_CALLBACK_URL + "?" + Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS);
+ request.addParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER, CLIENT_CLASS);
+ request.addHeader("Authorization", "Basic amxlbGV1OmpsZWxldQ==");
+ request.setServerName(LOCALHOST);
+ response = new MockHttpServletResponse();
+ filterChain = mock(FilterChain.class);
+ dispatcher.doFilter(request, response, filterChain);
+ // it should be a redirection to the original url
+ assertEquals(302, response.getStatus());
+ assertEquals(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL, response.getHeaders().get("Location"));
+ // we should have 3 cookies among with the user profile
+ cookies = response.getCookies();
+ Map<String, String> mapCookies = new HashMap<>();
+ assertEquals(3, cookies.size());
+ for (final Cookie cookie : cookies) {
+ mapCookies.put(cookie.getName(), cookie.getValue());
+ }
+ assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + CLIENT_CLASS + "$attemptedAuthentication"));
+ assertNotNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.USER_PROFILE));
+ assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.REQUESTED_URL));
+
+ // step 3: turn pac4j identity into KnoxSSO identity
+ request = new MockHttpServletRequest();
+ request.setCookies(cookies.toArray(new Cookie[cookies.size()]));
+ request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL);
+ request.setServerName(LOCALHOST);
+ response = new MockHttpServletResponse();
+ filterChain = mock(FilterChain.class);
+ dispatcher.doFilter(request, response, filterChain);
+ assertEquals(0, response.getStatus());
+ adapter.doFilter(request, response, filterChain);
+ cookies = response.getCookies();
+ assertEquals(1, cookies.size());
+ final Cookie userProfileCookie = cookies.get(0);
+ // the user profile has been cleaned
+ assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.USER_PROFILE, userProfileCookie.getName());
+ assertNull(userProfileCookie.getValue());
+ assertEquals(USERNAME, adapter.getTestIdentifier());
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-release/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-release/pom.xml b/gateway-release/pom.xml
index 1d2a60e..5a07d30 100644
--- a/gateway-release/pom.xml
+++ b/gateway-release/pom.xml
@@ -216,6 +216,10 @@
</dependency>
<dependency>
<groupId>${gateway-group}</groupId>
+ <artifactId>gateway-provider-security-pac4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${gateway-group}</groupId>
<artifactId>gateway-provider-security-webappsec</artifactId>
</dependency>
<dependency>
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
index 6c9bcfc..2b64456 100644
--- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
+++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
@@ -18,8 +18,6 @@
package org.apache.hadoop.gateway.service.knoxsso;
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.security.Principal;
import javax.annotation.PostConstruct;
@@ -57,7 +55,7 @@ public class WebSSOResource {
private static final String ORIGINAL_URL_COOKIE_NAME = "original-url";
private static final String JWT_COOKIE_NAME = "hadoop-jwt";
// default for the whitelist - open up for development - relative paths and localhost only
- private static final String DEFAULT_WHITELIST = "^/.*$;^https?://localhost:\\d{0,9}/.*$";
+ private static final String DEFAULT_WHITELIST = "^/.*$;^https?://(localhost|127.0.0.1|0:0:0:0:0:0:0:1|::1):\\d{0,9}/.*$";
static final String RESOURCE_PATH = "/api/v1/websso";
private static KnoxSSOMessages log = MessagesFactory.get( KnoxSSOMessages.class );
private boolean secureOnly = true;
@@ -66,10 +64,10 @@ public class WebSSOResource {
private String whitelist = null;
private String domainSuffix = null;
- @Context
+ @Context
private HttpServletRequest request;
- @Context
+ @Context
private HttpServletResponse response;
@Context
@@ -129,7 +127,7 @@ public class WebSSOResource {
private Response getAuthenticationToken(int statusCode) {
GatewayServices services = (GatewayServices) request.getServletContext()
- .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+ .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
boolean removeOriginalUrlCookie = true;
String original = getCookieValue((HttpServletRequest) request, ORIGINAL_URL_COOKIE_NAME);
if (original == null) {
@@ -145,7 +143,7 @@ public class WebSSOResource {
if (!validRedirect) {
log.whiteListMatchFail(original, whitelist);
throw new WebApplicationException("Original URL not valid according to the configured whitelist.",
- Response.Status.BAD_REQUEST);
+ Response.Status.BAD_REQUEST);
}
}
@@ -154,13 +152,13 @@ public class WebSSOResource {
try {
JWT token = ts.issueToken(p, "RS256", System.currentTimeMillis() + tokenTTL);
-
+
addJWTHadoopCookie(original, token);
-
+
if (removeOriginalUrlCookie) {
removeOriginalUrlCookie(response);
}
-
+
log.aboutToRedirectToOriginal(original);
response.setStatus(statusCode);
response.setHeader("Location", original);
@@ -181,7 +179,7 @@ public class WebSSOResource {
Cookie c = new Cookie(JWT_COOKIE_NAME, token.toString());
c.setPath("/");
try {
- String domain = getDomainName(original, domainSuffix);
+ String domain = Urls.getDomainName(original, domainSuffix);
if (domain != null) {
c.setDomain(domain);
}
@@ -208,44 +206,12 @@ public class WebSSOResource {
response.addCookie(c);
}
- String getDomainName(String url, String domainSuffix) throws URISyntaxException {
- URI uri = new URI(url);
- String domain = uri.getHost();
-
- // if the hostname ends with the domainSuffix the use the domainSuffix as
- // the cookie domain
- if (domainSuffix != null && domain.endsWith(domainSuffix)) {
- return (domainSuffix.startsWith(".")) ? domainSuffix : "." + domainSuffix;
- }
-
- // if accessing via ip address do not wildcard the cookie domain
- // let's use the default domain
- if (Urls.isIp(domain)) {
- return null;
- }
-
- // if there are fewer than 2 dots than this is likely a
- // specific host and we should use the default domain
- if (Urls.dotOccurrences(domain) < 2) {
- return null;
- }
-
- // assume any non-ip address with more than
- // 3 dots will need the first element removed and
- // all subdmains accepted
- int idx = domain.indexOf('.');
- if (idx == -1) {
- idx = 0;
- }
- return domain.substring(idx);
- }
-
private String getCookieValue(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
String value = null;
for(Cookie cookie : cookies){
if(name.equals(cookie.getName())){
- value = cookie.getValue();
+ value = cookie.getValue();
}
}
if (value == null) {
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
index 20c0566..73910dd 100644
--- a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
+++ b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java
@@ -25,39 +25,6 @@ import org.junit.Test;
*
*/
public class WebSSOResourceTest {
- /**
- * Domain name creation follows the following algorithm:
- * 1. if the incoming request hostname endsWith a configured domain suffix return the suffix - with prefixed dot
- * 2. if the request hostname is an ip address return null for default domain
- * 3. if the request hostname has less than 3 dots return null for default domain
- * 4. if request hostname has more than two dots strip the first element and return the remainder as domain
- * @throws Exception
- */
- @Test
- public void testDomainNameCreation() throws Exception {
- WebSSOResource resource = new WebSSOResource();
- // determine parent domain and wildcard the cookie domain with a dot prefix
- Assert.assertTrue(resource.getDomainName("http://www.local.com", null).equals(".local.com"));
- Assert.assertTrue(resource.getDomainName("http://ljm.local.com", null).equals(".local.com"));
-
- // test scenarios that will leverage the default cookie domain
- Assert.assertEquals(resource.getDomainName("http://local.home", null), null);
- Assert.assertEquals(resource.getDomainName("http://localhost", null), null); // chrome may not allow this
-
- Assert.assertTrue(resource.getDomainName("http://local.home.test.com", null).equals(".home.test.com"));
-
- // check the suffix config feature
- Assert.assertTrue(resource.getDomainName("http://local.home.test.com", ".test.com").equals(".test.com"));
- Assert.assertEquals(".novalocal", resource.getDomainName("http://34526yewt.novalocal", ".novalocal"));
-
- // make sure that even if the suffix doesn't start with a dot that the domain does
- // if we are setting a domain suffix then we want a specific domain for SSO and that
- // will require all hosts in the domain in order for it to work
- Assert.assertEquals(".novalocal", resource.getDomainName("http://34526yewt.novalocal", "novalocal"));
-
- // ip addresses can not be wildcarded - may be a completely different domain
- Assert.assertEquals(resource.getDomainName("http://127.0.0.1", null), null);
- }
@Test
public void testWhitelistMatching() throws Exception {
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
index 7ab05ee..485fd89 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
@@ -57,11 +57,14 @@ public class JWTToken implements JWT {
public JWTToken(String alg, String[] claimsArray) {
JWSHeader header = new JWSHeader(new JWSAlgorithm(alg));
- JWTClaimsSet claims = new JWTClaimsSet();
- claims.setIssuer(claimsArray[0]);
- claims.setSubject(claimsArray[1]);
- claims.setAudience(claimsArray[2]);
- claims.setExpirationTime(new Date(Long.parseLong(claimsArray[3])));
+
+ JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .issuer(claimsArray[0])
+ .subject(claimsArray[1])
+ .audience(claimsArray[2])
+ .expirationTime(new Date(Long.parseLong(claimsArray[3])))
+ .build();
+
jwt = new SignedJWT(header, claims);
}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
index 5255e3a..4d31150 100644
--- a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/util/Urls.java
@@ -17,6 +17,8 @@
*/
package org.apache.hadoop.gateway.util;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -66,4 +68,44 @@ public class Urls {
public static int dotOccurrences(String domain) {
return domain.length() - domain.replace(".", "").length();
}
+
+ /**
+ * Compute the domain name from an URL.
+ *
+ * @param url a given URL
+ * @param domainSuffix a domain suffix (can be <code>null</code>)
+ * @return the extracted domain name
+ * @throws URISyntaxException
+ */
+ public static String getDomainName(String url, String domainSuffix) throws URISyntaxException {
+ URI uri = new URI(url);
+ String domain = uri.getHost();
+
+ // if the hostname ends with the domainSuffix the use the domainSuffix as
+ // the cookie domain
+ if (domainSuffix != null && domain.endsWith(domainSuffix)) {
+ return (domainSuffix.startsWith(".")) ? domainSuffix : "." + domainSuffix;
+ }
+
+ // if accessing via ip address do not wildcard the cookie domain
+ // let's use the default domain
+ if (isIp(domain)) {
+ return null;
+ }
+
+ // if there are fewer than 2 dots than this is likely a
+ // specific host and we should use the default domain
+ if (dotOccurrences(domain) < 2) {
+ return null;
+ }
+
+ // assume any non-ip address with more than
+ // 3 dots will need the first element removed and
+ // all subdmains accepted
+ int idx = domain.indexOf('.');
+ if (idx == -1) {
+ idx = 0;
+ }
+ return domain.substring(idx);
+ }
}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
new file mode 100644
index 0000000..f55c79c
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/util/UrlsTest.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UrlsTest {
+
+ /**
+ * Domain name creation follows the following algorithm:
+ * 1. if the incoming request hostname endsWith a configured domain suffix return the suffix - with prefixed dot
+ * 2. if the request hostname is an ip address return null for default domain
+ * 3. if the request hostname has less than 3 dots return null for default domain
+ * 4. if request hostname has more than two dots strip the first element and return the remainder as domain
+ * @throws Exception
+ */
+ @Test
+ public void testDomainNameCreation() throws Exception {
+ // determine parent domain and wildcard the cookie domain with a dot prefix
+ Assert.assertTrue(Urls.getDomainName("http://www.local.com", null).equals(".local.com"));
+ Assert.assertTrue(Urls.getDomainName("http://ljm.local.com", null).equals(".local.com"));
+
+ // test scenarios that will leverage the default cookie domain
+ Assert.assertEquals(Urls.getDomainName("http://local.home", null), null);
+ Assert.assertEquals(Urls.getDomainName("http://localhost", null), null); // chrome may not allow this
+
+ Assert.assertTrue(Urls.getDomainName("http://local.home.test.com", null).equals(".home.test.com"));
+
+ // check the suffix config feature
+ Assert.assertTrue(Urls.getDomainName("http://local.home.test.com", ".test.com").equals(".test.com"));
+ Assert.assertEquals(".novalocal", Urls.getDomainName("http://34526yewt.novalocal", ".novalocal"));
+
+ // make sure that even if the suffix doesn't start with a dot that the domain does
+ // if we are setting a domain suffix then we want a specific domain for SSO and that
+ // will require all hosts in the domain in order for it to work
+ Assert.assertEquals(".novalocal", Urls.getDomainName("http://34526yewt.novalocal", "novalocal"));
+
+ // ip addresses can not be wildcarded - may be a completely different domain
+ Assert.assertEquals(Urls.getDomainName("http://127.0.0.1", null), null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/447b8fd7/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index c0ee6f9..4de7214 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
<module>gateway-provider-security-preauth</module>
<module>gateway-provider-security-hadoopauth</module>
<module>gateway-provider-security-shiro</module>
+ <module>gateway-provider-security-pac4j</module>
<module>gateway-provider-security-authz-acls</module>
<module>gateway-provider-identity-assertion-common</module>
<module>gateway-provider-identity-assertion-concat</module>
@@ -395,6 +396,11 @@
</dependency>
<dependency>
<groupId>${gateway-group}</groupId>
+ <artifactId>gateway-provider-security-pac4j</artifactId>
+ <version>${gateway-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${gateway-group}</groupId>
<artifactId>gateway-provider-security-webappsec</artifactId>
<version>${gateway-version}</version>
</dependency>
@@ -602,7 +608,7 @@
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
- <version>3.9</version>
+ <version>4.11</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@@ -1056,7 +1062,7 @@
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
- <scope>test</scope>
+ <!--scope>test</scope-->
</dependency>
<dependency>