Skip to content

Commit

Permalink
Add remote JWK and configuration retrieving tests
Browse files Browse the repository at this point in the history
  • Loading branch information
enricovianello committed Mar 11, 2024
1 parent 51b8b55 commit 2b5a106
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ public class DefaultOidcConfigurationFetcher implements OidcConfigurationFetcher
public static final String WELL_KNOWN_FRAGMENT = "/.well-known/openid-configuration";
public static final String ISSUER_MISMATCH_ERROR_TEMPLATE =
"Issuer in metadata '%s' does not match with requested issuer '%s'";
public static final String NO_JWKS_URI_ERROR_TEMPLATE =
public static final String NO_JWKS_URI_ERROR_TEMPLATE =
"No jwks_uri found in metadata for issuer '%s'";

private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json");
private static final MediaType APPLICATION_JWK_SET_JSON =
new MediaType("application", "jwk-set+json");

public static final Logger LOG = LoggerFactory.getLogger(DefaultOidcConfigurationFetcher.class);

final RestTemplateBuilder restBuilder;
final OAuthProperties oAuthProperties;
final RestTemplate restTemplate;

public DefaultOidcConfigurationFetcher(RestTemplateBuilder restBuilder,
OAuthProperties oAuthProperties) {
this.restBuilder = restBuilder;
this.oAuthProperties = oAuthProperties;
final Duration TIMEOUT = Duration.ofSeconds(oAuthProperties.getRefreshTimeoutSeconds());
this.restTemplate = restBuilder.setConnectTimeout(TIMEOUT).setReadTimeout(TIMEOUT).build();
}

private void metadataChecks(String issuer, Map<String, Object> oidcConfiguration) {
Expand All @@ -70,29 +70,27 @@ private void metadataChecks(String issuer, Map<String, Object> oidcConfiguration
throw new OidcConfigurationResolutionError(
format(ISSUER_MISMATCH_ERROR_TEMPLATE, metadataIssuer, issuer));
}

if (!oidcConfiguration.containsKey("jwks_uri")) {
throw new OidcConfigurationResolutionError(format(NO_JWKS_URI_ERROR_TEMPLATE,issuer));
throw new OidcConfigurationResolutionError(format(NO_JWKS_URI_ERROR_TEMPLATE, issuer));
}
}

