http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/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 ea64b45,0000000..802019b 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,263 -1,0 +1,278 @@@ +/** + * 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.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; + +/** + * + */ +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"; ++ + static JWTMessages log = MessagesFactory.get( JWTMessages.class ); - protected List<String> audiences; - protected JWTokenAuthority authority; - protected String verificationPEM = null; - protected RSAPublicKey publicKey = null; + 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; ++ + 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);; ++ if (expectedIssuer == null) { ++ expectedIssuer = JWT_DEFAULT_ISSUER; ++ } ++ } ++ + /** + * @param expectedAudiences + * @return + */ + protected List<String> parseExpectedAudiences(String expectedAudiences) { + ArrayList<String> audList = null; + // setup the list of valid audiences for token validation + if (expectedAudiences != null) { + // parse into the list + String[] audArray = expectedAudiences.split(","); + audList = new ArrayList<String>(); + for (String a : audArray) { + audList.add(a); + } + } + return audList; + } + + protected boolean tokenIsStillValid(JWTToken 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) { + 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) { + 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 ++ ++ // 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 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) + 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); + } - ++ + if (verified) { - // confirm that issue matches intended target - which for this filter must be KNOXSSO - if (token.getIssuer().equals("KNOXSSO")) { ++ // 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; + } + else { + log.failedToValidateAudience(); - handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST, ++ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST, + "Bad request: missing required token audience"); + } + } + else { + log.tokenHasExpired(); - handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST, ++ 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, ++ ++ protected abstract void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status, + String error) throws IOException; - ++ +}
http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/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 3850502,0000000..ec0e980 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,109 -1,0 +1,111 @@@ +/** + * 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 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); + } + + public void destroy() { + } + - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) ++ 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); + 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); ++ response.sendError(status, error); + } + else { + response.sendError(status); + } + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/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 edfdc41,0000000..8a5f1ef 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,168 -1,0 +1,170 @@@ +/** + * 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; + +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); + } + + public void destroy() { + } + + @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) ++ 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); + 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/50f46e9e/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 503f323,0000000..10efeb5 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,490 -1,0 +1,636 @@@ +/** + * 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.Before; +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.util.Base64URL; ++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 RSAPublicKey publicKey = null; + protected RSAPrivateKey privateKey = null; + protected 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 String buildDistinguishedName(String hostname) { + MessageFormat headerFormatter = new MessageFormat(dnTemplate); + String[] paramArray = new String[1]; + paramArray[0] = hostname; + String dn = headerFormatter.format(paramArray); + return dn; + } + + @Before + public void setup() 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); + + 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 == true); ++ 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.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); + + 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 == true); ++ 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.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); + + 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("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 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); + + 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 == true); ++ 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.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); + + 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 == false); ++ 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); + + 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 == true); ++ 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.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("bob", new Date(new Date().getTime() + 5000), privateKey, props); + + 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 false.", chain.doFilterCalled == false); ++ 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); ++ ++ 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); ++ ++ 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."); ++ } ++ } ++ + 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 { + List<String> aud = new ArrayList<String>(); + aud.add("bar"); + + JWTClaimsSet claims = new JWTClaimsSet.Builder() - .issuer("KNOXSSO") ++ .issuer(issuer) + .subject(sub) + .audience(aud) + .expirationTime(expires) + .claim("scope", "openid") + .build(); + + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build(); + + SignedJWT signedJWT = new SignedJWT(header, claims); - Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput()); + 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 JWTToken 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 JWTToken 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 JWTToken issueToken(Principal p, String audience, String algorithm) + throws TokenServiceException { + return null; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#verifyToken(JWTToken) + */ + @Override + public boolean verifyToken(JWTToken token) throws TokenServiceException { - return true; ++ 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 JWTToken issueToken(Principal p, String audience, String algorithm, + long expires) throws TokenServiceException { + return null; + } + + @Override + public JWTToken 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 audience, long l) + throws TokenServiceException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean verifyToken(JWTToken token, RSAPublicKey publicKey) throws TokenServiceException { - // TODO Auto-generated method stub - return true; ++ 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/50f46e9e/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java index 14b704a,0000000..c35d013 mode 100644,000000..100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java @@@ -1,67 -1,0 +1,67 @@@ +/** + * 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 java.security.NoSuchAlgorithmException; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.easymock.EasyMock; +import org.junit.Before; + +import com.nimbusds.jwt.SignedJWT; + +public class JWTFederationFilterTest extends AbstractJWTFilterTest { - ++ + @Before + public void setup() throws Exception, NoSuchAlgorithmException { + super.setup(); + handler = new TestJWTFederationFilter(); - ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority()); ++ ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority(publicKey)); + } - ++ + protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + String token = "Bearer " + jwt.serialize(); + EasyMock.expect(request.getHeader("Authorization")).andReturn(token); + } - ++ + protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + String token = "Bearer " + "ljm" + jwt.serialize(); + EasyMock.expect(request.getHeader("Authorization")).andReturn(token); + } + + protected String getAudienceProperty() { + return TestJWTFederationFilter.KNOX_TOKEN_AUDIENCES; + } - ++ + private static class TestJWTFederationFilter extends JWTFederationFilter { + + public void setTokenService(JWTokenAuthority ts) { + authority = ts; + } - ++ + } + + @Override + protected String getVerificationPemProperty() { + return TestJWTFederationFilter.TOKEN_VERIFICATION_PEM; + }; - ++ +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTTokenTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTTokenTest.java index 1f1973e,0000000..7359a8d mode 100644,000000..100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTTokenTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTTokenTest.java @@@ -1,133 -1,0 +1,132 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.provider.federation; + +import java.util.ArrayList; - import junit.framework.TestCase; + +import org.apache.knox.gateway.services.security.token.impl.JWTToken; +import org.junit.Test; + - public class JWTTokenTest extends TestCase { ++public class JWTTokenTest extends org.junit.Assert { + private static final String JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E"; + private static final String HEADER = "{\"alg\":\"RS256\", \"type\":\"JWT\"}"; + private static final String CLAIMS = "{\"iss\": \"gateway\", \"prn\": \"john....@example.com\", \"aud\": \"https://login.example.com\", \"exp\": \"1363360913\"}"; + +// public void testTokenParsing() throws Exception { +// try { +// JWTToken token = JWTToken.parseToken(JWT_TOKEN); +// assertEquals(token.getHeader(), HEADER); +// assertEquals(token.getClaims(), CLAIMS); +// +// assertEquals(token.getIssuer(), "gateway"); +// assertEquals(token.getPrincipal(), "john....@example.com"); +// assertEquals(token.getAudience(), "https://login.example.com"); +// assertEquals(token.getExpires(), "1363360913"); +// } +// catch (ParseException pe) { +// fail("ParseException encountered."); +// } +// } + + @Test + public void testTokenCreation() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "john....@example.com"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + JWTToken token = new JWTToken("RS256", claims); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + } + + @Test + public void testTokenCreationWithAudienceListSingle() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "john....@example.com"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + ArrayList<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + + JWTToken token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(1, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithAudienceListMultiple() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "john....@example.com"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + ArrayList<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + audiences.add("KNOXSSO"); + + JWTToken token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(2, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithAudienceListCombined() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "john....@example.com"; + claims[2] = "LJM"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + ArrayList<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + audiences.add("KNOXSSO"); + + JWTToken token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(3, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithNullAudienceList() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "john....@example.com"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + ArrayList<String> audiences = null; + + JWTToken token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertEquals(null, token.getAudience()); + assertEquals(null, token.getAudienceClaims()); + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/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 fa0e785,0000000..d217799 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,205 -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.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"; - private static final String REDIRECT_LOCATION = - "https://localhost:8443/authserver?originalUrl=" + SERVICE_URL; - ++ + @Before + public void setup() throws Exception, NoSuchAlgorithmException { + super.setup(); + handler = new TestSSOCookieFederationProvider(); - ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority()); ++ ((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); + + 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 == true); ++ Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); - Assert.assertTrue("No PrimaryPrincipal returned.", principals.size() > 0); ++ 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 testFailedSignatureValidationJWT() throws Exception { - try { - - // Create a public key that doesn't match the one needed to - // verify the signature - in order to make it fail verification... - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(2048); - - KeyPair kp = kpg.genKeyPair(); - RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic(); - - handler.setPublicKey(publicKey); - - Properties props = getProperties(); - handler.init(props); - - SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), - privateKey); - - Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize()); - HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - Mockito.when(request.getCookies()).thenReturn(new Cookie[] { cookie }); - Mockito.when(request.getRequestURL()).thenReturn( - new StringBuffer(SERVICE_URL)).anyTimes(); - HttpServletResponse response = Mockito.mock(HttpServletResponse.class); - Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn( - SERVICE_URL); - - AuthenticationToken token = handler.alternateAuthenticate(request, - response); - Mockito.verify(response).sendRedirect(REDIRECT_LOCATION); - } catch (ServletException se) { - fail("alternateAuthentication should NOT have thrown a ServletException"); - } catch (AuthenticationException ae) { - fail("alternateAuthentication should NOT have thrown a AuthenticationException"); - } - } - */ + + @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; + } + }; + +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-provider-security-picketlink/src/test/java/org/apache/knox/gateway/picketlink/PicketlinkTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-picketlink/src/test/java/org/apache/knox/gateway/picketlink/PicketlinkTest.java index 92edc98,0000000..a0cd7be mode 100644,000000..100644 --- a/gateway-provider-security-picketlink/src/test/java/org/apache/knox/gateway/picketlink/PicketlinkTest.java +++ b/gateway-provider-security-picketlink/src/test/java/org/apache/knox/gateway/picketlink/PicketlinkTest.java @@@ -1,30 -1,0 +1,29 @@@ + +/** + * 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.picketlink; + - import junit.framework.TestCase; + +import org.junit.Test; + - public class PicketlinkTest extends TestCase { ++public class PicketlinkTest extends org.junit.Assert { + @Test + public void testPicketlink() throws Exception { + assertTrue(true); + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/DefaultValidatorTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/DefaultValidatorTest.java index 699f7d4,0000000..0480e40 mode 100644,000000..100644 --- a/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/DefaultValidatorTest.java +++ b/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/DefaultValidatorTest.java @@@ -1,43 -1,0 +1,43 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.provider.federation; + +import junit.framework.TestCase; +import org.apache.knox.gateway.preauth.filter.DefaultValidator; +import org.junit.Test; + +import static org.mockito.Mockito.mock; + +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; + - public class DefaultValidatorTest extends TestCase { ++public class DefaultValidatorTest extends org.junit.Assert { + @Test + public void testDefault() throws Exception { + final FilterConfig filterConfig = mock(FilterConfig.class); + final HttpServletRequest request = mock(HttpServletRequest.class); + DefaultValidator dv = new DefaultValidator(); + assertTrue(dv.validate(request, filterConfig)); + } + + @Test + public void testName() { + DefaultValidator dv = new DefaultValidator(); + assertEquals(dv.getName(), DefaultValidator.DEFAULT_VALIDATION_METHOD_VALUE); + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/HeaderPreAuthFederationFilterTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/HeaderPreAuthFederationFilterTest.java index 79e6fe8,0000000..c018d0f mode 100644,000000..100644 --- a/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/HeaderPreAuthFederationFilterTest.java +++ b/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/HeaderPreAuthFederationFilterTest.java @@@ -1,147 -1,0 +1,144 @@@ +/** + * 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 junit.framework.TestCase; - import org.apache.knox.gateway.preauth.filter.*; +import org.apache.knox.gateway.preauth.filter.DefaultValidator; +import org.apache.knox.gateway.preauth.filter.HeaderPreAuthFederationFilter; +import org.apache.knox.gateway.preauth.filter.IPValidator; +import org.apache.knox.gateway.preauth.filter.PreAuthService; +import org.apache.knox.gateway.preauth.filter.PreAuthValidationException; +import org.apache.knox.gateway.preauth.filter.PreAuthValidator; +import org.junit.Test; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + - public class HeaderPreAuthFederationFilterTest extends TestCase { ++public class HeaderPreAuthFederationFilterTest extends org.junit.Assert { + + @Test - public void testDefaultValidator() throws ServletException, - PreAuthValidationException { ++ public void testDefaultValidator() throws ServletException, PreAuthValidationException { + HeaderPreAuthFederationFilter hpaff = new HeaderPreAuthFederationFilter(); + final HttpServletRequest request = mock(HttpServletRequest.class); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(PreAuthService.VALIDATION_METHOD_PARAM)).thenReturn + (DefaultValidator.DEFAULT_VALIDATION_METHOD_VALUE); + hpaff.init(filterConfig); + List<PreAuthValidator> validators = hpaff.getValidators(); + assertEquals(validators.size(), 1); + assertEquals(validators.get(0).getName(), DefaultValidator.DEFAULT_VALIDATION_METHOD_VALUE); + assertTrue(PreAuthService.validate(request, filterConfig, validators)); + } + + @Test + public void testIPValidator() throws ServletException, PreAuthValidationException { + HeaderPreAuthFederationFilter hpaff = new HeaderPreAuthFederationFilter(); + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("10.1.23.42"); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(IPValidator.IP_ADDRESSES_PARAM)).thenReturn("5.4.3.2,10.1.23.42"); + when(filterConfig.getInitParameter(PreAuthService.VALIDATION_METHOD_PARAM)).thenReturn(IPValidator + .IP_VALIDATION_METHOD_VALUE); + hpaff.init(filterConfig); + List<PreAuthValidator> validators = hpaff.getValidators(); + assertEquals(validators.size(), 1); + assertEquals(validators.get(0).getName(), IPValidator.IP_VALIDATION_METHOD_VALUE); + assertTrue(PreAuthService.validate(request, filterConfig, validators)); + //Negative testing + when(request.getRemoteAddr()).thenReturn("10.10.22.33"); + assertFalse(PreAuthService.validate(request, filterConfig, validators)); + } + + @Test + public void testCustomValidatorPositive() throws ServletException, PreAuthValidationException { + HeaderPreAuthFederationFilter hpaff = new HeaderPreAuthFederationFilter(); + final HttpServletRequest request = mock(HttpServletRequest.class); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(PreAuthService.VALIDATION_METHOD_PARAM)).thenReturn + (DummyValidator.NAME); + + hpaff.init(filterConfig); + List<PreAuthValidator> validators = hpaff.getValidators(); + assertEquals(validators.size(), 1); + assertEquals(validators.get(0).getName(), DummyValidator.NAME); + //Positive test + when(request.getHeader("CUSTOM_TOKEN")).thenReturn("HelloWorld"); + assertTrue(PreAuthService.validate(request, filterConfig, validators)); + + } + + @Test + public void testCustomValidatorNegative() throws ServletException, PreAuthValidationException { + HeaderPreAuthFederationFilter hpaff = new HeaderPreAuthFederationFilter(); + final HttpServletRequest request = mock(HttpServletRequest.class); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(PreAuthService.VALIDATION_METHOD_PARAM)).thenReturn + (DummyValidator.NAME); + + hpaff.init(filterConfig); + List<PreAuthValidator> validators = hpaff.getValidators(); + assertEquals(validators.size(), 1); + assertEquals(validators.get(0).getName(), DummyValidator.NAME); + + when(request.getHeader("CUSTOM_TOKEN")).thenReturn("NOTHelloWorld"); + assertFalse(PreAuthService.validate(request, filterConfig, validators)); + + } + + + public static class DummyValidator implements PreAuthValidator { + public static String NAME = "DummyValidator"; + + public DummyValidator() { + + } + + /** + * @param httpRequest + * @param filterConfig + * @return true if validated, otherwise false + * @throws PreAuthValidationException + */ + @Override + public boolean validate(HttpServletRequest httpRequest, FilterConfig filterConfig) throws + PreAuthValidationException { + String token = httpRequest.getHeader("CUSTOM_TOKEN"); + if (token.equalsIgnoreCase("HelloWorld")) { + return true; + } else { + return false; + } + } + + /** + * Return unique validator name + * + * @return name of validator + */ + @Override + public String getName() { + return NAME; + } + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/IPValidatorTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/IPValidatorTest.java index 704090c,0000000..2617bcf mode 100644,000000..100644 --- a/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/IPValidatorTest.java +++ b/gateway-provider-security-preauth/src/test/java/org/apache/knox/gateway/provider/federation/IPValidatorTest.java @@@ -1,61 -1,0 +1,61 @@@ +/** + * 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 junit.framework.TestCase; +import org.apache.knox.gateway.preauth.filter.IPValidator; +import org.apache.knox.gateway.preauth.filter.PreAuthValidationException; +import org.junit.Test; + +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + - public class IPValidatorTest extends TestCase { ++public class IPValidatorTest extends org.junit.Assert { + + @Test + public void testName() { + IPValidator ipv = new IPValidator(); + assertEquals(ipv.getName(), IPValidator.IP_VALIDATION_METHOD_VALUE); + } + + + @Test + public void testIPAddressPositive() throws PreAuthValidationException { + IPValidator ipv = new IPValidator(); + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("10.1.23.42"); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(IPValidator.IP_ADDRESSES_PARAM)).thenReturn("5.4.3.2,10.1.23.42"); + assertTrue(ipv.validate(request, filterConfig)); + } + + @Test + public void testIPAddressNegative() throws PreAuthValidationException { + IPValidator ipv = new IPValidator(); + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("10.1.23.42"); + final FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter(IPValidator.IP_ADDRESSES_PARAM)).thenReturn("10.22.34.56"); + assertFalse(ipv.validate(request, filterConfig)); + } + + +}