diff --git a/pom.xml b/pom.xml
index ac329f11..562e14e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
2.7.18
+ 2.5.2.RELEASE
italiangrid_storm-webdav
@@ -264,6 +265,12 @@
test
+
+ org.springframework.security.oauth
+ spring-security-oauth2
+ ${spring-security-oauth2.version}
+
+
org.springframework.boot
spring-boot-starter-thymeleaf
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
index 578bee2d..f8452aac 100644
--- a/src/main/resources/application-test.yml
+++ b/src/main/resources/application-test.yml
@@ -1,9 +1,58 @@
+spring:
+ main:
+ banner-mode: "off"
+ security:
+ oauth2:
+ client:
+ provider:
+ wlcg:
+ issuer-uri: https://wlcg.cloud.cnaf.infn.it/
+ registration:
+ wlcg:
+ provider: wlcg
+ authorization-grant-type: authorization_code
+ client-name: WLCG IAM
+ client-id: xfer.cr.cnaf.infn.it
+ client-secret: ANR7a3cYRFfXV3r8CXHVzA7xYSXFA7gQ6kEcQBKEESvJrdBXYZMaSaLXAlp3RXd5dfWAs1b1K4EGxbEJWTH4coU
+ scope:
+ - openid
+ - profile
+ - wlcg.groups
+
+ session:
+ store-type: none
+
+server:
+ jetty:
+ accesslog:
+ enabled: false
oauth:
-
+ enable-oidc: true
+ refresh-period-minutes: 1
issuers:
- - name: iam-test
- issuer: https://iam-test.indigo-datacloud.eu/
- - name: iam-wlcg
+ - name: wlcg
issuer: https://wlcg.cloud.cnaf.infn.it/
- - name: iam-local
- issuer: https://iam.local.io/
\ No newline at end of file
+ enforce-audience-checks: true
+ audiences:
+ - https://wlcg.cern.ch/jwt/v1/any
+ - https://xfer.cr.cnaf.infn.it:8443
+
+storm:
+ connector:
+ port: 8086
+ securePort: 9443
+ sa:
+ config-dir: src/test/resources/conf/sa.d
+ tls:
+ trust-anchors-dir: src/test/resources/trust-anchors
+ certificate-path: src/test/resources/hostcert/hostcert.pem
+ private-key-path: src/test/resources/hostcert/hostkey.pem
+ authz-server:
+ enabled: true
+ voms:
+ trust-store:
+ dir: src/test/resources/vomsdir
+ tape:
+ well-known:
+ source: src/test/resources/well-known/wlcg-tape-rest-api.json
+
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8761ed40..0d61b1d7 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -50,3 +50,5 @@ logging.level.org.italiangrid.storm=INFO
#logging.level.org.eclipse.jetty.io=ERROR
#logging.level.org.eclipse.jetty.server=DEBUG
#logging.level.org.eclipse.jetty.server.HttpOutput=ERROR
+
+logging.level.org.springframework.security.oauth2.jwt.NimbusJwtDecoder=DEBUG
\ No newline at end of file
diff --git a/src/test/java/org/italiangrid/storm/webdav/test/oauth/jwk/JWKCachingTests.java b/src/test/java/org/italiangrid/storm/webdav/test/oauth/jwk/JWKCachingTests.java
index 626f8189..8b134ec9 100644
--- a/src/test/java/org/italiangrid/storm/webdav/test/oauth/jwk/JWKCachingTests.java
+++ b/src/test/java/org/italiangrid/storm/webdav/test/oauth/jwk/JWKCachingTests.java
@@ -15,61 +15,122 @@
*/
package org.italiangrid.storm.webdav.test.oauth.jwk;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.text.ParseException;
+import java.net.URI;
+import java.nio.file.Path;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
-import org.italiangrid.storm.webdav.config.OAuthProperties;
-import org.italiangrid.storm.webdav.oauth.CompositeJwtDecoder;
+import org.apache.commons.io.FileUtils;
+import org.italiangrid.storm.webdav.authz.VOMSAuthenticationFilter;
+import org.italiangrid.storm.webdav.fs.attrs.ExtendedAttributesHelper;
+import org.italiangrid.storm.webdav.oauth.StormJwtAuthoritiesConverter;
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.italiangrid.storm.webdav.server.servlet.MiltonFilter;
+import org.italiangrid.storm.webdav.test.utils.oauth.WithMockOAuthUser;
+import org.italiangrid.storm.webdav.test.utils.voms.WithMockVOMSUser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mockito;
-import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.LoadingCache;
-import com.nimbusds.jose.jwk.JWKSet;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.RemoteKeySourceException;
-@ExtendWith(MockitoExtension.class)
+import io.lettuce.core.GetExArgs.Builder;
+import io.milton.http.HttpManager;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("jwk-test")
public class JWKCachingTests {
public static final Instant NOW = Instant.parse("2018-01-01T00:00:00.00Z");
+ public static final String SLASH_WLCG_SLASH_FILE = "/wlcg/example";
+ public static final String ISSUER = "https://wlcg.cloud.cnaf.infn.it/";
+ public static final URI JWKS_URI = URI.create("https://wlcg.cloud.cnaf.infn.it/jwk");
+
Clock fixedClock = Clock.fixed(NOW, ZoneId.systemDefault());
+ ObjectMapper mapper = new ObjectMapper();
- @Mock
- TrustedJwtDecoderCacheLoader loader;
+ @Autowired
+ private MockMvc mvc;
- @Mock
+ @MockBean
OidcConfigurationFetcher fetcher;
- @Mock
- ExecutorService executor;
+ @MockBean
+ JwtDecoder decoder;
+
+ @MockBean
+ ExtendedAttributesHelper helper;
+
+ @Autowired
+ private VOMSAuthenticationFilter filter;
- @Mock
- OAuthProperties oauthProperties;
+ @Autowired
+ private FilterRegistrationBean miltonFilter;
+
+ @SuppressWarnings("unchecked")
+ @BeforeEach
+ public void setup() throws IOException, RemoteKeySourceException {
- private JWKSet getTestJWKSet() throws IOException, ParseException {
+ ClassLoader classLoader = getClass().getClassLoader();
+ String jwks = FileUtils.readFileToString(new File(classLoader.getResource("jwk-test/well-known/jwk/keystore.jwks").getFile()), "UTF-8");
+ String oidcConfiguration = FileUtils.readFileToString(new File(classLoader.getResource("jwk-test/well-known/opeind-configuration").getFile()), "UTF-8");
+ Map configurationMap = (Map) mapper.readValue(oidcConfiguration, Map.class);
+ configurationMap.entrySet().forEach(e -> System.out.println(e.getKey() + "-" + e.getValue()));
+ lenient().when(fetcher.loadConfigurationForIssuer(eq(ISSUER))).thenReturn(configurationMap);
+ lenient().when(fetcher.loadJWKSourceForURL(eq(JWKS_URI))).thenReturn(jwks);
+
+ lenient().when(helper.getChecksumAttribute(Mockito.any(File.class))).thenReturn("");
+ lenient().when(helper.getChecksumAttribute(Mockito.any(Path.class))).thenReturn("");
- String data =
- new String(getClass().getResourceAsStream("jwk/test-keystore.jwks").readAllBytes());
- return JWKSet.parse(data);
+ filter.setCheckForPrincipalChanges(false);
+
+ HttpManager httpManager = Mockito.mock(HttpManager.class);
+ miltonFilter.getFilter().setMiltonHTTPManager(httpManager);
}
- public void testRefreshPeriodExpired() {
+ @Test
+ public void testRefreshPeriodExpired() throws Exception {
+
+ Jwt token = Jwt.withTokenValue("test")
+ .header("kid", "rsa1")
+ .issuer(ISSUER)
+ .subject("123")
+ .claim("scope", "storage.read:/ storage.modify:/")
+ .build();
-
+ Mockito.verify(fetcher, VerificationModeFactory.times(1)).loadConfigurationForIssuer(ISSUER);
+ mvc.perform(put("/wlcg/test").with(jwt().jwt(token))).andExpect(status().isOk());
+ Mockito.verify(fetcher, VerificationModeFactory.times(1)).loadJWKSourceForURL(JWKS_URI);
+ Thread.sleep(61000); // 61 seconds
+ mvc.perform(get(SLASH_WLCG_SLASH_FILE)).andExpect(status().isOk());
+ Mockito.verify(fetcher, VerificationModeFactory.times(2)).loadJWKSourceForURL(JWKS_URI);
}
}
diff --git a/src/test/java/org/italiangrid/storm/webdav/test/utils/ThreadPoolBuilderTests.java b/src/test/java/org/italiangrid/storm/webdav/test/utils/ThreadPoolBuilderTests.java
new file mode 100644
index 00000000..44fc9e49
--- /dev/null
+++ b/src/test/java/org/italiangrid/storm/webdav/test/utils/ThreadPoolBuilderTests.java
@@ -0,0 +1,98 @@
+/**
+ * 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.utils;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.italiangrid.storm.webdav.server.util.ThreadPoolBuilder.DEFAULT_IDLE_TIMEOUT;
+import static org.italiangrid.storm.webdav.server.util.ThreadPoolBuilder.DEFAULT_MAX_THREADS;
+import static org.italiangrid.storm.webdav.server.util.ThreadPoolBuilder.DEFAULT_MIN_THREADS;
+import static org.italiangrid.storm.webdav.server.util.ThreadPoolBuilder.DEFAULT_THREAD_POOL_METRIC_NAME;
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.italiangrid.storm.webdav.server.util.ThreadPoolBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool;
+
+@ExtendWith(MockitoExtension.class)
+public class ThreadPoolBuilderTests {
+
+ final static String PREFIX = "iam";
+ final static String NAME = "queue";
+ final static int IDLE_TIMEOUT = 6;
+ final static int MIN_THREADS = 10;
+ final static int MAX_THREADS = 20;
+ final static int MAX_QUEUE_SIZE = 40;
+
+ @Mock
+ MetricRegistry registry;
+
+ @Test
+ public void threadPoolBuilderTests() {
+
+ ThreadPool tp = ThreadPoolBuilder.instance()
+ .withMinThreads(MIN_THREADS)
+ .withMaxThreads(MAX_THREADS)
+ .withIdleTimeoutMsec(IDLE_TIMEOUT)
+ .withMaxRequestQueueSize(MAX_QUEUE_SIZE)
+ .withName(NAME)
+ .withPrefix(PREFIX)
+ .build();
+ assertNotNull(tp);
+ assertThat(tp.getClass(), is(QueuedThreadPool.class));
+ QueuedThreadPool qtp = (QueuedThreadPool) tp;
+ assertThat(qtp.getMinThreads(), is(MIN_THREADS));
+ assertThat(qtp.getMaxThreads(), is(MAX_THREADS));
+ assertThat(qtp.getIdleTimeout(), is(IDLE_TIMEOUT));
+ assertThat(qtp.getMaxAvailableThreads(), is(MAX_THREADS));
+ assertThat(qtp.getName(), is(NAME));
+ }
+
+ @Test
+ public void threadPoolBuilderWithDefaultValuesTests() {
+
+ ThreadPool tp = ThreadPoolBuilder.instance().build();
+ assertNotNull(tp);
+ assertThat(tp.getClass(), is(QueuedThreadPool.class));
+ QueuedThreadPool qtp = (QueuedThreadPool) tp;
+ assertThat(qtp.getMinThreads(), is(DEFAULT_MIN_THREADS));
+ assertThat(qtp.getMaxThreads(), is(DEFAULT_MAX_THREADS));
+ assertThat(qtp.getIdleTimeout(), is(DEFAULT_IDLE_TIMEOUT));
+ assertThat(qtp.getMaxAvailableThreads(), is(DEFAULT_MAX_THREADS));
+ assertThat(qtp.getName(), is(DEFAULT_THREAD_POOL_METRIC_NAME));
+ }
+
+ @Test
+ public void threadPoolBuilderWithRegistryTests() {
+
+ ThreadPool tp = ThreadPoolBuilder.instance().registry(registry).build();
+ assertNotNull(tp);
+ assertThat(tp.getClass(), is(InstrumentedQueuedThreadPool.class));
+ InstrumentedQueuedThreadPool qtp = (InstrumentedQueuedThreadPool) tp;
+ assertThat(qtp.getMinThreads(), is(DEFAULT_MIN_THREADS));
+ assertThat(qtp.getMaxThreads(), is(DEFAULT_MAX_THREADS));
+ assertThat(qtp.getIdleTimeout(), is(DEFAULT_IDLE_TIMEOUT));
+ assertThat(qtp.getMaxAvailableThreads(), is(DEFAULT_MAX_THREADS));
+ assertThat(qtp.getName(), is(DEFAULT_THREAD_POOL_METRIC_NAME));
+ }
+}
diff --git a/src/test/resources/application-authz-test.yml b/src/test/resources/application-authz-test.yml
index 3d9ceafd..d1b3a46b 100644
--- a/src/test/resources/application-authz-test.yml
+++ b/src/test/resources/application-authz-test.yml
@@ -4,6 +4,10 @@ server:
enabled: false
oauth:
enable-oidc: false
+ refresh-period-minutes: 1
+ issuers:
+ - name: iam-dev
+ issuer: https://iam-dev.cloud.cnaf.infn.it/
storm:
sa:
diff --git a/src/test/resources/conf/sa.d/test.properties b/src/test/resources/conf/sa.d/test.properties
index 513bfacc..0e0400fe 100644
--- a/src/test/resources/conf/sa.d/test.properties
+++ b/src/test/resources/conf/sa.d/test.properties
@@ -18,7 +18,8 @@ name=test
rootPath=src/test/resources/storage/test
filesystemType=posixfs
accessPoints=/test
-vos=test.vo
+vos=wlcg
+orgs=https://wlcg.cloud.cnaf.infn.it/
authenticatedReadEnabled=true
anonymousReadEnabled=false
voMapGrantsWritePermission=false