@Override
public Map<String, Object> loadConfigurationForIssuer(String issuer) {
LOG.debug("Fetching OpenID configuration for {}", issuer);

ParameterizedTypeReference<Map<String, Object>> typeReference =
new ParameterizedTypeReference<Map<String, Object>>() {};

RestTemplate rest = restBuilder.build();

URI uri = UriComponentsBuilder.fromUriString(issuer + WELL_KNOWN_FRAGMENT).build().toUri();

try {

RequestEntity<Void> request = RequestEntity.get(uri).build();
Map<String, Object> conf = rest.exchange(request, typeReference).getBody();
Map<String, Object> conf = restTemplate.exchange(request, typeReference).getBody();
metadataChecks(issuer, conf);
return conf;
return conf;
} catch (RuntimeException e) {
final String errorMsg =
format("Unable to resolve OpenID configuration for issuer '%s' from '%s': %s", issuer,
Expand All @@ -111,17 +109,15 @@ public String loadJWKSourceForURL(URI uri) throws RemoteKeySourceException {

LOG.debug("Fetching JWK from {}", uri);

final Duration TIMEOUT = Duration.ofSeconds(oAuthProperties.getRefreshTimeoutSeconds());
RestTemplate rest = restBuilder.setConnectTimeout(TIMEOUT).setReadTimeout(TIMEOUT).build();

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON));
ResponseEntity<String> response = null;
try {
RequestEntity<Void> request = RequestEntity.get(uri).headers(headers).build();
response = rest.exchange(request, String.class);
response = restTemplate.exchange(request, String.class);
if (response.getStatusCodeValue() != 200) {
throw new RuntimeException(format("Received status code: %s", response.getStatusCodeValue()));
throw new RuntimeException(
format("Received status code: %s", response.getStatusCodeValue()));
}
} catch (RuntimeException e) {
final String errorMsg = format("Unable to get JWK from '%s'", uri);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.italiangrid.storm.webdav.test.oauth.jwk;

import static org.mockito.Mockito.lenient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import org.italiangrid.storm.webdav.config.OAuthProperties;
import org.italiangrid.storm.webdav.oauth.CompositeJwtDecoder;
import org.italiangrid.storm.webdav.oauth.utils.OidcConfigurationFetcher;
import org.italiangrid.storm.webdav.oauth.utils.TrustedJwtDecoderCacheLoader;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.oauth2.jwt.JwtDecoder;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import com.nimbusds.jose.jwk.JWKSet;

@ExtendWith(MockitoExtension.class)
public class JWKCachingTests {

public static final Instant NOW = Instant.parse("2018-01-01T00:00:00.00Z");

Clock fixedClock = Clock.fixed(NOW, ZoneId.systemDefault());

@Mock
TrustedJwtDecoderCacheLoader loader;

@Mock
OidcConfigurationFetcher fetcher;

@Mock
ExecutorService executor;

@Mock
OAuthProperties oauthProperties;

private JWKSet getTestJWKSet() throws IOException, ParseException {

String data =
new String(getClass().getResourceAsStream("jwk/test-keystore.jwks").readAllBytes());
return JWKSet.parse(data);
}

public void testRefreshPeriodExpired() {


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.italiangrid.storm.webdav.test.oauth.jwk;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.text.ParseException;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.italiangrid.storm.webdav.config.OAuthProperties;
import org.italiangrid.storm.webdav.oauth.utils.DefaultOidcConfigurationFetcher;
import org.italiangrid.storm.webdav.oauth.utils.OidcConfigurationFetcher;
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 org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.google.common.collect.Maps;
import com.nimbusds.jose.RemoteKeySourceException;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;

@ExtendWith(MockitoExtension.class)
public class OidcConfigurationFetcherTests {

final static String ISSUER = "https://iam-dev.cloud.cnaf.infn.it/";
final static String JWK_URI = "https://iam-dev.cloud.cnaf.infn.it/jwk";

final static String KID = "rsa1";


final ParameterizedTypeReference<Map<String, Object>> typeReference =
new ParameterizedTypeReference<Map<String, Object>>() {};

@Mock
RestTemplate restTemplate;
@Mock
RestTemplateBuilder restBuilder;
@Mock
OAuthProperties oAuthProperties;

@SuppressWarnings("unchecked")
private ResponseEntity<Map<String, Object>> getMockedResponseFromWellKnown() {

Map<String, Object> wellKnownMap = Maps.newHashMap();
wellKnownMap.put("issuer", ISSUER);
wellKnownMap.put("jwks_uri", JWK_URI);
ResponseEntity<Map<String, Object>> mockedEntity = (ResponseEntity<Map<String, Object>>) Mockito.mock(ResponseEntity.class);
lenient().when(mockedEntity.getBody()).thenReturn(wellKnownMap);
return mockedEntity;
}

@SuppressWarnings("unchecked")
private ResponseEntity<String> getMockedResponseFromJWKURI() throws IOException {

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("jwk/test-keystore.jwks").getFile());
String data = FileUtils.readFileToString(file, "UTF-8");
ResponseEntity<String> mockedEntity = (ResponseEntity<String>) Mockito.mock(ResponseEntity.class);
lenient().when(mockedEntity.getBody()).thenReturn(data);
lenient().when(mockedEntity.getStatusCodeValue()).thenReturn(200);
return mockedEntity;
}

private OidcConfigurationFetcher getFetcher() throws RestClientException, IOException {

ResponseEntity<Map<String, Object>> mockedResponseMapEntity = getMockedResponseFromWellKnown();
lenient().when(restTemplate.exchange(any(), eq(typeReference))).thenReturn(mockedResponseMapEntity);
ResponseEntity<String> mockedResponseStringEntity = getMockedResponseFromJWKURI();
lenient().when(restTemplate.exchange(any(), eq(String.class))).thenReturn(mockedResponseStringEntity);

lenient().when(restBuilder.build()).thenReturn(restTemplate);
lenient().when(restBuilder.setConnectTimeout(any())).thenReturn(restBuilder);
lenient().when(restBuilder.setReadTimeout(any())).thenReturn(restBuilder);
lenient().when(oAuthProperties.getRefreshTimeoutSeconds()).thenReturn(30);
lenient().when(oAuthProperties.getRefreshPeriodMinutes()).thenReturn(1);

return new DefaultOidcConfigurationFetcher(restBuilder, oAuthProperties);
}

@Test
public void fetchWellKnownEndpointTests() throws RestClientException, IOException {

OidcConfigurationFetcher fetcher = getFetcher();
Map<String, Object> conf = fetcher.loadConfigurationForIssuer(ISSUER);
assertNotNull(conf);
assertThat(conf.get("issuer"), is(ISSUER));
assertThat(conf.get("jwks_uri"), is(JWK_URI));
}

@Test
public void fetchJWKEndpointTests() throws RestClientException, IOException, RemoteKeySourceException, ParseException {

OidcConfigurationFetcher fetcher = getFetcher();
JWKSet key = JWKSet.parse(fetcher.loadJWKSourceForURL(URI.create(JWK_URI)));

assertNotNull(key.getKeyByKeyId(KID));
assertThat(key.getKeyByKeyId(KID).getKeyType(), is(KeyType.RSA));
}
}
16 changes: 16 additions & 0 deletions src/test/resources/jwk/test-keystore.jwks
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"keys": [
{
"p": "-qdvzeHU7w_ToV2RlS2QlVggXNL2YfpRWQxvrO8pHZC_dVgYFwKz5nadOMzR1BK0tPuCTWuuI66sFgaA9VENGypdIYoCF2O1FBLFK6GjOO-uc0LZEbIDa6Xn0G7UYOWcLaiYriHTtC_Pzp11L7VGjrUlX4HRgU_B3X1oeGn0mbM",
"kty": "RSA",
"q": "5S2b9tYHi9zBcNGZ8X6GM4TAL4UU9mABH0rKIyzbudkG7Wxxbj6I18skuHzfOOPI4c8sTQSv6IVAr2n1bn3_E5RSyPpbtDSCTYGzhijXl9wZ0ba2NidFrVjnL-KPx_gcHKnUHebKvsIEdjxeuqaaZ1kqEJX326b450Frghd78p8",
"d": "oDb1qfQTaP73jEZHgkOG5C9dY5EJZW57fX_BYUJ-yYTuTHPWZBVDKw9I_Ir1tSYyuTF-Bfb4iPim46gnEBM3AdvMian2iajvrN_rJFUJHo65vtY9xCXCD0d_Jct5JMyOafP5LF3cP38yDcyZRS_JeyKGB6U1KhbL-gG4hrQGS8qO3rdY_JQiLDLVdRRptHsPphS44JHXdP2qeVNJ41-CTfPWKiMIUOC0fj-As-dbTzRXuLXs04NayAdM-yhvRiwKujEfL8YbKW9CDJIgJfm2vzWHXFus5Y11S2Zr65cWxxVvRfnAFbFO1AkIkJc2jHZ4xLxfDU2kTi20sOMq1UFrvQ",
"e": "AQAB",
"kid": "rsa1",
"qi": "bN1wnq_0VkJlECMGPmeRFdZCX2LgAMrgwbJpysRw5J04vO9YsVmAcB_4xqoDjDUg7koioAp3IOhMGjOJWpYzCqWzsaA_84kX4WKGsr2xz6oaFSgt1FvtBY4GqEeZj8RG0LMtEtKSyjHGieA0hd4TUcqSdNZ4osT98Bfd7z3peYc",
"dp": "YfyaxI2IRHyXavm9M-hAIWH2JNOD5gGJU5p8_cnw9NHlRuZNZJF16p5sEAxh6tn1MtsvsTxrMx_RvjqEp2IsEXaaOcZN0v7zhwlfcxMZT-TC-eQkH7rLg4Wz_dOVytt4FpFWPpySuloGjusXKLNhBeDi31dMo5SeYQvpj0k8iek",
"dq": "9_lhyLPNdohmxqwE5kkA7L23NbPJ-svmavWBwo3HMlCiLkQoeCEx8EzebsCux9-wfKSuSqfHrtCALU15QxUR6x2SdeRvVY17cGHm3kNTA_4j8cbBYdccjXSksitzZ-wOfvVDjxcqST2llkm8NjoO18Siv0-F4SXKLG-c5CaE9w",
"n": "4GRvJuFantVV3JdjwQOAkfREnwUFp2znRBTOIJhPamyH4gf4YlI5PQT79415NV4_HrWYzgooH5AK6-7WE-TLLGEAVK5vdk4vv79bG7ukvjvBPxAjEhQn6-Amln88iXtvicEGbh--3CKbQj1jryVU5aWM6jzweaabFSeCILVEd6ZT7ofXaAqan9eLzU5IEtTPy5MfrrOvWw5Q7D2yzMqc5LksmaQSw8XtmhA8gnENnIqjAMmPtRltf93wjtmiamgVENOVPdN-93Nd5w-pnMwEyoO6Q9JqXxV6lD6qBRxI7_5t4_vmVxcbbxcZbSAMoHqA2pbSMJ4Jcw-27Hct9jesLQ"
}
]
}

0 comments on commit 2b5a106

Please sign in to comment.