From 9af35cca20ee27a4684b8ea7d430787bd504110c Mon Sep 17 00:00:00 2001 From: Jan-Olav Eide Date: Sun, 3 Dec 2023 10:17:56 +0100 Subject: [PATCH 1/3] convert filters to kotlin --- token-validation-filter/pom.xml | 12 +- .../support/filter/JwtTokenExpiryFilter.java | 92 ------ .../filter/JwtTokenValidationFilter.java | 85 ------ .../support/filter/JwtTokenExpiryFilter.kt | 83 ++++++ .../filter/JwtTokenValidationFilter.kt | 53 ++++ .../filter/JwtTokenExpiryFilterTest.java | 91 ------ .../filter/JwtTokenValidationFilterTest.java | 275 ----------------- .../filter/JwtTokenExpiryFilterTest.kt | 87 ++++++ .../filter/JwtTokenValidationFilterTest.kt | 278 ++++++++++++++++++ 9 files changed, 510 insertions(+), 546 deletions(-) delete mode 100644 token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenExpiryFilter.java delete mode 100644 token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenValidationFilter.java create mode 100644 token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilter.kt create mode 100644 token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt delete mode 100644 token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.java delete mode 100644 token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.java create mode 100644 token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.kt create mode 100644 token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.kt diff --git a/token-validation-filter/pom.xml b/token-validation-filter/pom.xml index 4e6c3a66..a3eab3de 100644 --- a/token-validation-filter/pom.xml +++ b/token-validation-filter/pom.xml @@ -27,12 +27,18 @@ logback-classic test - + + org.jetbrains.kotlin + kotlin-stdlib + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin - org.apache.maven.plugins - maven-compiler-plugin + kotlin-maven-plugin + org.jetbrains.kotlin org.apache.maven.plugins diff --git a/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenExpiryFilter.java b/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenExpiryFilter.java deleted file mode 100644 index 61cd3f7f..00000000 --- a/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenExpiryFilter.java +++ /dev/null @@ -1,92 +0,0 @@ -package no.nav.security.token.support.filter; - -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import no.nav.security.token.support.core.JwtTokenConstants; -import no.nav.security.token.support.core.context.TokenValidationContextHolder; -import no.nav.security.token.support.core.jwt.JwtTokenClaims; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; -import java.util.Date; - -/** - * Checks the expiry time in a validated token against a preconfigured threshold - * and returns a custom http header if this threshold is reached. - *

- * Can be used to check if the token is about to expire and inform the caller - */ -public class JwtTokenExpiryFilter implements Filter { - - private static final Logger LOG = LoggerFactory.getLogger(JwtTokenExpiryFilter.class); - private final TokenValidationContextHolder contextHolder; - private final long expiryThresholdInMinutes; - - public JwtTokenExpiryFilter(TokenValidationContextHolder contextHolder, long expiryThresholdInMinutes) { - this.contextHolder = contextHolder; - this.expiryThresholdInMinutes = expiryThresholdInMinutes; - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - if (request instanceof HttpServletRequest) { - addHeaderOnTokenExpiryThreshold((HttpServletResponse) response); - chain.doFilter(request, response); - } - else { - chain.doFilter(request, response); - } - } - - @Override - public void destroy() { - } - - @Override - public void init(FilterConfig filterConfig) { - } - - private void addHeaderOnTokenExpiryThreshold(HttpServletResponse response) { - var tokenValidationContext = contextHolder.getTokenValidationContext(); - LOG.debug("Getting TokenValidationContext: {}", tokenValidationContext); - if (tokenValidationContext != null) { - LOG.debug("Getting issuers from validationcontext {}", tokenValidationContext.getIssuers()); - for (String issuer : tokenValidationContext.getIssuers()) { - var jwtTokenClaims = tokenValidationContext.getClaims(issuer); - if (tokenExpiresBeforeThreshold(jwtTokenClaims)) { - LOG.debug("Setting response header {}", JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER); - response.setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true"); - } - else { - LOG.debug("Token is still within expiry threshold."); - } - } - } - } - - private boolean tokenExpiresBeforeThreshold(JwtTokenClaims jwtTokenClaims) { - Date expiryDate = (Date)jwtTokenClaims.get("exp"); - LocalDateTime expiry = LocalDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()); - long minutesUntilExpiry = LocalDateTime.now().until(expiry, ChronoUnit.MINUTES); - LOG.debug("Checking token at time {} with expirationTime {} for how many minutes until expiry: {}", - LocalDateTime.now(), expiry, minutesUntilExpiry); - if (minutesUntilExpiry <= expiryThresholdInMinutes) { - LOG.debug("There are {} minutes until expiry which is equal to or less than the configured threshold {}", - minutesUntilExpiry, expiryThresholdInMinutes); - return true; - } - return false; - } - - @Override - public String toString() { - return getClass().getSimpleName() + " [contextHolder=" + contextHolder + ", expiryThresholdInMinutes=" - + expiryThresholdInMinutes + "]"; - } -} diff --git a/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenValidationFilter.java b/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenValidationFilter.java deleted file mode 100644 index f2c77bf4..00000000 --- a/token-validation-filter/src/main/java/no/nav/security/token/support/filter/JwtTokenValidationFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -package no.nav.security.token.support.filter; - -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import no.nav.security.token.support.core.context.TokenValidationContextHolder; -import no.nav.security.token.support.core.http.HttpRequest; -import no.nav.security.token.support.core.validation.JwtTokenValidationHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; - -public class JwtTokenValidationFilter implements Filter { - - private static final Logger LOG = LoggerFactory.getLogger(JwtTokenValidationFilter.class); - private final JwtTokenValidationHandler jwtTokenValidationHandler; - private final TokenValidationContextHolder contextHolder; - - public JwtTokenValidationFilter(JwtTokenValidationHandler jwtTokenValidationHandler, TokenValidationContextHolder contextHolder) { - this.jwtTokenValidationHandler = jwtTokenValidationHandler; - this.contextHolder = contextHolder; - } - - @Override - public void destroy() { - - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - if (request instanceof HttpServletRequest req) { - doTokenValidation(req, (HttpServletResponse) response, chain); - } else { - chain.doFilter(request, response); - } - } - - @Override - public void init(FilterConfig filterConfig) { - - } - - private void doTokenValidation(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws IOException, ServletException { - - contextHolder.setTokenValidationContext(jwtTokenValidationHandler.getValidatedTokens(fromHttpServletRequest(request))); - try { - chain.doFilter(request, response); - } finally { - contextHolder.setTokenValidationContext(null); - } - } - - static HttpRequest fromHttpServletRequest(final HttpServletRequest request) { - return new HttpRequest() { - @Override - public String getHeader(String headerName) { - return request.getHeader(headerName); - } - - @Override - public NameValue[] getCookies() { - if (request.getCookies() == null) { - return null; - } - return Arrays.stream(request.getCookies()).map(cookie -> new NameValue() { - - @Override - public String getName() { - return cookie.getName(); - } - - @Override - public String getValue() { - return cookie.getValue(); - } - }).toArray(NameValue[]::new); - } - }; - } - -} diff --git a/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilter.kt b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilter.kt new file mode 100644 index 00000000..05451828 --- /dev/null +++ b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilter.kt @@ -0,0 +1,83 @@ +package no.nav.security.token.support.filter + +import jakarta.servlet.Filter +import jakarta.servlet.FilterChain +import jakarta.servlet.FilterConfig +import jakarta.servlet.ServletException +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import java.io.IOException +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.temporal.ChronoUnit.MINUTES +import java.util.Date +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import no.nav.security.token.support.core.JwtTokenConstants +import no.nav.security.token.support.core.context.TokenValidationContextHolder +import no.nav.security.token.support.core.jwt.JwtTokenClaims + +/** + * Checks the expiry time in a validated token against a preconfigured threshold + * and returns a custom http header if this threshold is reached. + * + * + * Can be used to check if the token is about to expire and inform the caller + */ +class JwtTokenExpiryFilter(private val contextHolder : TokenValidationContextHolder, private val expiryThresholdInMinutes : Long) : Filter { + + override fun doFilter(request : ServletRequest, response : ServletResponse, chain : FilterChain) { + if (request is HttpServletRequest) { + addHeaderOnTokenExpiryThreshold(response as HttpServletResponse) + chain.doFilter(request, response) + } + else { + chain.doFilter(request, response) + } + } + + override fun destroy() {} + + override fun init(filterConfig : FilterConfig) {} + + private fun addHeaderOnTokenExpiryThreshold(response : HttpServletResponse) { + val tokenValidationContext = contextHolder.tokenValidationContext + LOG.debug("Getting TokenValidationContext: {}", tokenValidationContext) + if (tokenValidationContext != null) { + LOG.debug("Getting issuers from validationcontext {}", tokenValidationContext.issuers) + for (issuer in tokenValidationContext.issuers) { + val jwtTokenClaims = tokenValidationContext.getClaims(issuer) + if (tokenExpiresBeforeThreshold(jwtTokenClaims)) { + LOG.debug("Setting response header {}", JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER) + response.setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true") + } + else { + LOG.debug("Token is still within expiry threshold.") + } + } + } + } + + private fun tokenExpiresBeforeThreshold(jwtTokenClaims : JwtTokenClaims) : Boolean { + val expiryDate = jwtTokenClaims["exp"] as Date + val expiry = LocalDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()) + val minutesUntilExpiry = LocalDateTime.now().until(expiry, MINUTES) + LOG.debug("Checking token at time {} with expirationTime {} for how many minutes until expiry: {}", + LocalDateTime.now(), expiry, minutesUntilExpiry) + if (minutesUntilExpiry <= expiryThresholdInMinutes) { + LOG.debug("There are {} minutes until expiry which is equal to or less than the configured threshold {}", + minutesUntilExpiry, expiryThresholdInMinutes) + return true + } + return false + } + + override fun toString() = ("${javaClass.getSimpleName()} [contextHolder=$contextHolder, expiryThresholdInMinutes=$expiryThresholdInMinutes]") + + companion object { + + private val LOG : Logger = LoggerFactory.getLogger(JwtTokenExpiryFilter::class.java) + } +} \ No newline at end of file diff --git a/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt new file mode 100644 index 00000000..4801839e --- /dev/null +++ b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt @@ -0,0 +1,53 @@ +package no.nav.security.token.support.filter + +import jakarta.servlet.Filter +import jakarta.servlet.FilterChain +import jakarta.servlet.FilterConfig +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import no.nav.security.token.support.core.context.TokenValidationContextHolder +import no.nav.security.token.support.core.http.HttpRequest +import no.nav.security.token.support.core.http.HttpRequest.NameValue +import no.nav.security.token.support.core.validation.JwtTokenValidationHandler + +class JwtTokenValidationFilter(private val jwtTokenValidationHandler : JwtTokenValidationHandler, private val contextHolder : TokenValidationContextHolder) : Filter { + + override fun destroy() {} + + override fun doFilter(request : ServletRequest, response : ServletResponse, chain : FilterChain) { + if (request is HttpServletRequest) { + doTokenValidation(request, response as HttpServletResponse, chain) + } + else { + chain.doFilter(request, response) + } + } + + override fun init(filterConfig : FilterConfig) {} + + private fun doTokenValidation(request : HttpServletRequest, response : HttpServletResponse, chain : FilterChain) { + contextHolder.tokenValidationContext = jwtTokenValidationHandler.getValidatedTokens(fromHttpServletRequest(request)) + try { + chain.doFilter(request, response) + } + finally { + contextHolder.tokenValidationContext = null + } + } + + companion object { + + @JvmStatic + fun fromHttpServletRequest(request: HttpServletRequest) = object : HttpRequest { + override fun getHeader(headerName: String) = request.getHeader(headerName) + override fun getCookies() = request.cookies?.map { + object : NameValue { + override fun getName() = it.name + override fun getValue() = it.value + } + }?.toTypedArray() + } + } +} \ No newline at end of file diff --git a/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.java b/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.java deleted file mode 100644 index dc692128..00000000 --- a/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package no.nav.security.token.support.filter; - -import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.PlainJWT; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import no.nav.security.token.support.core.JwtTokenConstants; -import no.nav.security.token.support.core.context.TokenValidationContext; -import no.nav.security.token.support.core.context.TokenValidationContextHolder; -import no.nav.security.token.support.core.jwt.JwtTokenClaims; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.IOException; -import java.text.ParseException; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Collections; -import java.util.Date; - -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) - class JwtTokenExpiryFilterTest { - - @Mock - private HttpServletRequest servletRequest; - @Mock - private FilterChain filterChain; - @Mock - private HttpServletResponse servletResponse; - private TokenValidationContextHolder tokenValidationContextHolder; - private static final long EXPIRY_THRESHOLD = 1; - - @Test - void tokenExpiresBeforeThreshold() throws IOException, ServletException { - setupMocks(LocalDateTime.now().plusMinutes(2)); - - JwtTokenExpiryFilter jwtTokenExpiryFilter = new JwtTokenExpiryFilter(tokenValidationContextHolder, - EXPIRY_THRESHOLD); - jwtTokenExpiryFilter.doFilter(servletRequest, servletResponse, filterChain); - verify(servletResponse).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true"); - } - - @Test - void tokenExpiresAfterThreshold() throws IOException, ServletException { - setupMocks(LocalDateTime.now().plusMinutes(3)); - - JwtTokenExpiryFilter jwtTokenExpiryFilter = new JwtTokenExpiryFilter(tokenValidationContextHolder, - EXPIRY_THRESHOLD); - jwtTokenExpiryFilter.doFilter(servletRequest, servletResponse, filterChain); - verify(servletResponse, never()).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true"); - } - - @Test - void noValidToken() throws IOException, ServletException { - JwtTokenExpiryFilter jwtTokenExpiryFilter = new JwtTokenExpiryFilter(mock(TokenValidationContextHolder.class), - EXPIRY_THRESHOLD); - jwtTokenExpiryFilter.doFilter(servletRequest, servletResponse, filterChain); - verify(servletResponse, never()).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true"); - } - - private void setupMocks(LocalDateTime expiry) { - tokenValidationContextHolder = mock(TokenValidationContextHolder.class); - TokenValidationContext tokenValidationContext = mock(TokenValidationContext.class); - when(tokenValidationContextHolder.getTokenValidationContext()).thenReturn(tokenValidationContext); - when(tokenValidationContext.getIssuers()).thenReturn(Collections.singletonList("issuer1")); - - Date expiryDate = Date.from(expiry.atZone(ZoneId.systemDefault()) - .toInstant()); - when(tokenValidationContext.getClaims(anyString())).thenReturn(createOIDCClaims(expiryDate)); - } - - private static JwtTokenClaims createOIDCClaims(Date expiry) { - try { - JWT jwt = new PlainJWT(new JWTClaimsSet.Builder() - .subject("subject") - .issuer("http//issuer1") - .expirationTime(expiry).build()); - return new JwtTokenClaims(jwt.getJWTClaimsSet()); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.java b/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.java deleted file mode 100644 index da5e1347..00000000 --- a/token-validation-filter/src/test/java/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.java +++ /dev/null @@ -1,275 +0,0 @@ -package no.nav.security.token.support.filter; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.util.IOUtils; -import com.nimbusds.jose.util.Resource; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import no.nav.security.token.support.core.JwtTokenConstants; -import no.nav.security.token.support.core.configuration.IssuerProperties; -import no.nav.security.token.support.core.configuration.MultiIssuerConfiguration; -import no.nav.security.token.support.core.configuration.ProxyAwareResourceRetriever; -import no.nav.security.token.support.core.context.TokenValidationContext; -import no.nav.security.token.support.core.context.TokenValidationContextHolder; -import no.nav.security.token.support.core.http.HttpRequest; -import no.nav.security.token.support.core.validation.JwtTokenValidationHandler; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class JwtTokenValidationFilterTest { - - private static final String KEYID = "myKeyId"; - private static final String AUDIENCE = "aud1"; - private static final String IDTOKENCOOKIENAME = "idtokencookie"; - - @Mock - private HttpServletRequest servletRequest; - - @Mock - private HttpServletResponse servletResponse; - - @Test - void testSingleValidIdTokenInCookie() throws IOException, URISyntaxException, ServletException, JOSEException { - final String issuername = "myissuer"; - Map issuerProps = createIssuerPropertiesMap(issuername, IDTOKENCOOKIENAME); - MockResourceRetriever mockResources = new MockResourceRetriever(issuername); - final TokenValidationContextHolder ctxHolder = new TestTokenValidationContextHolder(); - - JwtTokenValidationFilter filter = createFilterToTest(issuerProps, mockResources, ctxHolder); - final String jwt = createJWT(issuername, mockResources.keysForIssuer(issuername).toRSAPrivateKey()); - - final int[] filterCallCounter = new int[]{0}; - - when(servletRequest.getCookies()).thenReturn(new Cookie[]{new Cookie("JSESSIONID", "ABCDEF"), new Cookie(IDTOKENCOOKIENAME, jwt)}); - filter.doFilter(servletRequest, servletResponse, - mockFilterchainAsserting(issuername, "foobar", ctxHolder, filterCallCounter)); - - assertEquals(1, filterCallCounter[0], "doFilter should have been called once"); - } - - @Test - void testSingleValidIdTokenInHeader() throws IOException, URISyntaxException, ServletException, JOSEException { - final String anotherIssuer = "anotherIssuer"; - Map issuerProps = createIssuerPropertiesMap(anotherIssuer, IDTOKENCOOKIENAME); - - MockResourceRetriever mockResources = new MockResourceRetriever(anotherIssuer); - final TokenValidationContextHolder ctxHolder = new TestTokenValidationContextHolder(); - JwtTokenValidationFilter filter = createFilterToTest(issuerProps, mockResources, ctxHolder); - - final String jwt = createJWT(anotherIssuer, mockResources.keysForIssuer(anotherIssuer).toRSAPrivateKey()); - - final int[] filterCallCounter = new int[]{0}; - - when(servletRequest.getCookies()).thenReturn(null); - when(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer " + jwt); - filter.doFilter(servletRequest, servletResponse, - mockFilterchainAsserting(anotherIssuer, "foobar", ctxHolder, filterCallCounter)); - - assertEquals(1, filterCallCounter[0], "doFilter should have been called once"); - } - - @Test - void testTwoValidIdTokensWithDifferentIssuersInHeader() throws IOException, URISyntaxException, ServletException, JOSEException { - final String issuer1 = "issuer1"; - final String anotherIssuer = "issuerNumberTwo"; - Map issuerProps = new HashMap<>(); - issuerProps.putAll(createIssuerPropertiesMap(issuer1, null)); - issuerProps.putAll(createIssuerPropertiesMap(anotherIssuer, null)); - - MockResourceRetriever mockResources = new MockResourceRetriever(issuer1, anotherIssuer); - final TokenValidationContextHolder ctxHolder = new TestTokenValidationContextHolder(); - JwtTokenValidationFilter filter = createFilterToTest(issuerProps, mockResources, ctxHolder); - - final String jwt1 = createJWT(issuer1, mockResources.keysForIssuer(issuer1).toRSAPrivateKey()); - final String jwt2 = createJWT(anotherIssuer, mockResources.keysForIssuer(anotherIssuer).toRSAPrivateKey()); - - final int[] filterCallCounter = new int[]{0}; - - when(servletRequest.getCookies()).thenReturn(null); - when(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer " + jwt1 + ",Bearer " + jwt2); - filter.doFilter(servletRequest, servletResponse, - mockFilterchainAsserting(new String[]{issuer1, anotherIssuer}, new String[]{"foobar", "foobar"}, ctxHolder, filterCallCounter)); - - assertEquals(1, filterCallCounter[0], "doFilter should have been called once"); - } - - @Test - void testRequestConverterShouldHandleWhenCookiesAreNULL() { - when(servletRequest.getCookies()).thenReturn(null); - when(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn(null); - - HttpRequest req = JwtTokenValidationFilter.fromHttpServletRequest(servletRequest); - assertNull(req.getCookies()); - assertNull(req.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)); - } - - @Test - void testRequestConverterShouldConvertCorrectly() { - when(servletRequest.getCookies()).thenReturn(new Cookie[]{new Cookie("JSESSIONID", "ABCDEF"), new Cookie("IDTOKEN", "THETOKEN")}); - when(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer eyAAA"); - - HttpRequest req = JwtTokenValidationFilter.fromHttpServletRequest(servletRequest); - assertEquals("JSESSIONID", req.getCookies()[0].getName()); - assertEquals("ABCDEF", req.getCookies()[0].getValue()); - assertEquals("IDTOKEN", req.getCookies()[1].getName()); - assertEquals("THETOKEN", req.getCookies()[1].getValue()); - assertEquals("Bearer eyAAA", req.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)); - } - - - private FilterChain mockFilterchainAsserting(String issuer, String subject, TokenValidationContextHolder ctxHolder, int[] filterCallCounter) { - return mockFilterchainAsserting(new String[]{issuer}, new String[]{subject}, ctxHolder, filterCallCounter); - } - - private FilterChain mockFilterchainAsserting(String[] issuers, String[] subjects, TokenValidationContextHolder ctxHolder, int[] filterCallCounter) { - return (servletRequest, servletResponse) -> { - // TokenValidationContext is nulled after filter-call, so we check it here: - filterCallCounter[0]++; - final TokenValidationContext ctx = ctxHolder.getTokenValidationContext(); - assertTrue(ctx.hasValidToken()); - assertEquals(issuers.length, ctx.getIssuers().size()); - for (int i = 0; i < issuers.length; i++) { - assertTrue(ctx.hasTokenFor(issuers[i])); - assertEquals(subjects[i], ctx.getClaims(issuers[i]).getStringClaim("sub")); - } - }; - } - - //////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////// - - private JwtTokenValidationFilter createFilterToTest(Map issuerProps, - MockResourceRetriever mockResources, TokenValidationContextHolder ctxHolder) { - MultiIssuerConfiguration conf = new MultiIssuerConfiguration(issuerProps, mockResources); - JwtTokenValidationHandler jwtTokenValidationHandler = new JwtTokenValidationHandler(conf); - return new JwtTokenValidationFilter(jwtTokenValidationHandler, ctxHolder); - } - - private Map createIssuerPropertiesMap(String issuer, String cookieName) - throws URISyntaxException, MalformedURLException { - Map issuerPropertiesMap = new HashMap<>(); - issuerPropertiesMap.put(issuer, - new IssuerProperties(new URI("https://" + issuer).toURL(), Collections.singletonList(AUDIENCE), cookieName)); - return issuerPropertiesMap; - } - - private String createJWT(String issuer, RSAPrivateKey signingKey) throws JOSEException { - Date now = new Date(); - JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() - .subject("foobar").issuer(issuer).audience(AUDIENCE).notBeforeTime(now).issueTime(now) - .expirationTime(new Date(now.getTime() + 3600)).build(); - - JWSSigner signer = new RSASSASigner(signingKey); - SignedJWT signedJWT = new SignedJWT( - new JWSHeader(JWSAlgorithm.RS256, null, null, null, null, null, null, null, null, null, KEYID, null, null), claimsSet); - signedJWT.sign(signer); - return signedJWT.serialize(); - } - - private static class TestTokenValidationContextHolder implements TokenValidationContextHolder { - - TokenValidationContext tokenValidationContext = new TokenValidationContext(Collections.emptyMap()); - - @Override - public TokenValidationContext getTokenValidationContext() { - return tokenValidationContext; - } - - @Override - public void setTokenValidationContext(TokenValidationContext tokenValidationContext) { - this.tokenValidationContext = tokenValidationContext; - } - } - - static class MockResourceRetriever extends ProxyAwareResourceRetriever { - - final String[] mockedIssuers; - final Map keys = new HashMap<>(); - - MockResourceRetriever(String... mockedIssuers) { - this.mockedIssuers = mockedIssuers; - for (String iss : mockedIssuers) { - keys.put(iss, genkey()); - } - } - - RSAKey keysForIssuer(String issuer) { - return keys.get(issuer); - } - - private RSAKey genkey() { - try { - KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); - gen.initialize(2048); - KeyPair keyPair = gen.generateKeyPair(); - return new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) - .privateKey((RSAPrivateKey) keyPair.getPrivate()) - .keyID(KEYID).build(); - } catch (NoSuchAlgorithmException nsae) { - throw new RuntimeException(nsae); - } - } - - @Override - public Resource retrieveResource(URL url) throws IOException { - final String jkwsPrefix = "http://jwks"; - if (url.toString().startsWith(jkwsPrefix)) { - return retrieveJWKS(url.toString().substring(jkwsPrefix.length())); - } else if (Arrays.binarySearch(mockedIssuers, url.getHost()) >= 0) { - final String issuer = url.getHost(); - String content = getContentFromFile(); - content = content.replace("$ISSUER", issuer); - content = content.replace(jkwsPrefix, jkwsPrefix + issuer); - return new Resource(content, "application/json"); - } - throw new RuntimeException("dont know about issuer " + url); - } - - private String getContentFromFile() throws IOException { - return IOUtils.readInputStreamToString(getInputStream("/mockmetadata.json"), StandardCharsets.UTF_8); - } - - private InputStream getInputStream(String file) { - return JwtTokenValidationFilterTest.MockResourceRetriever.class.getResourceAsStream(file); - } - - Resource retrieveJWKS(String issuer) { - JWKSet set = new JWKSet(keys.get(issuer)); - String content = set.toString(); - return new Resource(content, "application/json"); - - } - } -} \ No newline at end of file diff --git a/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.kt b/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.kt new file mode 100644 index 00000000..8db49b86 --- /dev/null +++ b/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenExpiryFilterTest.kt @@ -0,0 +1,87 @@ +package no.nav.security.token.support.filter + +import com.nimbusds.jwt.JWT +import com.nimbusds.jwt.JWTClaimsSet.Builder +import com.nimbusds.jwt.PlainJWT +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import java.text.ParseException +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.Date +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.junit.jupiter.MockitoExtension +import no.nav.security.token.support.core.JwtTokenConstants +import no.nav.security.token.support.core.context.TokenValidationContext +import no.nav.security.token.support.core.context.TokenValidationContextHolder +import no.nav.security.token.support.core.jwt.JwtTokenClaims + +@ExtendWith(MockitoExtension::class) +internal class JwtTokenExpiryFilterTest { + + @Mock + private val servletRequest : HttpServletRequest? = null + + @Mock + private val filterChain : FilterChain? = null + + @Mock + private val servletResponse : HttpServletResponse? = null + private lateinit var tokenValidationContextHolder : TokenValidationContextHolder + @Test + fun tokenExpiresBeforeThreshold() { + setupMocks(LocalDateTime.now().plusMinutes(2)) + + val jwtTokenExpiryFilter = JwtTokenExpiryFilter(tokenValidationContextHolder, EXPIRY_THRESHOLD) + jwtTokenExpiryFilter.doFilter(servletRequest!!, servletResponse!!, filterChain!!) + verify(servletResponse).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true") + } + + @Test + fun tokenExpiresAfterThreshold() { + setupMocks(LocalDateTime.now().plusMinutes(3)) + + val jwtTokenExpiryFilter = JwtTokenExpiryFilter(tokenValidationContextHolder, EXPIRY_THRESHOLD) + jwtTokenExpiryFilter.doFilter(servletRequest!!, servletResponse!!, filterChain!!) + verify(servletResponse, never()).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true") + } + + @Test + fun noValidToken() { + val jwtTokenExpiryFilter = JwtTokenExpiryFilter(mock(TokenValidationContextHolder::class.java), + EXPIRY_THRESHOLD) + jwtTokenExpiryFilter.doFilter(servletRequest!!, servletResponse!!, filterChain!!) + verify(servletResponse, never()).setHeader(JwtTokenConstants.TOKEN_EXPIRES_SOON_HEADER, "true") + } + + private fun setupMocks(expiry : LocalDateTime) { + tokenValidationContextHolder = mock(TokenValidationContextHolder::class.java) + val tokenValidationContext = mock(TokenValidationContext::class.java) + `when`(tokenValidationContextHolder.getTokenValidationContext()).thenReturn(tokenValidationContext) + `when`(tokenValidationContext.issuers).thenReturn(listOf("issuer1")) + + val expiryDate = Date.from(expiry.atZone(ZoneId.systemDefault()).toInstant()) + `when`(tokenValidationContext.getClaims(anyString())).thenReturn(createOIDCClaims(expiryDate)) + } + + companion object { + + private const val EXPIRY_THRESHOLD : Long = 1 + + private fun createOIDCClaims(expiry : Date) = + try { + val jwt : JWT = PlainJWT(Builder() + .subject("subject") + .issuer("http//issuer1") + .expirationTime(expiry).build()) + JwtTokenClaims(jwt.jwtClaimsSet) + } + catch (e : ParseException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.kt b/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.kt new file mode 100644 index 00000000..62da3e82 --- /dev/null +++ b/token-validation-filter/src/test/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilterTest.kt @@ -0,0 +1,278 @@ +package no.nav.security.token.support.filter + +import com.nimbusds.jose.JOSEException +import com.nimbusds.jose.JWSAlgorithm +import com.nimbusds.jose.JWSHeader +import com.nimbusds.jose.JWSSigner +import com.nimbusds.jose.crypto.RSASSASigner +import com.nimbusds.jose.jwk.JWKSet +import com.nimbusds.jose.jwk.RSAKey +import com.nimbusds.jose.util.IOUtils +import com.nimbusds.jose.util.Resource +import com.nimbusds.jwt.JWTClaimsSet.Builder +import com.nimbusds.jwt.SignedJWT +import jakarta.servlet.FilterChain +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import java.io.IOException +import java.io.InputStream +import java.net.MalformedURLException +import java.net.URI +import java.net.URISyntaxException +import java.net.URL +import java.nio.charset.StandardCharsets +import java.security.KeyPairGenerator +import java.security.NoSuchAlgorithmException +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.util.Arrays +import java.util.Date +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.junit.jupiter.MockitoExtension +import no.nav.security.token.support.core.JwtTokenConstants +import no.nav.security.token.support.core.configuration.IssuerProperties +import no.nav.security.token.support.core.configuration.MultiIssuerConfiguration +import no.nav.security.token.support.core.configuration.ProxyAwareResourceRetriever +import no.nav.security.token.support.core.context.TokenValidationContext +import no.nav.security.token.support.core.context.TokenValidationContextHolder +import no.nav.security.token.support.core.validation.JwtTokenValidationHandler +import no.nav.security.token.support.filter.JwtTokenValidationFilter.Companion.fromHttpServletRequest + +@ExtendWith(MockitoExtension::class) +internal class JwtTokenValidationFilterTest { + + @Mock + private val servletRequest : HttpServletRequest? = null + + @Mock + private val servletResponse : HttpServletResponse? = null + + @Test + fun testSingleValidIdTokenInCookie() { + val issuername = "myissuer" + val issuerProps = createIssuerPropertiesMap(issuername, IDTOKENCOOKIENAME) + val mockResources = MockResourceRetriever(issuername) + val ctxHolder : TokenValidationContextHolder = TestTokenValidationContextHolder() + + val filter = createFilterToTest(issuerProps, mockResources, ctxHolder) + val jwt = createJWT(issuername, mockResources.keysForIssuer(issuername)!!.toRSAPrivateKey()) + + val filterCallCounter = intArrayOf(0) + + Mockito.`when`(servletRequest!!.cookies).thenReturn(arrayOf(Cookie("JSESSIONID", "ABCDEF"), Cookie(IDTOKENCOOKIENAME, jwt))) + filter.doFilter(servletRequest, servletResponse!!, + mockFilterchainAsserting(issuername, "foobar", ctxHolder, filterCallCounter)) + + Assertions.assertEquals(1, filterCallCounter[0], "doFilter should have been called once") + } + + @Test + fun testSingleValidIdTokenInHeader() { + val anotherIssuer = "anotherIssuer" + val issuerProps = createIssuerPropertiesMap(anotherIssuer, IDTOKENCOOKIENAME) + + val mockResources = MockResourceRetriever(anotherIssuer) + val ctxHolder : TokenValidationContextHolder = TestTokenValidationContextHolder() + val filter = createFilterToTest(issuerProps, mockResources, ctxHolder) + + val jwt = createJWT(anotherIssuer, mockResources.keysForIssuer(anotherIssuer)!!.toRSAPrivateKey()) + + val filterCallCounter = intArrayOf(0) + + Mockito.`when`(servletRequest!!.cookies).thenReturn(null) + Mockito.`when`(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer $jwt") + filter.doFilter(servletRequest, servletResponse!!, + mockFilterchainAsserting(anotherIssuer, "foobar", ctxHolder, filterCallCounter)) + + Assertions.assertEquals(1, filterCallCounter[0], "doFilter should have been called once") + } + + @Test + fun testTwoValidIdTokensWithDifferentIssuersInHeader() { + val issuer1 = "issuer1" + val anotherIssuer = "issuerNumberTwo" + val issuerProps : MutableMap = HashMap() + issuerProps.putAll(createIssuerPropertiesMap(issuer1, null)) + issuerProps.putAll(createIssuerPropertiesMap(anotherIssuer, null)) + + val mockResources = MockResourceRetriever(issuer1, anotherIssuer) + val ctxHolder : TokenValidationContextHolder = TestTokenValidationContextHolder() + val filter = createFilterToTest(issuerProps, mockResources, ctxHolder) + + val jwt1 = createJWT(issuer1, mockResources.keysForIssuer(issuer1)!!.toRSAPrivateKey()) + val jwt2 = createJWT(anotherIssuer, mockResources.keysForIssuer(anotherIssuer)!!.toRSAPrivateKey()) + + val filterCallCounter = intArrayOf(0) + + Mockito.`when`(servletRequest!!.cookies).thenReturn(null) + Mockito.`when`(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer $jwt1,Bearer $jwt2") + filter.doFilter(servletRequest, servletResponse!!, + mockFilterchainAsserting(arrayOf(issuer1, anotherIssuer), arrayOf("foobar", "foobar"), ctxHolder, filterCallCounter)) + + Assertions.assertEquals(1, filterCallCounter[0], "doFilter should have been called once") + } + + @Test + fun testRequestConverterShouldHandleWhenCookiesAreNULL() { + Mockito.`when`(servletRequest!!.cookies).thenReturn(null) + Mockito.`when`(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn(null) + + val req = fromHttpServletRequest(servletRequest) + Assertions.assertNull(req.cookies) + Assertions.assertNull(req.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)) + } + + @Test + fun testRequestConverterShouldConvertCorrectly() { + Mockito.`when`(servletRequest!!.cookies).thenReturn(arrayOf(Cookie("JSESSIONID", "ABCDEF"), Cookie("IDTOKEN", "THETOKEN"))) + Mockito.`when`(servletRequest.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)).thenReturn("Bearer eyAAA") + + val req = fromHttpServletRequest(servletRequest) + Assertions.assertEquals("JSESSIONID", req.cookies[0].name) + Assertions.assertEquals("ABCDEF", req.cookies[0].value) + Assertions.assertEquals("IDTOKEN", req.cookies[1].name) + Assertions.assertEquals("THETOKEN", req.cookies[1].value) + Assertions.assertEquals("Bearer eyAAA", req.getHeader(JwtTokenConstants.AUTHORIZATION_HEADER)) + } + + private fun mockFilterchainAsserting(issuer : String, subject : String, ctxHolder : TokenValidationContextHolder, + filterCallCounter : IntArray) : FilterChain { + return mockFilterchainAsserting(arrayOf(issuer), arrayOf(subject), ctxHolder, filterCallCounter) + } + + private fun mockFilterchainAsserting(issuers : Array, subjects : Array, ctxHolder : TokenValidationContextHolder, + filterCallCounter : IntArray) : FilterChain { + return FilterChain { servletRequest : ServletRequest?, servletResponse : ServletResponse? -> + // TokenValidationContext is nulled after filter-call, so we check it here: + filterCallCounter[0]++ + val ctx = ctxHolder.tokenValidationContext + Assertions.assertTrue(ctx.hasValidToken()) + Assertions.assertEquals(issuers.size, ctx.issuers.size) + for (i in issuers.indices) { + Assertions.assertTrue(ctx.hasTokenFor(issuers[i])) + Assertions.assertEquals(subjects[i], ctx.getClaims(issuers[i]).getStringClaim("sub")) + } + } + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + private fun createFilterToTest(issuerProps : Map, + mockResources : MockResourceRetriever, ctxHolder : TokenValidationContextHolder) : JwtTokenValidationFilter { + val conf = MultiIssuerConfiguration(issuerProps, mockResources) + val jwtTokenValidationHandler = JwtTokenValidationHandler(conf) + return JwtTokenValidationFilter(jwtTokenValidationHandler, ctxHolder) + } + + @Throws(URISyntaxException::class, MalformedURLException::class) + private fun createIssuerPropertiesMap(issuer : String, cookieName : String?) : Map { + val issuerPropertiesMap : MutableMap = HashMap() + issuerPropertiesMap[issuer] = IssuerProperties(URI("https://$issuer").toURL(), + listOf(AUDIENCE), + cookieName) + return issuerPropertiesMap + } + + @Throws(JOSEException::class) + private fun createJWT(issuer : String, signingKey : RSAPrivateKey) : String { + val now = Date() + val claimsSet = Builder() + .subject("foobar").issuer(issuer).audience(AUDIENCE).notBeforeTime(now).issueTime(now) + .expirationTime(Date(now.time + 3600)).build() + + val signer : JWSSigner = RSASSASigner(signingKey) + val signedJWT = SignedJWT( + JWSHeader(JWSAlgorithm.RS256, null, null, null, null, null, null, null, null, null, KEYID, null, null), claimsSet) + signedJWT.sign(signer) + return signedJWT.serialize() + } + + private class TestTokenValidationContextHolder : TokenValidationContextHolder { + + var ctx = TokenValidationContext(emptyMap()) + + override fun getTokenValidationContext() = ctx + + override fun setTokenValidationContext(tokenValidationContext : TokenValidationContext?) { + if (tokenValidationContext != null) { + this.ctx = tokenValidationContext + } + } + } + + internal class MockResourceRetriever(vararg mockedIssuers : String) : ProxyAwareResourceRetriever() { + + val mockedIssuers : Array + val keys : MutableMap = HashMap() + + init { + this.mockedIssuers = mockedIssuers + for (iss in mockedIssuers) { + keys[iss] = genkey() + } + } + + fun keysForIssuer(issuer : String) : RSAKey? { + return keys[issuer] + } + + private fun genkey() : RSAKey { + try { + val gen = KeyPairGenerator.getInstance("RSA") + gen.initialize(2048) + val keyPair = gen.generateKeyPair() + return RSAKey.Builder(keyPair.public as RSAPublicKey) + .privateKey(keyPair.private as RSAPrivateKey) + .keyID(KEYID).build() + } + catch (nsae : NoSuchAlgorithmException) { + throw RuntimeException(nsae) + } + } + + @Throws(IOException::class) + override fun retrieveResource(url : URL) : Resource { + val jkwsPrefix = "http://jwks" + if (url.toString().startsWith(jkwsPrefix)) { + return retrieveJWKS(url.toString().substring(jkwsPrefix.length)) + } + else if (Arrays.binarySearch(mockedIssuers, url.host) >= 0) { + val issuer = url.host + var content = contentFromFile + content = content.replace("\$ISSUER", issuer) + content = content.replace(jkwsPrefix, jkwsPrefix + issuer) + return Resource(content, "application/json") + } + throw RuntimeException("dont know about issuer $url") + } + + @get:Throws(IOException::class) + private val contentFromFile : String + get() = IOUtils.readInputStreamToString(getInputStream("/mockmetadata.json"), StandardCharsets.UTF_8) + + private fun getInputStream(file : String) : InputStream { + return MockResourceRetriever::class.java.getResourceAsStream(file) + } + + fun retrieveJWKS(issuer : String) : Resource { + val set = JWKSet(keys[issuer]) + val content = set.toString() + return Resource(content, "application/json") + } + } + + companion object { + + private const val KEYID = "myKeyId" + private const val AUDIENCE = "aud1" + private const val IDTOKENCOOKIENAME = "idtokencookie" + } +} \ No newline at end of file From a2a4d4c529246c45b7a6ea0ba205099b3efceee0 Mon Sep 17 00:00:00 2001 From: Jan-Olav Eide Date: Sun, 3 Dec 2023 10:21:43 +0100 Subject: [PATCH 2/3] convert filters to kotlin --- .../context/TokenValidationContextHolder.java | 3 +- .../validation/JwtTokenAnnotationHandler.java | 2 +- token-validation-spring-test/pom.xml | 53 +++++++++++++++++-- .../EnableJwtTokenValidationConfiguration.kt | 2 +- .../SpringJwtTokenAnnotationHandler.kt | 2 +- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/token-validation-core/src/main/java/no/nav/security/token/support/core/context/TokenValidationContextHolder.java b/token-validation-core/src/main/java/no/nav/security/token/support/core/context/TokenValidationContextHolder.java index 0b515a44..9631e588 100644 --- a/token-validation-core/src/main/java/no/nav/security/token/support/core/context/TokenValidationContextHolder.java +++ b/token-validation-core/src/main/java/no/nav/security/token/support/core/context/TokenValidationContextHolder.java @@ -1,8 +1,9 @@ package no.nav.security.token.support.core.context; + public interface TokenValidationContextHolder { TokenValidationContext getTokenValidationContext(); void setTokenValidationContext(TokenValidationContext tokenValidationContext); -} +} \ No newline at end of file diff --git a/token-validation-core/src/main/java/no/nav/security/token/support/core/validation/JwtTokenAnnotationHandler.java b/token-validation-core/src/main/java/no/nav/security/token/support/core/validation/JwtTokenAnnotationHandler.java index a3d3512a..8f05cb56 100755 --- a/token-validation-core/src/main/java/no/nav/security/token/support/core/validation/JwtTokenAnnotationHandler.java +++ b/token-validation-core/src/main/java/no/nav/security/token/support/core/validation/JwtTokenAnnotationHandler.java @@ -118,7 +118,7 @@ protected boolean handleProtectedWithClaimsAnnotation(ProtectedWithClaims a, Jwt } protected boolean handleProtectedWithClaims(String issuer, String[] requiredClaims, boolean combineWithOr, JwtToken jwtToken) { - if (Objects.nonNull(issuer) && issuer.length() > 0) { + if (Objects.nonNull(issuer) && !issuer.isEmpty()) { return containsRequiredClaims(jwtToken, combineWithOr, requiredClaims); } return true; diff --git a/token-validation-spring-test/pom.xml b/token-validation-spring-test/pom.xml index 255d8abd..6ef79f43 100644 --- a/token-validation-spring-test/pom.xml +++ b/token-validation-spring-test/pom.xml @@ -10,6 +10,12 @@ 4.0.0 token-validation-spring-test + + + 1.9.21 + + + org.springframework.boot @@ -40,13 +46,20 @@ spring-boot-starter-test test + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + - - org.apache.maven.plugins - maven-compiler-plugin - org.apache.maven.plugins maven-source-plugin @@ -55,6 +68,38 @@ org.apache.maven.plugins maven-javadoc-plugin + + org.jetbrains.kotlin + kotlin-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + diff --git a/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/EnableJwtTokenValidationConfiguration.kt b/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/EnableJwtTokenValidationConfiguration.kt index 500f0d68..c607cc27 100644 --- a/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/EnableJwtTokenValidationConfiguration.kt +++ b/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/EnableJwtTokenValidationConfiguration.kt @@ -64,7 +64,7 @@ class EnableJwtTokenValidationConfiguration (private val env: Environment) : Web fun requestContextListener() = RequestContextListener() @Bean - fun tokenValidationFilter(config: MultiIssuerConfiguration?, h: TokenValidationContextHolder?) = JwtTokenValidationFilter(JwtTokenValidationHandler(config), h) + fun tokenValidationFilter(config: MultiIssuerConfiguration?, h: TokenValidationContextHolder) = JwtTokenValidationFilter(JwtTokenValidationHandler(config), h) @Bean @ConditionalOnProperty(EXPIRY_THRESHOLD_ENV_PROPERTY) diff --git a/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/validation/interceptor/SpringJwtTokenAnnotationHandler.kt b/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/validation/interceptor/SpringJwtTokenAnnotationHandler.kt index 3aa427df..a92a2faf 100644 --- a/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/validation/interceptor/SpringJwtTokenAnnotationHandler.kt +++ b/token-validation-spring/src/main/kotlin/no/nav/security/token/support/spring/validation/interceptor/SpringJwtTokenAnnotationHandler.kt @@ -11,5 +11,5 @@ class SpringJwtTokenAnnotationHandler(holder: TokenValidationContextHolder?) : J findAnnotation(m, types) ?: findAnnotation(m.declaringClass, types) private fun findAnnotation(e: AnnotatedElement, types: List>) = - types.firstNotNullOfOrNull { t: Class -> findMergedAnnotation(e, t) } + types.firstNotNullOfOrNull { findMergedAnnotation(e, it) } } \ No newline at end of file From 282c943364a8b6f5809d2526d03d888efe6611c5 Mon Sep 17 00:00:00 2001 From: Jan-Olav Eide Date: Sun, 3 Dec 2023 10:23:57 +0100 Subject: [PATCH 3/3] convert filters to kotlin --- .../security/token/support/filter/JwtTokenValidationFilter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt index 4801839e..01f1ec6d 100644 --- a/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt +++ b/token-validation-filter/src/main/kotlin/no/nav/security/token/support/filter/JwtTokenValidationFilter.kt @@ -12,7 +12,7 @@ import no.nav.security.token.support.core.http.HttpRequest import no.nav.security.token.support.core.http.HttpRequest.NameValue import no.nav.security.token.support.core.validation.JwtTokenValidationHandler -class JwtTokenValidationFilter(private val jwtTokenValidationHandler : JwtTokenValidationHandler, private val contextHolder : TokenValidationContextHolder) : Filter { +open class JwtTokenValidationFilter(private val jwtTokenValidationHandler : JwtTokenValidationHandler, private val contextHolder : TokenValidationContextHolder) : Filter { override fun destroy() {}