You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/10/25 19:20:52 UTC
[16/17] knox git commit: Merge branch 'master' into
KNOX-998-Package_Restructuring
Merge branch 'master' into KNOX-998-Package_Restructuring
# Conflicts:
# gateway-provider-rewrite-func-hostmap-static/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ProviderDeploymentContributor
# gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
# gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
# gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
# gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java
# gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
# gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
# gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java
Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/58780d37
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/58780d37
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/58780d37
Branch: refs/heads/KNOX-998-Package_Restructuring
Commit: 58780d37c96f1f49f3863ecd065a09fb3bfda26a
Parents: 7d0bff1 994ac32
Author: Sandeep More <mo...@apache.org>
Authored: Wed Oct 25 10:59:55 2017 -0400
Committer: Sandeep More <mo...@apache.org>
Committed: Wed Oct 25 10:59:55 2017 -0400
----------------------------------------------------------------------
.../discovery/ambari/AmbariCluster.java | 2 +-
.../provider/impl/BaseZookeeperURLManager.java | 195 ++++++++++++
.../provider/impl/HBaseZookeeperURLManager.java | 138 +++++++++
.../provider/impl/KafkaZookeeperURLManager.java | 152 ++++++++++
.../provider/impl/SOLRZookeeperURLManager.java | 118 ++++++++
.../ha/provider/impl/StringResponseHandler.java | 49 +++
.../impl/HBaseZookeeperURLManagerTest.java | 72 +++++
.../impl/KafkaZookeeperURLManagerTest.java | 71 +++++
.../impl/SOLRZookeeperURLManagerTest.java | 110 +++++++
...gateway.deploy.ProviderDeploymentContributor | 5 +-
.../provider/federation/jwt/JWTMessages.java | 3 +
.../jwt/filter/AbstractJWTFilter.java | 57 +++-
.../jwt/filter/JWTFederationFilter.java | 5 +-
.../jwt/filter/SSOCookieFederationFilter.java | 5 +-
.../federation/AbstractJWTFilterTest.java | 239 +++++++++++++--
.../federation/SSOCookieProviderTest.java | 5 +-
gateway-provider-security-pac4j/pom.xml | 31 +-
.../pac4j/filter/Pac4jDispatcherFilter.java | 15 +-
.../pac4j/filter/Pac4jIdentityAdapter.java | 36 ++-
.../gateway/pac4j/session/KnoxSessionStore.java | 28 +-
.../knox/gateway/pac4j/Pac4jProviderTest.java | 10 +-
.../impl/DefaultTokenAuthorityService.java | 22 +-
.../topology/impl/DefaultTopologyService.java | 16 +
.../topology/simple/SimpleDescriptor.java | 4 +-
.../simple/SimpleDescriptorHandler.java | 43 ++-
.../topology/simple/SimpleDescriptorImpl.java | 12 +
.../impl/DefaultTokenAuthorityServiceTest.java | 94 ++++++
.../simple/SimpleDescriptorFactoryTest.java | 230 +++++++++++++-
.../simple/SimpleDescriptorHandlerTest.java | 79 ++++-
.../gateway/service/knoxsso/WebSSOResource.java | 18 +-
.../service/knoxsso/WebSSOResourceTest.java | 299 +++++++++++++++++-
.../service/knoxtoken/TokenResource.java | 20 +-
.../knoxtoken/TokenServiceResourceTest.java | 302 ++++++++++++++++++-
.../apache/knox/gateway/shell/job/Sqoop.java | 2 +-
.../services/security/token/impl/JWT.java | 3 +
.../services/security/token/impl/JWTToken.java | 38 ++-
.../security/token/impl/JWTTokenTest.java | 45 ++-
pom.xml | 2 +-
38 files changed, 2409 insertions(+), 166 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
index d65bff7,0000000..d71e079
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
@@@ -1,115 -1,0 +1,115 @@@
+/**
+ * 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.knox.gateway.topology.discovery.ambari;
+
+import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class AmbariCluster implements ServiceDiscovery.Cluster {
+
+ private String name = null;
+
+ private AmbariDynamicServiceURLCreator urlCreator;
+
+ private Map<String, Map<String, ServiceConfiguration>> serviceConfigurations = new HashMap<>();
+
+ private Map<String, AmbariComponent> components = null;
+
+
+ AmbariCluster(String name) {
+ this.name = name;
+ components = new HashMap<>();
+ urlCreator = new AmbariDynamicServiceURLCreator(this);
+ }
+
+ void addServiceConfiguration(String serviceName, String configurationType, ServiceConfiguration serviceConfig) {
+ if (!serviceConfigurations.keySet().contains(serviceName)) {
- serviceConfigurations.put(serviceName, new HashMap<String, ServiceConfiguration>());
++ serviceConfigurations.put(serviceName, new HashMap<>());
+ }
+ serviceConfigurations.get(serviceName).put(configurationType, serviceConfig);
+ }
+
+
+ void addComponent(AmbariComponent component) {
+ components.put(component.getName(), component);
+ }
+
+
+ ServiceConfiguration getServiceConfiguration(String serviceName, String configurationType) {
+ ServiceConfiguration sc = null;
+ Map<String, ServiceConfiguration> configs = serviceConfigurations.get(serviceName);
+ if (configs != null) {
+ sc = configs.get(configurationType);
+ }
+ return sc;
+ }
+
+
+ Map<String, AmbariComponent> getComponents() {
+ return components;
+ }
+
+
+ AmbariComponent getComponent(String name) {
+ return components.get(name);
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+
+ @Override
+ public List<String> getServiceURLs(String serviceName) {
+ List<String> urls = new ArrayList<>();
+ urls.addAll(urlCreator.create(serviceName));
+ return urls;
+ }
+
+
+ static class ServiceConfiguration {
+
+ private String type;
+ private String version;
+ private Map<String, String> props;
+
+ ServiceConfiguration(String type, String version, Map<String, String> properties) {
+ this.type = type;
+ this.version = version;
+ this.props = properties;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Map<String, String> getProperties() {
+ return props;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-rewrite-func-hostmap-static/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ProviderDeploymentContributor
----------------------------------------------------------------------
diff --cc gateway-provider-rewrite-func-hostmap-static/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ProviderDeploymentContributor
index 76328d9,0000000..d6b9608
mode 100644,000000..100644
--- a/gateway-provider-rewrite-func-hostmap-static/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ProviderDeploymentContributor
+++ b/gateway-provider-rewrite-func-hostmap-static/src/main/resources/META-INF/services/org.apache.knox.gateway.deploy.ProviderDeploymentContributor
@@@ -1,19 -1,0 +1,22 @@@
+##########################################################################
+# 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.knox.gateway.hostmap.impl.HostmapDeploymentContributor
++org.apache.knox.gateway.ha.provider.impl.HS2ZookeeperURLManager
++org.apache.knox.gateway.ha.provider.impl.SOLRZookeeperURLManager
++org.apache.knox.gateway.ha.provider.impl.KafkaZookeeperURLManager
++org.apache.knox.gateway.ha.provider.impl.HBaseZookeeperURLManager
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
index e1e0dcb,0000000..70efa8c
mode 100644,000000..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
@@@ -1,57 -1,0 +1,60 @@@
+/**
+
+ * 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.knox.gateway.provider.federation.jwt;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+
+@Messages(logger="org.apache.knox.gateway.provider.federation.jwt")
+public interface JWTMessages {
+ @Message( level = MessageLevel.WARN, text = "Failed to validate the audience attribute." )
+ void failedToValidateAudience();
+
+ @Message( level = MessageLevel.WARN, text = "Failed to verify the token signature." )
+ void failedToVerifyTokenSignature();
+
+ @Message( level = MessageLevel.INFO, text = "Access token has expired; a new one must be acquired." )
+ void tokenHasExpired();
+
++ @Message( level = MessageLevel.INFO, text = "The NotBefore check failed." )
++ void notBeforeCheckFailed();
++
+ @Message( level = MessageLevel.WARN, text = "Expected Bearer token is missing." )
+ void missingBearerToken();
+
+ @Message( level = MessageLevel.INFO, text = "Unable to verify token: {0}" )
+ void unableToVerifyToken(@StackTrace( level = MessageLevel.ERROR) Exception e);
+
+ @Message( level = MessageLevel.ERROR, text = "Unable to verify token: {0}" )
+ void unableToIssueToken(@StackTrace( level = MessageLevel.DEBUG) Exception e);
+
+ @Message( level = MessageLevel.DEBUG, text = "Sending redirect to: {0}" )
+ void sendRedirectToLoginURL(String loginURL);
+
+ @Message( level = MessageLevel.ERROR, text = "Required configuration element for authentication provider is missing." )
+ void missingAuthenticationProviderUrlConfiguration();
+
+ @Message( level = MessageLevel.DEBUG, text = "{0} Cookie has been found and is being processed." )
+ void cookieHasBeenFound(String cookieName);
+
+ @Message( level = MessageLevel.DEBUG, text = "Audience claim has been validated." )
+ void jwtAudienceValidated();
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 077fa05,0000000..49357f0
mode 100644,000000..100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@@ -1,278 -1,0 +1,315 @@@
+/**
+ * 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.knox.gateway.provider.federation.jwt.filter;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.interfaces.RSAPublicKey;
++import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.knox.gateway.audit.api.Action;
+import org.apache.knox.gateway.audit.api.ActionOutcome;
+import org.apache.knox.gateway.audit.api.AuditContext;
+import org.apache.knox.gateway.audit.api.AuditService;
+import org.apache.knox.gateway.audit.api.AuditServiceFactory;
+import org.apache.knox.gateway.audit.api.Auditor;
+import org.apache.knox.gateway.audit.api.ResourceType;
+import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.knox.gateway.filter.AbstractGatewayFilter;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
+import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.apache.knox.gateway.services.security.token.TokenServiceException;
- import org.apache.knox.gateway.services.security.token.impl.JWTToken;
++import org.apache.commons.lang.StringUtils;
++import org.apache.knox.gateway.services.security.token.impl.JWT;
++
++import com.nimbusds.jose.JWSHeader;
+
+/**
+ *
+ */
+public abstract class AbstractJWTFilter implements Filter {
+ /**
+ * If specified, this configuration property refers to a value which the issuer of a received
+ * token must match. Otherwise, the default value "KNOXSSO" is used
+ */
+ public static final String JWT_EXPECTED_ISSUER = "jwt.expected.issuer";
+ public static final String JWT_DEFAULT_ISSUER = "KNOXSSO";
+
++ /**
++ * If specified, this configuration property refers to the signature algorithm which a received
++ * token must match. Otherwise, the default value "RS256" is used
++ */
++ public static final String JWT_EXPECTED_SIGALG = "jwt.expected.sigalg";
++ public static final String JWT_DEFAULT_SIGALG = "RS256";
++
+ static JWTMessages log = MessagesFactory.get( JWTMessages.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 );
+
+ protected List<String> audiences;
+ protected JWTokenAuthority authority;
+ protected RSAPublicKey publicKey = null;
+ private String expectedIssuer;
++ private String expectedSigAlg;
+
+ public abstract void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException;
+
+ /**
+ *
+ */
+ public AbstractJWTFilter() {
+ super();
+ }
+
+ @Override
+ public void init( FilterConfig filterConfig ) throws ServletException {
+ ServletContext context = filterConfig.getServletContext();
+ if (context != null) {
+ GatewayServices services = (GatewayServices) context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+ if (services != null) {
+ authority = (JWTokenAuthority) services.getService(GatewayServices.TOKEN_SERVICE);
+ }
+ }
+ }
+
- protected void configureExpectedIssuer(FilterConfig filterConfig) {
- expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);;
++ protected void configureExpectedParameters(FilterConfig filterConfig) {
++ expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);
+ if (expectedIssuer == null) {
+ expectedIssuer = JWT_DEFAULT_ISSUER;
+ }
++
++ expectedSigAlg = filterConfig.getInitParameter(JWT_EXPECTED_SIGALG);
++ if (expectedSigAlg == null) {
++ expectedSigAlg = JWT_DEFAULT_SIGALG;
++ }
+ }
+
+ /**
+ * @param expectedAudiences
+ * @return
+ */
+ protected List<String> parseExpectedAudiences(String expectedAudiences) {
- ArrayList<String> audList = null;
++ List<String> audList = null;
+ // setup the list of valid audiences for token validation
- if (expectedAudiences != null) {
++ if (!StringUtils.isEmpty(expectedAudiences)) {
+ // parse into the list
+ String[] audArray = expectedAudiences.split(",");
+ audList = new ArrayList<String>();
+ for (String a : audArray) {
+ audList.add(a.trim());
+ }
+ }
+ return audList;
+ }
+
- protected boolean tokenIsStillValid(JWTToken jwtToken) {
++ protected boolean tokenIsStillValid(JWT jwtToken) {
+ // if there is no expiration date then the lifecycle is tied entirely to
+ // the cookie validity - otherwise ensure that the current time is before
+ // the designated expiration time
+ Date expires = jwtToken.getExpiresDate();
+ return (expires == null || expires != null && new Date().before(expires));
+ }
+
+ /**
+ * Validate whether any of the accepted audience claims is present in the
+ * issued token claims list for audience. Override this method in subclasses
+ * in order to customize the audience validation behavior.
+ *
+ * @param jwtToken
+ * the JWT token where the allowed audiences will be found
+ * @return true if an expected audience is present, otherwise false
+ */
- protected boolean validateAudiences(JWTToken jwtToken) {
++ protected boolean validateAudiences(JWT jwtToken) {
+ boolean valid = false;
+
+ String[] tokenAudienceList = jwtToken.getAudienceClaims();
+ // if there were no expected audiences configured then just
+ // consider any audience acceptable
+ if (audiences == null) {
+ valid = true;
+ } else {
+ // if any of the configured audiences is found then consider it
+ // acceptable
+ if (tokenAudienceList != null) {
+ for (String aud : tokenAudienceList) {
+ if (audiences.contains(aud)) {
+ log.jwtAudienceValidated();
+ valid = true;
+ break;
+ }
+ }
+ }
+ }
+ return valid;
+ }
+
+ protected void continueWithEstablishedSecurityContext(Subject subject, final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
+ Principal principal = (Principal) subject.getPrincipals(PrimaryPrincipal.class).toArray()[0];
+ AuditContext context = auditService.getContext();
+ if (context != null) {
+ context.setUsername( principal.getName() );
+ String sourceUri = (String)request.getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
+ if (sourceUri != null) {
+ auditor.audit( Action.AUTHENTICATION , sourceUri, ResourceType.URI, ActionOutcome.SUCCESS );
+ }
+ }
+
+ try {
+ Subject.doAs(
+ subject,
+ new PrivilegedExceptionAction<Object>() {
+ @Override
+ 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);
+ }
+ }
+ }
+
- protected Subject createSubjectFromToken(JWTToken token) {
++ protected Subject createSubjectFromToken(JWT token) {
+ final String principal = token.getSubject();
+
+ @SuppressWarnings("rawtypes")
+ HashSet emptySet = new HashSet();
+ Set<Principal> principals = new HashSet<>();
+ Principal p = new PrimaryPrincipal(principal);
+ principals.add(p);
+
+ // The newly constructed Sets check whether this Subject has been set read-only
+ // before permitting subsequent modifications. The newly created Sets also prevent
+ // illegal modifications by ensuring that callers have sufficient permissions.
+ //
+ // To modify the Principals Set, the caller must have AuthPermission("modifyPrincipals").
+ // To modify the public credential Set, the caller must have AuthPermission("modifyPublicCredentials").
+ // To modify the private credential Set, the caller must have AuthPermission("modifyPrivateCredentials").
+ javax.security.auth.Subject subject = new javax.security.auth.Subject(true, principals, emptySet, emptySet);
+ return subject;
+ }
+
+ protected boolean validateToken(HttpServletRequest request, HttpServletResponse response,
- FilterChain chain, JWTToken token)
++ FilterChain chain, JWT token)
+ throws IOException, ServletException {
+ boolean verified = false;
+ try {
+ if (publicKey == null) {
+ verified = authority.verifyToken(token);
+ }
+ else {
+ verified = authority.verifyToken(token, publicKey);
+ }
+ } catch (TokenServiceException e) {
+ log.unableToVerifyToken(e);
+ }
+
++ // Check received signature algorithm
++ if (verified) {
++ try {
++ String receivedSigAlg = JWSHeader.parse(token.getHeader()).getAlgorithm().getName();
++ if (!receivedSigAlg.equals(expectedSigAlg)) {
++ verified = false;
++ }
++ } catch (ParseException e) {
++ log.unableToVerifyToken(e);
++ verified = false;
++ }
++ }
++
+ if (verified) {
+ // confirm that issue matches intended target
+ if (expectedIssuer.equals(token.getIssuer())) {
+ // if there is no expiration data then the lifecycle is tied entirely to
+ // the cookie validity - otherwise ensure that the current time is before
+ // the designated expiration time
+ if (tokenIsStillValid(token)) {
+ boolean audValid = validateAudiences(token);
+ if (audValid) {
- return true;
++ Date nbf = token.getNotBeforeDate();
++ if (nbf == null || new Date().after(nbf)) {
++ return true;
++ } else {
++ log.notBeforeCheckFailed();
++ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
++ "Bad request: the NotBefore check failed");
++ }
+ }
+ else {
+ log.failedToValidateAudience();
+ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
+ "Bad request: missing required token audience");
+ }
+ }
+ else {
+ log.tokenHasExpired();
+ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
+ "Bad request: token has expired");
+ }
+ }
+ else {
+ handleValidationError(request, response, HttpServletResponse.SC_UNAUTHORIZED, null);
+ }
+ }
+ else {
+ log.failedToVerifyTokenSignature();
+ handleValidationError(request, response, HttpServletResponse.SC_UNAUTHORIZED, null);
+ }
+
+ return false;
+ }
+
+ protected abstract void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
+ String error) throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index ec0e980,0000000..187d2b0
mode 100644,000000..100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@@ -1,111 -1,0 +1,112 @@@
+/**
+ * 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.knox.gateway.provider.federation.jwt.filter;
+
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import org.apache.knox.gateway.util.CertificateUtils;
++import org.apache.knox.gateway.services.security.token.impl.JWT;
+
+import javax.security.auth.Subject;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+public class JWTFederationFilter extends AbstractJWTFilter {
+
+ public static final String KNOX_TOKEN_AUDIENCES = "knox.token.audiences";
+ public static final String TOKEN_VERIFICATION_PEM = "knox.token.verification.pem";
+ private static final String KNOX_TOKEN_QUERY_PARAM_NAME = "knox.token.query.param.name";
+ private static final String BEARER = "Bearer ";
+ private String paramName = "knoxtoken";
+
+ @Override
+ public void init( FilterConfig filterConfig ) throws ServletException {
+ super.init(filterConfig);
+
+ // expected audiences or null
+ String expectedAudiences = filterConfig.getInitParameter(KNOX_TOKEN_AUDIENCES);
+ if (expectedAudiences != null) {
+ audiences = parseExpectedAudiences(expectedAudiences);
+ }
+
+ // query param name for finding the provided knoxtoken
+ String queryParamName = filterConfig.getInitParameter(KNOX_TOKEN_QUERY_PARAM_NAME);
+ if (queryParamName != null) {
+ paramName = queryParamName;
+ }
+
+ // token verification pem
+ String verificationPEM = filterConfig.getInitParameter(TOKEN_VERIFICATION_PEM);
+ // setup the public key of the token issuer for verification
+ if (verificationPEM != null) {
+ publicKey = CertificateUtils.parseRSAPublicKey(verificationPEM);
+ }
+
- configureExpectedIssuer(filterConfig);
++ configureExpectedParameters(filterConfig);
+ }
+
+ public void destroy() {
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ String header = ((HttpServletRequest) request).getHeader("Authorization");
+ String wireToken = null;
+ if (header != null && header.startsWith(BEARER)) {
+ // what follows the bearer designator should be the JWT token being used to request or as an access token
+ wireToken = header.substring(BEARER.length());
+ }
+ else {
+ // check for query param
+ wireToken = ((HttpServletRequest) request).getParameter(paramName);
+ }
+
+ if (wireToken != null) {
+ try {
- JWTToken token = new JWTToken(wireToken);
++ JWT token = new JWTToken(wireToken);
+ if (validateToken((HttpServletRequest)request, (HttpServletResponse)response, chain, token)) {
+ Subject subject = createSubjectFromToken(token);
+ continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain);
+ }
+ } catch (ParseException ex) {
+ ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+ else {
+ // no token provided in header
+ ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+
+ protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
+ String error) throws IOException {
+ if (error != null) {
+ response.sendError(status, error);
+ }
+ else {
+ response.sendError(status);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
index 8a5f1ef,0000000..dbdb364
mode 100644,000000..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
@@@ -1,170 -1,0 +1,171 @@@
+/**
+ * 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.knox.gateway.provider.federation.jwt.filter;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+import javax.security.auth.Subject;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
+import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import org.apache.knox.gateway.util.CertificateUtils;
++import org.apache.knox.gateway.services.security.token.impl.JWT;
+
+public class SSOCookieFederationFilter extends AbstractJWTFilter {
+ public static final String SSO_COOKIE_NAME = "sso.cookie.name";
+ public static final String SSO_EXPECTED_AUDIENCES = "sso.expected.audiences";
+ public static final String SSO_AUTHENTICATION_PROVIDER_URL = "sso.authentication.provider.url";
+ public static final String SSO_VERIFICATION_PEM = "sso.token.verification.pem";
+ private static JWTMessages log = MessagesFactory.get( JWTMessages.class );
+ private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
+ private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt";
+
+ private String cookieName;
+ private String authenticationProviderUrl;
+
+ @Override
+ public void init( FilterConfig filterConfig ) throws ServletException {
+ super.init(filterConfig);
+
+ // configured cookieName
+ cookieName = filterConfig.getInitParameter(SSO_COOKIE_NAME);
+ if (cookieName == null) {
+ cookieName = DEFAULT_SSO_COOKIE_NAME;
+ }
+
+ // expected audiences or null
+ String expectedAudiences = filterConfig.getInitParameter(SSO_EXPECTED_AUDIENCES);
+ if (expectedAudiences != null) {
+ audiences = parseExpectedAudiences(expectedAudiences);
+ }
+
+ // url to SSO authentication provider
+ authenticationProviderUrl = filterConfig.getInitParameter(SSO_AUTHENTICATION_PROVIDER_URL);
+ if (authenticationProviderUrl == null) {
+ log.missingAuthenticationProviderUrlConfiguration();
+ throw new ServletException("Required authentication provider URL is missing.");
+ }
+
+ // token verification pem
+ String verificationPEM = filterConfig.getInitParameter(SSO_VERIFICATION_PEM);
+ // setup the public key of the token issuer for verification
+ if (verificationPEM != null) {
+ publicKey = CertificateUtils.parseRSAPublicKey(verificationPEM);
+ }
+
- configureExpectedIssuer(filterConfig);
++ configureExpectedParameters(filterConfig);
+ }
+
+ public void destroy() {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ String wireToken = null;
+ HttpServletRequest req = (HttpServletRequest) request;
+
+ String loginURL = constructLoginURL(req);
+ wireToken = getJWTFromCookie(req);
+ if (wireToken == null) {
+ if (req.getMethod().equals("OPTIONS")) {
+ // CORS preflight requests to determine allowed origins and related config
+ // must be able to continue without being redirected
+ Subject sub = new Subject();
+ sub.getPrincipals().add(new PrimaryPrincipal("anonymous"));
+ continueWithEstablishedSecurityContext(sub, req, (HttpServletResponse) response, chain);
+ }
+ log.sendRedirectToLoginURL(loginURL);
+ ((HttpServletResponse) response).sendRedirect(loginURL);
+ }
+ else {
+ try {
- JWTToken token = new JWTToken(wireToken);
++ JWT token = new JWTToken(wireToken);
+ if (validateToken((HttpServletRequest)request, (HttpServletResponse)response, chain, token)) {
+ Subject subject = createSubjectFromToken(token);
+ continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain);
+ }
+ } catch (ParseException ex) {
+ ((HttpServletResponse) response).sendRedirect(loginURL);
+ }
+ }
+ }
+
+ protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
+ String error) throws IOException {
+ String loginURL = constructLoginURL(request);
+ response.sendRedirect(loginURL);
+ }
+
+ /**
+ * Encapsulate the acquisition of the JWT token from HTTP cookies within the
+ * request.
+ *
+ * @param req servlet request to get the JWT token from
+ * @return serialized JWT token
+ */
+ protected String getJWTFromCookie(HttpServletRequest req) {
+ String serializedJWT = null;
+ Cookie[] cookies = req.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (cookieName.equals(cookie.getName())) {
+ log.cookieHasBeenFound(cookieName);
+ serializedJWT = cookie.getValue();
+ break;
+ }
+ }
+ }
+ return serializedJWT;
+ }
+
+ /**
+ * Create the URL to be used for authentication of the user in the absence of
+ * a JWT token within the incoming request.
+ *
+ * @param request for getting the original request URL
+ * @return url to use as login url for redirect
+ */
+ protected String constructLoginURL(HttpServletRequest request) {
+ String delimiter = "?";
+ if (authenticationProviderUrl.contains("?")) {
+ delimiter = "&";
+ }
+ String loginURL = authenticationProviderUrl + delimiter
+ + ORIGINAL_URL_QUERY_PARAM
+ + request.getRequestURL().append(getOriginalQueryString(request));
+ return loginURL;
+ }
+
+ private String getOriginalQueryString(HttpServletRequest request) {
+ String originalQueryString = request.getQueryString();
+ return (originalQueryString == null) ? "" : "?" + originalQueryString;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
index 9888eab,0000000..f79a743
mode 100644,000000..100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
@@@ -1,667 -1,0 +1,870 @@@
+/**
+ * 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.knox.gateway.provider.federation;
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.AccessController;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Properties;
+import java.util.Date;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
+import org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter;
+import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.services.security.impl.X509CertificateUtil;
+import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.apache.knox.gateway.services.security.token.TokenServiceException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.nimbusds.jose.*;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+
+public abstract class AbstractJWTFilterTest {
+ private static final String SERVICE_URL = "https://localhost:8888/resource";
+ private static final String dnTemplate = "CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
+
+ protected AbstractJWTFilter handler = null;
+ protected static RSAPublicKey publicKey = null;
+ protected static RSAPrivateKey privateKey = null;
+ protected static String pem = null;
+
+ protected abstract void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt);
+ protected abstract void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt);
+ protected abstract String getAudienceProperty();
+ protected abstract String getVerificationPemProperty();
+
+ private static String buildDistinguishedName(String hostname) {
+ MessageFormat headerFormatter = new MessageFormat(dnTemplate);
+ String[] paramArray = new String[1];
+ paramArray[0] = hostname;
+ String dn = headerFormatter.format(paramArray);
+ return dn;
+ }
+
+ @BeforeClass
+ public static void generateKeys() throws Exception, NoSuchAlgorithmException {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(2048);
+ KeyPair KPair = kpg.generateKeyPair();
+ String dn = buildDistinguishedName(InetAddress.getLocalHost().getHostName());
+ Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 365, "SHA1withRSA");
+ byte[] data = cert.getEncoded();
+ Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) );
+ pem = new String(encoder.encodeToString( data ).getBytes( "ASCII" )).trim();
+
+ publicKey = (RSAPublicKey) KPair.getPublic();
+ privateKey = (RSAPrivateKey) KPair.getPrivate();
+ }
+
+ @After
+ public void teardown() throws Exception {
+ handler.destroy();
+ }
+
+ @Test
+ public void testValidJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testValidAudienceJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.put(getAudienceProperty(), "bar");
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testInvalidAudienceJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.put(getAudienceProperty(), "foo");
+ props.put("sso.authentication.provider.url", "https://localhost:8443/gateway/knoxsso/api/v1/websso");
+
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testValidAudienceJWTWhitespace() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.put(getAudienceProperty(), " foo, bar ");
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000), privateKey);
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
++ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
++ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
++ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
++ @Test
++ public void testNoTokenAudience() throws Exception {
++ try {
++ Properties props = getProperties();
++ props.put(getAudienceProperty(), "bar");
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", null,
++ new Date(new Date().getTime() + 5000), new Date(), privateKey, "RS256");
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
++ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
++ @Test
++ public void testNoAudienceConfigured() throws Exception {
++ try {
++ Properties props = getProperties();
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", null,
++ new Date(new Date().getTime() + 5000), new Date(), privateKey, "RS256");
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
++ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
++ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
++ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
++ @Test
++ public void testEmptyAudienceConfigured() throws Exception {
++ try {
++ Properties props = getProperties();
++ props.put(getAudienceProperty(), "");
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", null,
++ new Date(new Date().getTime() + 5000), new Date(), privateKey, "RS256");
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testValidVerificationPEM() throws Exception {
+ try {
+ Properties props = getProperties();
+
+// System.out.println("+" + pem + "+");
+
+ props.put(getAudienceProperty(), "bar");
+ props.put("sso.authentication.provider.url", "https://localhost:8443/gateway/knoxsso/api/v1/websso");
+ props.put(getVerificationPemProperty(), pem);
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 50000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testExpiredJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() - 1000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() - 1000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testValidJWTNoExpiration() throws Exception {
+ try {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", null, privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", null, privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL).anyTimes();
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testUnableToParseJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
++ new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setGarbledTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL).anyTimes();
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testFailedSignatureValidationJWT() throws Exception {
+ try {
+ // Create a private key to sign the token
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+
+ KeyPair kp = kpg.genKeyPair();
+
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000),
- (RSAPrivateKey)kp.getPrivate(), props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
++ new Date(new Date().getTime() + 5000), (RSAPrivateKey)kp.getPrivate());
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL).anyTimes();
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testInvalidVerificationPEM() throws Exception {
+ try {
+ Properties props = getProperties();
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+
+ KeyPair KPair = kpg.generateKeyPair();
+ String dn = buildDistinguishedName(InetAddress.getLocalHost().getHostName());
+ Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 365, "SHA1withRSA");
+ byte[] data = cert.getEncoded();
+ Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) );
+ String failingPem = new String(encoder.encodeToString( data ).getBytes( "ASCII" )).trim();
+
+ props.put(getAudienceProperty(), "bar");
+ props.put(getVerificationPemProperty(), failingPem);
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 50000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", chain.doFilterCalled == false);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testInvalidIssuer() throws Exception {
+ try {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
+ SignedJWT jwt = getJWT("new-issuer", "alice", new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testValidIssuerViaConfig() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.setProperty(AbstractJWTFilter.JWT_EXPECTED_ISSUER, "new-issuer");
+ handler.init(new TestFilterConfig(props));
+
+ SignedJWT jwt = getJWT("new-issuer", "alice", new Date(new Date().getTime() + 5000), privateKey);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled);
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal", principals.size() > 0);
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
++ @Test
++ public void testRS512SignatureAlgorithm() throws Exception {
++ try {
++ Properties props = getProperties();
++ props.put(AbstractJWTFilter.JWT_EXPECTED_SIGALG, "RS512");
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", new Date(new Date().getTime() + 5000),
++ new Date(), privateKey, JWSAlgorithm.RS512.getName());
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
++ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
++ Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty());
++ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
++ @Test
++ public void testInvalidSignatureAlgorithm() throws Exception {
++ try {
++ Properties props = getProperties();
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", new Date(new Date().getTime() + 5000),
++ new Date(), privateKey, JWSAlgorithm.RS384.getName());
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be false.", !chain.doFilterCalled );
++ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
++ @Test
++ public void testNotBeforeJWT() throws Exception {
++ try {
++ Properties props = getProperties();
++ handler.init(new TestFilterConfig(props));
++
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000),
++ new Date(new Date().getTime() + 5000), privateKey,
++ JWSAlgorithm.RS256.getName());
++
++ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++ setTokenOnRequest(request, jwt);
++
++ EasyMock.expect(request.getRequestURL()).andReturn(
++ new StringBuffer(SERVICE_URL)).anyTimes();
++ EasyMock.expect(request.getQueryString()).andReturn(null);
++ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
++ SERVICE_URL);
++ EasyMock.replay(request);
++
++ TestFilterChain chain = new TestFilterChain();
++ handler.doFilter(request, response, chain);
++ Assert.assertTrue("doFilterCalled should not be false.", !chain.doFilterCalled);
++ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
++ } catch (ServletException se) {
++ fail("Should NOT have thrown a ServletException.");
++ }
++ }
++
+ protected Properties getProperties() {
+ Properties props = new Properties();
+ props.setProperty(
+ SSOCookieFederationFilter.SSO_AUTHENTICATION_PROVIDER_URL,
+ "https://localhost:8443/authserver");
+ return props;
+ }
+
- protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey,
- Properties props) throws Exception {
- return getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, sub, expires, privateKey);
++ protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey)
++ throws Exception {
++ return getJWT(issuer, sub, expires, new Date(), privateKey, JWSAlgorithm.RS256.getName());
++ }
++
++ protected SignedJWT getJWT(String issuer, String sub, Date expires, Date nbf, RSAPrivateKey privateKey,
++ String signatureAlgorithm)
++ throws Exception {
++ return getJWT(issuer, sub, "bar", expires, nbf, privateKey, signatureAlgorithm);
+ }
+
- protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey)
++ protected SignedJWT getJWT(String issuer, String sub, String aud, Date expires, Date nbf, RSAPrivateKey privateKey,
++ String signatureAlgorithm)
+ throws Exception {
- List<String> aud = new ArrayList<String>();
- aud.add("bar");
++ List<String> audiences = new ArrayList<String>();
++ if (aud != null) {
++ audiences.add(aud);
++ }
+
+ JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .subject(sub)
+ .audience(aud)
+ .expirationTime(expires)
++ .notBeforeTime(nbf)
+ .claim("scope", "openid")
+ .build();
+
- JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
++ JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.parse(signatureAlgorithm)).build();
+
+ SignedJWT signedJWT = new SignedJWT(header, claims);
+ JWSSigner signer = new RSASSASigner(privateKey);
+
+ signedJWT.sign(signer);
+
+ return signedJWT;
+ }
+
+ protected static class TestFilterConfig implements FilterConfig {
+ Properties props = null;
+
+ public TestFilterConfig(Properties props) {
+ this.props = props;
+ }
+
+ @Override
+ public String getFilterName() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see javax.servlet.FilterConfig#getServletContext()
+ */
+ @Override
+ public ServletContext getServletContext() {
+// JWTokenAuthority authority = EasyMock.createNiceMock(JWTokenAuthority.class);
+// GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
+// EasyMock.expect(services.getService("TokenService").andReturn(authority));
+// ServletContext context = EasyMock.createNiceMock(ServletContext.class);
+// EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE).andReturn(new DefaultGatewayServices()));
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see javax.servlet.FilterConfig#getInitParameter(java.lang.String)
+ */
+ @Override
+ public String getInitParameter(String name) {
+ return props.getProperty(name, null);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.servlet.FilterConfig#getInitParameterNames()
+ */
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ return null;
+ }
+
+ }
+
+ protected static class TestJWTokenAuthority implements JWTokenAuthority {
+
+ private PublicKey verifyingKey;
+
+ public TestJWTokenAuthority(PublicKey verifyingKey) {
+ this.verifyingKey = verifyingKey;
+ }
+
+ /* (non-Javadoc)
+ * @see JWTokenAuthority#issueToken(javax.security.auth.Subject, java.lang.String)
+ */
+ @Override
+ public JWT issueToken(Subject subject, String algorithm)
+ throws TokenServiceException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String)
+ */
+ @Override
+ public JWT issueToken(Principal p, String algorithm)
+ throws TokenServiceException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, java.lang.String)
+ */
+ @Override
+ public JWT issueToken(Principal p, String audience, String algorithm)
+ throws TokenServiceException {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.knox.gateway.services.security.token.JWTokenAuthority#verifyToken(org.apache.knox.gateway.services.security.token.impl.JWT)
+ */
+ @Override
+ public boolean verifyToken(JWT token) throws TokenServiceException {
+ JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) verifyingKey);
+ return token.verify(verifier);
+ }
+
+ /* (non-Javadoc)
+ * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, java.lang.String, long)
+ */
+ @Override
+ public JWT issueToken(Principal p, String audience, String algorithm,
+ long expires) throws TokenServiceException {
+ return null;
+ }
+
+ @Override
+ public JWT issueToken(Principal p, List<String> audiences, String algorithm,
+ long expires) throws TokenServiceException {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, long)
+ */
+ @Override
+ public JWT issueToken(Principal p, String algorithm, long expires)
+ throws TokenServiceException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean verifyToken(JWT token, RSAPublicKey publicKey) throws TokenServiceException {
+ JWSVerifier verifier = new RSASSAVerifier(publicKey);
+ return token.verify(verifier);
+ }
+
+ }
+
+ protected static class TestFilterChain implements FilterChain {
+ boolean doFilterCalled = false;
+ Subject subject = null;
+
+ /* (non-Javadoc)
+ * @see javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException {
+ doFilterCalled = true;
+
+ subject = Subject.getSubject( AccessController.getContext() );
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java
----------------------------------------------------------------------
diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java
index babbee2,0000000..50a44ce
mode 100644,000000..100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java
@@@ -1,161 -1,0 +1,162 @@@
+/**
+ * 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.knox.gateway.provider.federation;
+
+import static org.junit.Assert.fail;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.util.Properties;
+import java.util.Date;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
++import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
+import org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter;
+import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.nimbusds.jwt.SignedJWT;
+
+public class SSOCookieProviderTest extends AbstractJWTFilterTest {
+ private static final String SERVICE_URL = "https://localhost:8888/resource";
+
+ @Before
+ public void setup() throws Exception, NoSuchAlgorithmException {
+ handler = new TestSSOCookieFederationProvider();
+ ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority(publicKey));
+ }
+
+ protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
+ Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize());
+ EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie });
+ }
+
+ protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
+ Cookie cookie = new Cookie("hadoop-jwt", "ljm" + jwt.serialize());
+ EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie });
+ }
+
+ protected String getAudienceProperty() {
+ return TestSSOCookieFederationProvider.SSO_EXPECTED_AUDIENCES;
+ }
+
+ @Test
+ public void testCustomCookieNameJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.put("sso.cookie.name", "jowt");
+ handler.init(new TestFilterConfig(props));
+
- SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000),
- privateKey, props);
++ SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
++ new Date(new Date().getTime() + 5000), privateKey);
+
+ Cookie cookie = new Cookie("jowt", jwt.serialize());
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie });
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled );
+ Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
+ Assert.assertTrue("No PrimaryPrincipal returned.", !principals.isEmpty());
+ Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testNoProviderURLJWT() throws Exception {
+ try {
+ Properties props = getProperties();
+ props.remove("sso.authentication.provider.url");
+ handler.init(new TestFilterConfig(props));
+
+ fail("Servlet exception should have been thrown.");
+
+ } catch (ServletException se) {
+ // expected - let's ensure it mentions the missing authentication provider URL
+ se.getMessage().contains("authentication provider URL is missing");
+ }
+ }
+
+ @Test
+ public void testOrigURLWithQueryString() throws Exception {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn("name=value");
+ EasyMock.replay(request);
+
+ String loginURL = ((TestSSOCookieFederationProvider)handler).testConstructLoginURL(request);
+ Assert.assertNotNull("loginURL should not be null.", loginURL);
+ Assert.assertEquals("https://localhost:8443/authserver?originalUrl=" + SERVICE_URL + "?name=value", loginURL);
+ }
+
+ @Test
+ public void testOrigURLNoQueryString() throws Exception {
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ EasyMock.replay(request);
+
+ String loginURL = ((TestSSOCookieFederationProvider)handler).testConstructLoginURL(request);
+ Assert.assertNotNull("LoginURL should not be null.", loginURL);
+ Assert.assertEquals("https://localhost:8443/authserver?originalUrl=" + SERVICE_URL, loginURL);
+ }
+
+
+ @Override
+ protected String getVerificationPemProperty() {
+ return SSOCookieFederationFilter.SSO_VERIFICATION_PEM;
+ };
+
+ private static class TestSSOCookieFederationProvider extends SSOCookieFederationFilter {
+ public String testConstructLoginURL(HttpServletRequest req) {
+ return constructLoginURL(req);
+ }
+
+ public void setTokenService(JWTokenAuthority ts) {
+ authority = ts;
+ }
+ };
+
+}