blob: 157aabec86347172dc304c13e28eeb1b7a0d6529 [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.knox.gateway.provider.federation;
import com.nimbusds.jwt.SignedJWT;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
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.Assert;
import org.junit.Test;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.ParseException;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail;
@SuppressWarnings({"PMD.JUnit4TestShouldUseBeforeAnnotation", "PMD.JUnit4TestShouldUseTestAnnotation"})
public class TokenIDAsHTTPBasicCredsFederationFilterTest extends JWTAsHTTPBasicCredsFederationFilterTest {
TestTokenStateService tss;
@Override
public void setUp() {
super.setUp();
tss = new TestTokenStateService();
((TestJWTFederationFilter) handler).setTokenStateService(tss);
}
@Override
protected void setTokenOnRequest(final HttpServletRequest request, final SignedJWT jwt) {
addTokenState(jwt);
setTokenOnRequest(request, TestJWTFederationFilter.PASSCODE, getTokenId(jwt));
}
/**
* Bind the specified JWT to the specified request, and apply the specified authUsername as the HTTP Basic
* username in the Authorization header value.
*
* @param request The request to which the JWT should be bound.
* @param jwt The JWT
* @param authUsername The HTTP Basic auth username to apply in the Authorization header value.
*/
protected void setTokenOnRequest(final HttpServletRequest request,
final SignedJWT jwt,
final String authUsername) {
addTokenState(jwt);
setTokenOnRequest(request, authUsername, getTokenId(jwt));
}
@Override
protected void setGarbledTokenOnRequest(final HttpServletRequest request, final SignedJWT jwt) {
setTokenOnRequest(request, TestJWTFederationFilter.PASSCODE, "junk" + getTokenId(jwt));
}
private String getTokenId(final SignedJWT jwt) {
String tokenId = null;
try {
tokenId = (String) jwt.getJWTClaimsSet().getClaim(JWTToken.KNOX_ID_CLAIM);
} catch (ParseException e) {
//
}
return tokenId;
}
private void addTokenState(final SignedJWT jwt) {
try {
JWTToken token = new JWTToken(jwt.serialize());
tss.addToken(token, System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
String subject = (String) jwt.getJWTClaimsSet().getClaim(JWTToken.SUBJECT);
tss.addMetadata(TokenUtils.getTokenId(token), new TokenMetadata(subject));
} catch (ParseException e) {
Assert.fail(e.getMessage());
}
}
@Override
public void testAlternativeCaseUsername() throws Exception {
try {
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
new Date(new Date().getTime() + 5000), privateKey);
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt, JWTFederationFilter.PASSCODE.toLowerCase(Locale.ROOT));
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.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertTrue("doFilterCalled should be true.", chain.doFilterCalled );
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Test
public void testInvalidUsername() throws Exception {
try {
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
new Date(new Date().getTime() + 5000), privateKey);
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt, "InvalidBasicAuthUsername");
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.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertFalse("doFilterCalled should be false.", chain.doFilterCalled );
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
/**
* Negative test, specifying the Basic auth username as the one used for JWTs while specifying
* the passcode as the password.
*/
@Test
public void testInvalidJWTForPasscode() throws Exception {
try {
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
new Date(new Date().getTime() + 5000), privateKey);
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt, JWTFederationFilter.TOKEN);
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.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertFalse("doFilterCalled should be false.", chain.doFilterCalled );
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
/**
* Negative test, specifying the Basic auth username as the one used for passcodes while specifying the JWT
* as the password.
*/
@Test
public void testInvalidPasscodeForJWT() throws Exception {
try {
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob",
new Date(new Date().getTime() + 5000), privateKey);
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, JWTFederationFilter.PASSCODE, jwt.serialize());
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.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
EasyMock.replay(request, response);
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
Assert.assertFalse("doFilterCalled should be false.", chain.doFilterCalled );
Assert.assertNull("Subject should be null since authentication should have failed.", chain.subject);
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
}
}
@Override
public void testJWTWithoutKnoxUUIDClaim() throws Exception {
// Override to disable N/A test
}
@Override
public void testExpiredJWTWithoutKnoxUUIDClaim() throws Exception {
// Override to disable N/A test
}
@Override
public void testInvalidAudienceJWT() throws Exception {
// Override to disable N/A test
}
@Override
public void testNoTokenAudience() throws Exception {
// Override to disable N/A test
}
@Override
public void testNoAudienceConfigured() throws Exception {
// Override to disable N/A test
}
@Override
public void testEmptyAudienceConfigured() throws Exception {
// Override to disable N/A test
}
@Override
public void testValidAudienceJWT() throws Exception {
// Override to disable N/A test
}
@Override
public void testValidAudienceJWTWhitespace() throws Exception {
// Override to disable N/A test
}
@Override
public void testValidIssuerViaConfig() throws Exception {
// Override to disable N/A test
}
@Override
public void testInvalidIssuer() throws Exception {
// Override to disable N/A test
}
@Override
public void testFailedSignatureValidationJWT() throws Exception {
// Override to disable N/A test
}
@Override
public void testInvalidVerificationPEM() throws Exception {
// Override to disable N/A test
}
@Override
public void testRS512SignatureAlgorithm() throws Exception {
// Override to disable N/A test
}
@Override
public void testInvalidSignatureAlgorithm() throws Exception {
// Override to disable N/A test
}
@Override
public void testValidVerificationPEM() throws Exception {
// Override to disable N/A test
}
@Override
public void testNotBeforeJWT() throws Exception {
// Override to disable N/A test
}
@Override
public void testVerificationOptimization() throws Exception {
// Override to disable N/A test
}
@Override
public void testExpiredTokensEvictedFromSignatureVerificationCache() throws Exception {
// Override to disable N/A test
}
/**
* Very basic TokenStateService implementation for these tests only
*/
private static class TestTokenStateService implements TokenStateService {
private final Map<String, Long> tokenExpirations = new ConcurrentHashMap<>();
private final Map<String, TokenMetadata> tokenMetadata = new ConcurrentHashMap<>();
@Override
public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
}
@Override
public void start() throws ServiceLifecycleException {
}
@Override
public void stop() throws ServiceLifecycleException {
}
@Override
public long getDefaultRenewInterval() {
return TimeUnit.DAYS.toMillis(1);
}
@Override
public long getDefaultMaxLifetimeDuration() {
return TimeUnit.DAYS.toMillis(7);
}
@Override
public void addToken(JWTToken token, long issueTime) {
String expiration = token.getExpires();
if (expiration == null || expiration.isEmpty()) {
expiration = "0";
}
addToken(TokenUtils.getTokenId(token), Instant.now().toEpochMilli(), Long.parseLong(expiration));
}
private void addToken(String tokenId, long expiration) {
tokenExpirations.put(tokenId, expiration);
}
@Override
public void addToken(String tokenId, long issueTime, long expiration) {
addToken(tokenId, expiration);
}
@Override
public void addToken(String tokenId, long issueTime, long expiration, long maxLifetimeDuration) {
addToken(tokenId, expiration);
}
@Override
public boolean isExpired(JWTToken token) throws UnknownTokenException {
Long expiration;
String tokenId = TokenUtils.getTokenId(token);
if (tokenId != null) {
expiration = tokenExpirations.get(tokenId);
} else {
expiration = Long.parseLong(token.getExpires());
}
return Instant.ofEpochMilli(expiration).isBefore(Instant.now());
}
@Override
public void revokeToken(JWTToken token) throws UnknownTokenException {
}
@Override
public void revokeToken(String tokenId) throws UnknownTokenException {
}
@Override
public long renewToken(JWTToken token) throws UnknownTokenException {
return 0;
}
@Override
public long renewToken(JWTToken token, long renewInterval) throws UnknownTokenException {
return 0;
}
@Override
public long renewToken(String tokenId) throws UnknownTokenException {
return 0;
}
@Override
public long renewToken(String tokenId, long renewInterval) throws UnknownTokenException {
return 0;
}
@Override
public long getTokenExpiration(JWT token) throws UnknownTokenException {
return getTokenExpiration(TokenUtils.getTokenId(token));
}
@Override
public long getTokenExpiration(String tokenId) throws UnknownTokenException {
if (!tokenExpirations.containsKey(tokenId)) {
throw new UnknownTokenException(tokenId);
}
return tokenExpirations.get(tokenId);
}
@Override
public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
return getTokenExpiration(tokenId);
}
@Override
public void addMetadata(String tokenId, TokenMetadata metadata) {
tokenMetadata.put(tokenId, metadata);
}
@Override
public TokenMetadata getTokenMetadata(String tokenId) throws UnknownTokenException {
if (!tokenMetadata.containsKey(tokenId)) {
throw new UnknownTokenException(tokenId);
}
return tokenMetadata.get(tokenId);
}
}
}