Skip to content

KNOX-3052 - Allow Multiple Issuers and with some and no Audience #926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

import javax.security.auth.Subject;
import javax.servlet.Filter;
Expand Down Expand Up @@ -112,7 +113,7 @@ public abstract class AbstractJWTFilter implements Filter {
protected JWTokenAuthority authority;
protected RSAPublicKey publicKey;
protected SignatureVerificationCache signatureVerificationCache;
private String expectedIssuer;
private List<String> expectedIssuers;
private String expectedSigAlg;
protected String expectedPrincipalClaim;
protected String expectedJWKSUrl;
Expand Down Expand Up @@ -160,17 +161,30 @@ public void init( FilterConfig filterConfig ) throws ServletException {
}

protected void configureExpectedParameters(FilterConfig filterConfig) {
expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);
if (expectedIssuer == null) {
expectedIssuer = JWT_DEFAULT_ISSUER;
String expectedIssuersParam = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);
if (expectedIssuersParam == null) {
expectedIssuersParam = JWT_DEFAULT_ISSUER;
}
expectedIssuers = parseToListOfStrings(expectedIssuersParam);

expectedSigAlg = filterConfig.getInitParameter(JWT_EXPECTED_SIGALG);
if(StringUtils.isBlank(expectedSigAlg)) {
expectedSigAlg = JWT_DEFAULT_SIGALG;
}
}

/**
* Helper function to extract a List<String> from a comma separated
* String param.
* @param commaSeparatedList string to parse into a List<String>
*/
protected List<String> parseToListOfStrings(final String commaSeparatedList) {
return Arrays.stream(
commaSeparatedList.split(","))
.map(String::trim)
.collect(Collectors.toList());
}

protected List<String> parseExpectedAudiences(String expectedAudiences) {
List<String> audList = null;
// setup the list of valid audiences for token validation
Expand Down Expand Up @@ -240,6 +254,9 @@ protected boolean validateAudiences(final JWT jwtToken) {
break;
}
}
} else if (audiences.contains("NONE")) {
log.jwtAudienceValidated();
valid = true;
}
}
return valid;
Expand Down Expand Up @@ -350,7 +367,7 @@ protected boolean validateToken(final HttpServletRequest request, final HttpServ
final String displayableTokenId = Tokens.getTokenIDDisplayText(tokenId);
final String displayableToken = Tokens.getTokenDisplayText(token.toString());
// confirm that issuer matches the intended target
if (expectedIssuer.equals(token.getIssuer())) {
if (expectedIssuers.contains(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,96 @@ public void testValidAudienceJWT() throws Exception {
}
}

@Test
public void testValidAudienceJWTWithNONEAllowed() throws Exception {
try {
Properties props = getProperties();
props.put(getAudienceProperty(), "bar, NONE");
handler.init(new TestFilterConfig(props));

SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
null, new Date(new Date().getTime() + 5000));
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);

EasyMock.expect(request.getRequestURL()).andReturn(new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getPathInfo()).andReturn("resource").anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);

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.assertFalse("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 testValidAudienceJWTWithNONEAllowedButWithBar() throws Exception {
try {
Properties props = getProperties();
props.put(getAudienceProperty(), "bar, NONE");
handler.init(new TestFilterConfig(props));

SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
"bar", new Date(new Date().getTime() + 5000));
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);

EasyMock.expect(request.getRequestURL()).andReturn(new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getPathInfo()).andReturn("resource").anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);

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.assertFalse("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 testInvalidAudienceJWTWithNONENotAllowed() throws Exception {
try {
Properties props = getProperties();
props.put(getAudienceProperty(), "bar");
handler.init(new TestFilterConfig(props));

SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
null, new Date(new Date().getTime() + 5000));
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);

EasyMock.expect(request.getRequestURL()).andReturn(new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getPathInfo()).andReturn("resource").anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);

TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertFalse("doFilterCalled should not be true.", chain.doFilterCalled );
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}

@Test
public void testInvalidAudienceJWT() throws Exception {
try {
Expand Down Expand Up @@ -766,6 +856,65 @@ public void testValidIssuerViaConfig() throws Exception {
}
}

@Test
public void testValidIssuerViaConfigWithTwoIssuers() throws Exception {
try {
Properties props = getProperties();
props.setProperty(AbstractJWTFilter.JWT_EXPECTED_ISSUER, "new-issuer, KNOXSSO");
handler.init(new TestFilterConfig(props));

SignedJWT jwt = getJWT("KNOXSSO", "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.getPathInfo()).andReturn("resource").anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);

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.assertFalse("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 testInvalidIssuerViaConfigWithTwoIssuers() throws Exception {
try {
Properties props = getProperties();
props.setProperty(AbstractJWTFilter.JWT_EXPECTED_ISSUER, "new-issuer, KNOXSSO");
handler.init(new TestFilterConfig(props));

SignedJWT jwt = getJWT("fake-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.getPathInfo()).andReturn("resource").anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);

TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertFalse("doFilterCalled should not be true.", chain.doFilterCalled);
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}

@Test
public void testRS512SignatureAlgorithm() throws Exception {
try {
Expand Down Expand Up @@ -1103,6 +1252,10 @@ protected SignedJWT getJWT(String issuer, String sub, Date expires) throws Excep
return getJWT(issuer, sub, expires, privateKey);
}

protected SignedJWT getJWT(String issuer, String sub, String aud, Date expires) throws Exception {
return getJWT(issuer, sub, aud, expires, null, privateKey, JWSAlgorithm.RS256.getName());
}

protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey)
throws Exception {
return getJWT(issuer, sub, expires, new Date(), privateKey, JWSAlgorithm.RS256.getName());
Expand Down
Loading