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 <m...@apache.org> Authored: Wed Oct 25 10:59:55 2017 -0400 Committer: Sandeep More <m...@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; + } + }; + +}