Skip to content

Commit

Permalink
Fix missing dependencies and licenses. Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
enricovianello committed Mar 13, 2024
1 parent e0d36df commit 937db49
Show file tree
Hide file tree
Showing 12 changed files with 508 additions and 22 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@
<artifactId>logback-classic</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
Expand Down
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
@@ -1,5 +1,5 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2012-2019.
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2012-2019.
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2012-2019.
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import java.util.Map;

import org.slf4j.MDC;
import org.apache.log4j.MDC;

import com.google.common.base.Splitter;
import com.google.common.base.Splitter.MapSplitter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed 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.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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed 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.italiangrid.storm.webdav.test.oauth.jwk;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.lenient;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.io.FileUtils;
import org.italiangrid.storm.webdav.config.OAuthProperties;
import org.italiangrid.storm.webdav.config.ServiceConfigurationProperties;
import org.italiangrid.storm.webdav.config.ServiceConfigurationProperties.AuthorizationServerProperties;
import org.italiangrid.storm.webdav.config.OAuthProperties.AuthorizationServer;
import org.italiangrid.storm.webdav.oauth.utils.OidcConfigurationFetcher;
import org.italiangrid.storm.webdav.oauth.utils.TrustedJwtDecoderCacheLoader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ListenableFuture;
import com.nimbusds.jose.RemoteKeySourceException;

@ExtendWith(MockitoExtension.class)
public class TrustedJwtDecoderCacheLoaderTests {

private final String ISSUER = "https://wlcg.cloud.cnaf.infn.it/";
private final String JWK_URI = "https://wlcg.cloud.cnaf.infn.it/jwks";

@Mock
ServiceConfigurationProperties properties;
@Mock
OAuthProperties oauthProperties;
@Mock
RestTemplateBuilder builder;
@Mock
OidcConfigurationFetcher fetcher;

private ExecutorService executor;
private TrustedJwtDecoderCacheLoader jwtLoader;

@BeforeEach
public void setup() throws IOException, RemoteKeySourceException {

AuthorizationServer as = new AuthorizationServer();
as.setIssuer(ISSUER);
as.setJwkUri(JWK_URI);
List<AuthorizationServer> issuerServers = Lists.newArrayList(as);
lenient().when(oauthProperties.getIssuers()).thenReturn(issuerServers);

Map<String, Object> oidcConfiguration = Maps.newHashMap();
oidcConfiguration.put("issuer", ISSUER);
oidcConfiguration.put("jwks_uri", JWK_URI);

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("jwk/test-keystore.jwks").getFile());
String data = FileUtils.readFileToString(file, "UTF-8");

lenient().when(fetcher.loadConfigurationForIssuer(ISSUER)).thenReturn(oidcConfiguration);
lenient().when(fetcher.loadJWKSourceForURL(URI.create(JWK_URI))).thenReturn(data);

AuthorizationServerProperties props = new AuthorizationServerProperties();
props.setEnabled(false);
props.setIssuer("http://localhost");
lenient().when(properties.getAuthzServer()).thenReturn(props);

executor = Executors.newScheduledThreadPool(1);

jwtLoader =
new TrustedJwtDecoderCacheLoader(properties, oauthProperties, builder, fetcher, executor);

}

@Test
public void testLoadRemoteIssuerConfiguration() throws Exception {

JwtDecoder decoder = jwtLoader.load(ISSUER);
assertTrue(decoder instanceof NimbusJwtDecoder);
ListenableFuture<JwtDecoder> reloaded = jwtLoader.reload(ISSUER, decoder);
JwtDecoder newDecoder = reloaded.get();
assertTrue(newDecoder instanceof NimbusJwtDecoder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.italiangrid.storm.webdav.test.oauth.jwt;

import static org.junit.Assert.assertEquals;

import org.italiangrid.storm.webdav.oauth.utils.NoExpirationStringCache;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class NoExpirationStringCacheTests {

final String CACHED_VALUE = "this-is-my-cached-value";
final String FAKE_ISSUER = "http://localhost";

@Test
public void noExpirationCacheWorks() {

NoExpirationStringCache cache = new NoExpirationStringCache(CACHED_VALUE);

assertEquals("NoExpirationCache", cache.getName());
assertEquals(cache, cache.getNativeCache());
assertEquals(CACHED_VALUE, cache.get(FAKE_ISSUER).get());
cache.clear();
cache.put(FAKE_ISSUER, CACHED_VALUE);
assertEquals(CACHED_VALUE, cache.get(FAKE_ISSUER).get());
cache.evict(FAKE_ISSUER);
assertEquals(CACHED_VALUE, cache.get(FAKE_ISSUER).get());
}
}
Loading

0 comments on commit 937db49

Please sign in to comment.