blob: 4126624d7d238403df989e719e1659cfeb957354 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.gateway.provider.federation;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.security.AccessController;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
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.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter;
import org.apache.hadoop.gateway.security.PrimaryPrincipal;
import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority;
import org.apache.hadoop.gateway.services.security.token.TokenServiceException;
import org.apache.hadoop.gateway.services.security.token.impl.JWT;
import org.apache.hadoop.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;
public class SSOCookieProviderTest {
private static final String SERVICE_URL = "https://localhost:8888/resource";
private static final String REDIRECT_LOCATION =
"https://localhost:8443/authserver?originalUrl=" + SERVICE_URL;
RSAPublicKey publicKey = null;
RSAPrivateKey privateKey = null;
SSOCookieFederationFilter handler = null;
@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);
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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == true);
Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class);
Assert.assertTrue("No PrimaryPrincipal returned.", 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 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 authenticaiton provider URL
se.getMessage().contains("authentication provider URL is missing");
}
}
/*
@Test
public void testUnableToParseJWT() throws Exception {
try {
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", "ljm" + 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));
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 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));
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 testExpiredJWT() throws Exception {
try {
((TestSSOCookieFederationProvider) handler).setPublicKey(publicKey);
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() - 1000),
privateKey);
Cookie cookie = new Cookie("hadoop-jwt", 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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == false);
// 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());
// Assert.assertEquals("alice", token.getUserName());
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Test
public void testInvalidAudienceJWT() throws Exception {
try {
Properties props = getProperties();
props
.put("sso.expected.audiences", "foo");
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000),
privateKey);
Cookie cookie = new Cookie("hadoop-jwt", 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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == false);
Assert.assertTrue("No Subject should be returned.", chain.subject == null);
// Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName());
// Assert.assertEquals("alice", token.getUserName());
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Test
public void testValidAudienceJWT() throws Exception {
try {
Properties props = getProperties();
props
.put("sso.expected.audiences", "bar");
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000),
privateKey);
Cookie cookie = new Cookie("hadoop-jwt", 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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == true);
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());
// Assert.assertEquals("alice", token.getUserName());
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Test
public void testValidJWT() throws Exception {
try {
((TestSSOCookieFederationProvider) handler).setPublicKey(publicKey);
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000),
privateKey);
Cookie cookie = new Cookie("hadoop-jwt", 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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == true);
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());
// Assert.assertEquals("alice", token.getUserName());
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Test
public void testValidJWTNoExpiration() throws Exception {
try {
((TestSSOCookieFederationProvider) handler).setPublicKey(publicKey);
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT("alice", null,
privateKey);
Cookie cookie = new Cookie("hadoop-jwt", 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));
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
SERVICE_URL);
EasyMock.replay(request);
((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled == true);
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());
// Assert.assertEquals("alice", token.getUserName());
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@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));
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));
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);
}
@Before
public void setup() throws Exception, NoSuchAlgorithmException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
publicKey = (RSAPublicKey) kp.getPublic();
privateKey = (RSAPrivateKey) kp.getPrivate();
handler = new TestSSOCookieFederationProvider();
}
@After
public void teardown() throws Exception {
handler.destroy();
}
protected Properties getProperties() {
Properties props = new Properties();
props.setProperty(
TestSSOCookieFederationProvider.SSO_AUTHENTICATION_PROVIDER_URL,
"https://localhost:8443/authserver");
return props;
}
protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey)
throws Exception {
List<String> aud = new ArrayList<String>();
aud.add("bar");
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("https://c2id.com")
.subject(sub)
.audience(aud)
.expirationTime(expires)
.claim("scope", "openid")
.build();
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT signedJWT = new SignedJWT(header, claims);
Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput());
JWSSigner signer = new RSASSASigner(privateKey);
signedJWT.sign(signer);
return signedJWT;
}
class TestSSOCookieFederationProvider extends SSOCookieFederationFilter {
public String testConstructLoginURL(HttpServletRequest req) {
return constructLoginURL(req);
}
public void setPublicKey(RSAPublicKey key) {
publicKey = key;
}
public void setTokenService(JWTokenAuthority ts) {
authority = ts;
}
};
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;
}
}
class TestJWTokenAuthority implements JWTokenAuthority {
/* (non-Javadoc)
* @see org.apache.hadoop.gateway.services.security.token.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 org.apache.hadoop.gateway.services.security.token.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 org.apache.hadoop.gateway.services.security.token.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 org.apache.hadoop.gateway.services.security.token.JWTokenAuthority#verifyToken(org.apache.hadoop.gateway.services.security.token.impl.JWTToken)
*/
@Override
public boolean verifyToken(JWTToken token) throws TokenServiceException {
return true;
}
/* (non-Javadoc)
* @see org.apache.hadoop.gateway.services.security.token.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 org.apache.hadoop.gateway.services.security.token.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;
}
}
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() );
}
}
}