From 39419b24a64ab5c22035d58f7af493d3dca3ff7c Mon Sep 17 00:00:00 2001 From: Thomas Beekman Date: Tue, 14 Apr 2026 11:17:51 +0200 Subject: [PATCH] ENT-5402: Build release OC SSO Notification 2.5.0 --- .github/workflows/dependency-check-maven.yml | 10 +- .github/workflows/develop.yml | 3 +- .github/workflows/main.yml | 2 +- oc-sso-notificatie/pom.xml | 2 +- .../nl/kennisnet/services/Application.java | 17 ++-- .../config/BuildPropertiesConfig.java | 55 +++++++++++ .../config/CookieSameSiteConfiguration.java | 2 +- .../web/service/CacheHashService.java | 19 ++-- .../services/web/service/IdPProvider.java | 16 ++-- .../services/web/CacheHashServiceTest.java | 72 ++++++++------- .../services/web/IdPProviderTest.java | 91 ++++++++++--------- .../web/config/BuildPropertiesConfigTest.java | 91 +++++++++++++++++++ .../SsoNotificationControllerTest.java | 42 +++++++-- .../jobs/DataServicesUpdateRunnerTest.java | 7 +- pom.xml | 4 +- release/pom.xml | 4 +- .../site/markdown/docs/2.5.0/release-notes.md | 21 +++++ 17 files changed, 332 insertions(+), 126 deletions(-) create mode 100644 oc-sso-notificatie/src/main/java/nl/kennisnet/services/config/BuildPropertiesConfig.java create mode 100644 oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/config/BuildPropertiesConfigTest.java create mode 100644 release/src/site/markdown/docs/2.5.0/release-notes.md diff --git a/.github/workflows/dependency-check-maven.yml b/.github/workflows/dependency-check-maven.yml index 997b347..50a02df 100644 --- a/.github/workflows/dependency-check-maven.yml +++ b/.github/workflows/dependency-check-maven.yml @@ -6,20 +6,20 @@ on: - cron: "0 10 * * 6" jobs: - dependency-check-maven: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: develop + - name: Set up JDK 21 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: + distribution: temurin java-version: '21' - distribution: 'adopt' cache: maven + - name: Execute OWASP dependency check run: mvn clean install -P security-updates -DskipTests=true -B diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index c722812..b4da8d6 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -11,7 +11,6 @@ on: jobs: build: - runs-on: ubuntu-latest steps: @@ -19,8 +18,8 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v4 with: + distribution: temurin java-version: '21' - distribution: 'adopt' cache: maven - name: Fail build if version contains SNAPSHOT run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 82414e1..957b682 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,8 +17,8 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v4 with: + distribution: temurin java-version: '21' - distribution: 'adopt' cache: maven - run: echo "VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV - run: echo "BUILD_VERSION=${VERSION%-SNAPSHOT}" >> $GITHUB_ENV diff --git a/oc-sso-notificatie/pom.xml b/oc-sso-notificatie/pom.xml index a2e5aeb..9a7aa62 100644 --- a/oc-sso-notificatie/pom.xml +++ b/oc-sso-notificatie/pom.xml @@ -19,7 +19,7 @@ oc-sso-notificatie-parent nl.kennisnet.services - 2.4.0-SNAPSHOT + 2.5.0-SNAPSHOT 4.0.0 diff --git a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/Application.java b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/Application.java index 2247a6a..6db861c 100644 --- a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/Application.java +++ b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/Application.java @@ -18,10 +18,11 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.web.client.RestClient; +import java.net.http.HttpClient; import java.time.Duration; /** @@ -38,11 +39,13 @@ public static void main(String[] args) { } @Bean - public RestTemplate restTemplate(RestTemplateBuilder builder) { - return builder - .connectTimeout(Duration.ofSeconds(connectionTimeout)) - .readTimeout(Duration.ofSeconds(connectionTimeout)) - .build(); + public RestClient restClient() { + HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(connectionTimeout)).build(); + + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient); + requestFactory.setReadTimeout(Duration.ofSeconds(connectionTimeout)); + + return RestClient.builder().requestFactory(requestFactory).build(); } } diff --git a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/config/BuildPropertiesConfig.java b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/config/BuildPropertiesConfig.java new file mode 100644 index 0000000..50c8e85 --- /dev/null +++ b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/config/BuildPropertiesConfig.java @@ -0,0 +1,55 @@ +package nl.kennisnet.services.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PropertiesLoaderUtils; + +import java.io.IOException; +import java.time.Instant; +import java.util.Properties; + +@Configuration +public class BuildPropertiesConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(BuildPropertiesConfig.class); + + @Bean + public BuildProperties buildProperties(ResourceLoader resourceLoader) { + try { + Resource resource = resourceLoader.getResource("classpath:META-INF/build-info.properties"); + if (!resource.exists()) { + LOGGER.warn("META-INF/build-info.properties not found, using default build properties"); + Properties defaultProperties = new Properties(); + defaultProperties.put("time", Instant.now().toString()); + defaultProperties.put("version", "0.0.0-TEST"); + defaultProperties.put("name", "test-build"); + defaultProperties.put("group", "test-group"); + defaultProperties.put("artifact", "test-artifact"); + return new BuildProperties(defaultProperties); + } + + Properties properties = PropertiesLoaderUtils.loadProperties(resource); + Properties flattenedProperties = new Properties(); + properties.forEach((key, value) -> { + String newKey = key.toString().replaceFirst("^build\\.", ""); + flattenedProperties.put(newKey, value); + }); + + return new BuildProperties(flattenedProperties); + } catch (IOException e) { + LOGGER.error("Error loading build-info.properties", e); + Properties defaultProperties = new Properties(); + defaultProperties.put("time", Instant.now().toString()); + defaultProperties.put("version", "0.0.0-ERROR"); + defaultProperties.put("name", "error-build"); + defaultProperties.put("group", "error-group"); + defaultProperties.put("artifact", "error-artifact"); + return new BuildProperties(defaultProperties); + } + } +} diff --git a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/config/CookieSameSiteConfiguration.java b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/config/CookieSameSiteConfiguration.java index 763e949..49a2af2 100644 --- a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/config/CookieSameSiteConfiguration.java +++ b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/config/CookieSameSiteConfiguration.java @@ -15,7 +15,7 @@ */ package nl.kennisnet.services.web.config; -import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier; +import org.springframework.boot.web.server.servlet.CookieSameSiteSupplier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/CacheHashService.java b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/CacheHashService.java index 5b75f3f..ceef5a9 100644 --- a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/CacheHashService.java +++ b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/CacheHashService.java @@ -3,15 +3,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; @Service public class CacheHashService { @@ -27,10 +24,10 @@ public class CacheHashService { @Value("${api.endpoint.url.cacheHash:#{null}}") private String url; - private final RestTemplate restTemplate; + private final RestClient restClient; - public CacheHashService(RestTemplate restTemplate) { - this.restTemplate = restTemplate; + public CacheHashService(RestClient restClient) { + this.restClient = restClient; } public String fetchCacheHash() { @@ -45,10 +42,12 @@ public String fetchCacheHash() { HttpEntity httpEntity = new HttpEntity<>(requestHeaders); try { - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, - new ParameterizedTypeReference<>() {}); + String result = restClient.get() + .uri(url) + .headers(headers -> headers.addAll(httpEntity.getHeaders())) + .retrieve() + .body(String.class); - String result = response.getBody(); if (null == result) { LOGGER.warn("Received null from data-services cache-hash, returning empty hash"); return ""; diff --git a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/IdPProvider.java b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/IdPProvider.java index 95ca297..db53cb9 100644 --- a/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/IdPProvider.java +++ b/oc-sso-notificatie/src/main/java/nl/kennisnet/services/web/service/IdPProvider.java @@ -27,12 +27,11 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; @@ -52,7 +51,7 @@ public class IdPProvider { private static final ObjectMapper objectMapper = new ObjectMapper(); - private final RestTemplate restTemplate; + private final RestClient restClient; @Value("${data.location}") private Resource dataSource; @@ -69,8 +68,8 @@ public class IdPProvider { @Value("${api.endpoint.url.all-suffix:#{null}}") private String endpointAllSuffix; - public IdPProvider(RestTemplate restTemplate) { - this.restTemplate = restTemplate; + public IdPProvider(RestClient restClient) { + this.restClient = restClient; } /** @@ -102,8 +101,11 @@ private List getAllSsoNotificationsFromDataServices() { URI uri = UriComponentsBuilder.fromUriString(endpointUrl + endpointAllSuffix).build().toUri(); try { - return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, - new ParameterizedTypeReference>(){}).getBody(); + return restClient.get() + .uri(uri) + .headers(headers -> headers.addAll(httpEntity.getHeaders())) + .retrieve() + .body(new ParameterizedTypeReference>() {}); } catch (HttpClientErrorException hcee) { if (HttpStatus.NOT_FOUND == hcee.getStatusCode()) { return new ArrayList<>(); diff --git a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/CacheHashServiceTest.java b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/CacheHashServiceTest.java index 59a5ae5..27defb3 100644 --- a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/CacheHashServiceTest.java +++ b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/CacheHashServiceTest.java @@ -3,87 +3,91 @@ import nl.kennisnet.services.web.service.CacheHashService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; -@SpringBootTest +@ExtendWith(MockitoExtension.class) class CacheHashServiceTest { @Mock - RestTemplate restTemplate; + RestClient restClient; - @InjectMocks - CacheHashService cacheHashService; + private CacheHashService cacheHashService; @BeforeEach void setUp() { + cacheHashService = new CacheHashService(restClient); + ReflectionTestUtils.setField(cacheHashService, "url", "sso-notification-url"); ReflectionTestUtils.setField(cacheHashService, "API_KEY_HEADER", "api-key"); } @Test void fetchCacheHashTest() { - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), - any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>("HASH", HttpStatus.OK)); + RestClient.RequestHeadersUriSpec spec = buildRequestSpec("HASH"); - String result = cacheHashService.fetchCacheHash(); + when(restClient.get()).thenReturn((RestClient.RequestHeadersUriSpec) spec); - assertEquals("HASH", result); + assertEquals("HASH", cacheHashService.fetchCacheHash()); } @Test void fetchCacheNullReturnTest() { - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), - any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(null, HttpStatus.OK)); + RestClient.RequestHeadersUriSpec spec = buildRequestSpec(null); - String result = cacheHashService.fetchCacheHash(); + when(restClient.get()).thenReturn((RestClient.RequestHeadersUriSpec) spec); - assertEquals("", result); + assertEquals("", cacheHashService.fetchCacheHash()); } @Test void fetchCacheNullUrlTest() { ReflectionTestUtils.setField(cacheHashService, "url", null); - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), - any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(null, HttpStatus.OK)); - - String result = cacheHashService.fetchCacheHash(); - - assertEquals("", result); + assertEquals("", cacheHashService.fetchCacheHash()); } @Test void fetchCacheHashHttpExceptionTest() { - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), - any(ParameterizedTypeReference.class))).thenThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN)); + when(restClient.get()).thenThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN)); - String result = cacheHashService.fetchCacheHash(); - - assertEquals("", result); + assertEquals("", cacheHashService.fetchCacheHash()); } @Test void fetchCacheRestClientExceptionTest() { - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), - any(ParameterizedTypeReference.class))).thenThrow(new RestClientException("ERROR")); + when(restClient.get()).thenThrow(new RestClientException("ERROR")); + + assertEquals("", cacheHashService.fetchCacheHash()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private RestClient.RequestHeadersUriSpec buildRequestSpec(String responseBody) { + + RestClient.RequestHeadersUriSpec requestSpec = + mock(RestClient.RequestHeadersUriSpec.class); + + RestClient.RequestHeadersSpec headersSpec = + mock(RestClient.RequestHeadersSpec.class); + + RestClient.ResponseSpec responseSpec = + mock(RestClient.ResponseSpec.class); - String result = cacheHashService.fetchCacheHash(); + when(requestSpec.uri(anyString())).thenReturn(headersSpec); + when(headersSpec.headers(any())).thenReturn(headersSpec); + when(headersSpec.retrieve()).thenReturn(responseSpec); + when(responseSpec.body(String.class)).thenReturn(responseBody); - assertEquals("", result); + return requestSpec; } } \ No newline at end of file diff --git a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/IdPProviderTest.java b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/IdPProviderTest.java index 127daf3..d51c698 100644 --- a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/IdPProviderTest.java +++ b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/IdPProviderTest.java @@ -17,51 +17,53 @@ import nl.kennisnet.services.web.model.IdP; import nl.kennisnet.services.web.service.IdPProvider; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.client.RestClient; -import java.net.URI; -import java.nio.file.Files; import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.*; +import static org.mockito.Mockito.when; -@RestClientTest(IdPProvider.class) -@TestPropertySource(properties = "classpath:application.properties") +@ExtendWith(MockitoExtension.class) class IdPProviderTest { - @Autowired - private IdPProvider provider; - - @Autowired - private MockRestServiceServer server; - - @Value("classpath:rest_response.json") - private Resource resourceFile; + @Mock + private RestClient restClient; @Value("${api.endpoint.url}") private String endpointUrl; - @Test - void getIdPTest() throws Exception { - String resource = new String(Files.readAllBytes(resourceFile.getFile().toPath())); - this.server.expect(requestTo(createUrl())).andRespond(withSuccess(resource, MediaType.APPLICATION_JSON)); + private IdPProvider idPProvider; + + private Resource restResource; - List idps = provider.getAllSsoNotifications(); - IdP idp = idps.get(0); + private Resource staticResource; + + @BeforeEach + void setUp() { + idPProvider = new IdPProvider(restClient); + + staticResource = new ClassPathResource("idp.data.json"); + restResource = new ClassPathResource("rest_response.json"); + + ReflectionTestUtils.setField(idPProvider, "dataSource", restResource); + } + + @Test + void getIdPTest() { + List idps = idPProvider.getAllSsoNotifications(); + IdP idp = idps.getFirst(); assertNotNull(idp); assertEquals("RefELOSAML-OpenConext", idp.getEntityId()); assertNotNull(idp.getIdpUrlList()); @@ -75,27 +77,37 @@ void getIdPTest() throws Exception { assertTrue(idp.getRedirectUrlList().contains("https://referentie.vm.openconext.org")); assertTrue(idp.getRedirectUrlList().contains("https://referentie.vm2.openconext.org")); assertTrue(idp.getRedirectUrlList().contains("https://referentie.vm3.openconext.org")); - this.server.verify(); } @Test void invalidApiKeyTest() { - this.server.expect(requestTo(createUrl())).andRespond(withUnauthorizedRequest()); - assertThrows(HttpClientErrorException.Unauthorized.class, () -> provider.getAllSsoNotifications()); + ReflectionTestUtils.setField(idPProvider, "endpointUrl", "http://localhost:3000/api/sso-notification"); + ReflectionTestUtils.setField(idPProvider, "apiKeyHeaderKey", "api-key"); + ReflectionTestUtils.setField(idPProvider, "apiKeyHeaderValue", "TESTTOKEN"); + ReflectionTestUtils.setField(idPProvider, "endpointAllSuffix", "/all"); + + when(restClient.get()).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); + assertThrows(HttpClientErrorException.class, () -> idPProvider.getAllSsoNotifications()); } @Test - void notFoundResponseTest() { - this.server.expect(requestTo(createUrl())).andRespond(withStatus(HttpStatus.NOT_FOUND)); - assertTrue(provider.getAllSsoNotifications().isEmpty()); + void notFoundResponseTest() { + ReflectionTestUtils.setField(idPProvider, "endpointUrl", "http://localhost:3000/api/sso-notification"); + ReflectionTestUtils.setField(idPProvider, "apiKeyHeaderKey", "api-key"); + ReflectionTestUtils.setField(idPProvider, "apiKeyHeaderValue", "TESTTOKEN"); + ReflectionTestUtils.setField(idPProvider, "endpointAllSuffix", "/all"); + + when(restClient.get()).thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); + + assertTrue(idPProvider.getAllSsoNotifications().isEmpty()); } @Test void getIdPStaticFileTest() { - ReflectionTestUtils.setField(provider, "endpointUrl", null); + ReflectionTestUtils.setField(idPProvider, "dataSource", staticResource); - List idps = provider.getAllSsoNotifications(); - IdP idp = idps.get(0); + List idps = idPProvider.getAllSsoNotifications(); + IdP idp = idps.getFirst(); assertNotNull(idp); assertEquals("xxx", idp.getEntityId()); assertNotNull(idp.getIdpUrlList()); @@ -109,14 +121,9 @@ void getIdPStaticFileTest() { @Test void getIdpInvalidResourceStaticFileTest() { - ReflectionTestUtils.setField(provider, "dataSource", new ClassPathResource("file_does_not_exist")); - List idps = provider.getAllSsoNotifications(); + ReflectionTestUtils.setField(idPProvider, "dataSource", new ClassPathResource("file_does_not_exist")); + List idps = idPProvider.getAllSsoNotifications(); assertTrue(idps.isEmpty()); } - private URI createUrl() { - String ALL_SUFFIX = "/all"; - return UriComponentsBuilder.fromUriString(endpointUrl + ALL_SUFFIX).build().toUri(); - } - } diff --git a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/config/BuildPropertiesConfigTest.java b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/config/BuildPropertiesConfigTest.java new file mode 100644 index 0000000..6e49e2a --- /dev/null +++ b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/config/BuildPropertiesConfigTest.java @@ -0,0 +1,91 @@ +package nl.kennisnet.services.web.config; + +import nl.kennisnet.services.config.BuildPropertiesConfig; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.info.BuildProperties; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class BuildPropertiesConfigTest { + + @Mock + private ResourceLoader resourceLoader; + + @InjectMocks + private final BuildPropertiesConfig buildPropertiesConfig = new BuildPropertiesConfig(); + + + @Test + void buildPropertiesMissing() { + Resource mockResource = mock(Resource.class); + when(mockResource.exists()).thenReturn(false); + when(resourceLoader.getResource(any())).thenReturn(mockResource); + + BuildProperties buildProperties = buildPropertiesConfig.buildProperties(resourceLoader); + + assertEquals("0.0.0-TEST", buildProperties.getVersion()); + assertEquals("test-build", buildProperties.getName()); + assertEquals("test-group", buildProperties.getGroup()); + assertEquals("test-artifact", buildProperties.getArtifact()); + } + + @Test + void buildPropertiesAvailable() throws IOException { + Resource mockResource = mock(Resource.class); + + when(mockResource.exists()).thenReturn(true); + when(mockResource.getInputStream()).thenReturn( + new ByteArrayInputStream(( + """ + build.artifact=oc-sso-notificatie + build.group=nl.kennisnet.services + build.java_version=21 + build.name=OC SSO Notification - Application + build.spring_boot_version=4.0.5 + build.time=2026-04-13T10\\:35\\:03.571Z + build.version=2.5.0""").getBytes())); + + when(resourceLoader.getResource(any())).thenReturn(mockResource); + + BuildProperties buildProperties = buildPropertiesConfig.buildProperties(resourceLoader); + + assertEquals("oc-sso-notificatie", buildProperties.getArtifact()); + assertEquals("nl.kennisnet.services", buildProperties.getGroup()); + assertEquals("OC SSO Notification - Application", buildProperties.getName()); + assertEquals("2.5.0", buildProperties.getVersion()); + assertEquals(Instant.parse("2026-04-13T10:35:03.571Z"), buildProperties.getTime()); + assertEquals("21", buildProperties.get("java_version")); + assertEquals("4.0.5", buildProperties.get("spring_boot_version")); + } + + @Test + void buildPropertiesMissingIOException() throws IOException { + Resource mockResource = mock(Resource.class); + + when(mockResource.exists()).thenReturn(true); + doThrow(IOException.class).when(mockResource).getInputStream(); + + when(resourceLoader.getResource(any())).thenReturn(mockResource); + + BuildProperties buildProperties = buildPropertiesConfig.buildProperties(resourceLoader); + + assertEquals("0.0.0-ERROR", buildProperties.getVersion()); + assertEquals("error-build", buildProperties.getName()); + assertEquals("error-group", buildProperties.getGroup()); + assertEquals("error-artifact", buildProperties.getArtifact()); + } + +} diff --git a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/controller/SsoNotificationControllerTest.java b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/controller/SsoNotificationControllerTest.java index 73f79ec..7d0fc80 100644 --- a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/controller/SsoNotificationControllerTest.java +++ b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/controller/SsoNotificationControllerTest.java @@ -24,9 +24,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.springframework.boot.test.context.SpringBootTest; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -40,27 +39,33 @@ import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -@ExtendWith(SpringExtension.class) -@SpringBootTest +@ExtendWith(MockitoExtension.class) class SsoNotificationControllerTest { private static final String SINGLE_URL = "singleUrl"; + private static final String SINGLE_URL_WITH_PATH = "singleUrlWithPath"; + private static final String INVALID = "invalid"; private static final String MULTIPLE_URLS = "multipleUrls"; + private static final String NO_PATH_URL_IDP = "noIdpUrlConfigured"; + private static final String NO_PATH_URL_REDIRECT = "noRedirectUrlConfigured"; private static final String SINGLE_WILDCARD_URL = "singleWildcardUrl"; private static final String SSO_NOTIFICATION_URL = "/"; + private static final String URL = "url"; + private static final String REDIRECT_URI = "redirectUri"; + private MockMvc mvc; @InjectMocks - private SsoNotificationController controller; + private SsoNotificationController ssoNotificationController; @Mock private CacheConfig cacheConfig; @@ -72,8 +77,8 @@ class SsoNotificationControllerTest { private IdPProvider idPProvider; @BeforeEach - void setUp() throws Exception { - this.mvc = MockMvcBuilders.standaloneSetup(controller).build(); + void setUp() { + this.mvc = MockMvcBuilders.standaloneSetup(ssoNotificationController).build(); ArrayList ssoNotifications = new ArrayList<>(); ssoNotifications.add(new IdP(NO_PATH_URL_IDP, null, List.of("http://www.example.com"))); @@ -90,8 +95,6 @@ void setUp() throws Exception { List.of("http://www.example.com"))); when(idPProvider.getAllSsoNotifications()).thenReturn(ssoNotifications); - when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( - new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); } @Test @@ -148,6 +151,9 @@ void processSsoNotificationInvalidReferrerBasicAuthTest() throws Exception { @Test void processSsoNotificationNoRedirectUrlParamTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", SINGLE_URL) .param(URL, "http://www.example.com")) .andExpect(status().isOk()) @@ -156,6 +162,9 @@ void processSsoNotificationNoRedirectUrlParamTest() throws Exception { @Test void processSsoNotificationRedirectTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", "multipleUrls") .param(URL, "http://www.example.com") .param(REDIRECT_URI, "http://www.exampledomain.com")) @@ -166,6 +175,9 @@ void processSsoNotificationRedirectTest() throws Exception { @Test void processSsoNotificationFromIframeTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", "multipleUrls") .param(URL, "http://www.example.com")) .andExpect(status().isOk()) @@ -174,6 +186,9 @@ void processSsoNotificationFromIframeTest() throws Exception { @Test void processSsoNotificationFromIframeWithRedirectTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", "multipleUrls") .param(REDIRECT_URI, "http://www.example.com") .param(URL, "http://www.example.com")) @@ -184,6 +199,9 @@ void processSsoNotificationFromIframeWithRedirectTest() throws Exception { @Test void processSsoNotificationFromIframeWildcardTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", SINGLE_WILDCARD_URL) .param(URL, "http://test.example.com")) .andExpect(cookie().exists(SsoNotificationController.COOKIE_NOTIFICATION)) @@ -229,6 +247,9 @@ void processSsoNotificationNoRedirectExpressionsTest() throws Exception { @Test void testUrlPathTest() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", SINGLE_URL_WITH_PATH) .header(HttpHeaders.REFERER, "http://www.example.com")) .andExpect(status().isOk()) @@ -258,6 +279,9 @@ void processNotExistingId() throws Exception { @Test void testDataFetchError() throws Exception { + when(cookiesHandler.createCookie(anyString(), nullable(String.class), any(URL.class), any())).thenReturn( + new Cookie(SsoNotificationController.COOKIE_NOTIFICATION, "testValue")); + mvc.perform(MockMvcRequestBuilders.get(SSO_NOTIFICATION_URL).param("id", SINGLE_URL_WITH_PATH) .param(REDIRECT_URI, "http://www.example.com") .header(HttpHeaders.REFERER, "http://www.example.com")); diff --git a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/jobs/DataServicesUpdateRunnerTest.java b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/jobs/DataServicesUpdateRunnerTest.java index a98cd8c..6b3e923 100644 --- a/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/jobs/DataServicesUpdateRunnerTest.java +++ b/oc-sso-notificatie/src/test/java/nl/kennisnet/services/web/jobs/DataServicesUpdateRunnerTest.java @@ -5,14 +5,15 @@ import nl.kennisnet.services.web.service.jobs.DataServicesUpdateRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.springframework.boot.test.context.SpringBootTest; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import static org.mockito.Mockito.*; -@SpringBootTest +@ExtendWith(MockitoExtension.class) class DataServicesUpdateRunnerTest { @Mock @@ -71,4 +72,4 @@ void canNotRunDouble() { verify(cacheConfig, times(0)).cacheEvict(); } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index c95375e..c55f04e 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ nl.kennisnet.services oc-sso-notificatie-parent pom - 2.4.0-SNAPSHOT + 2.5.0-SNAPSHOT oc-sso-notificatie release @@ -41,7 +41,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.10 + 4.0.5 diff --git a/release/pom.xml b/release/pom.xml index 3d72adc..aab751b 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -19,7 +19,7 @@ oc-sso-notificatie-parent nl.kennisnet.services - 2.4.0-SNAPSHOT + 2.5.0-SNAPSHOT 4.0.0 @@ -31,7 +31,7 @@ - 2.4.0,2.3.8,2.3.7,2.3.6,2.3.5,2.3.4,2.3.3,2.3.2,2.3.1,2.3.0,2.2.1,2.2.0,2.1.1,2.1.0,2.0.6,2.0.5,2.0.4,2.0.3,2.0.2,2.0.1,1.0.0 + 2.5.0,2.4.0,2.3.8,2.3.7,2.3.6,2.3.5,2.3.4,2.3.3,2.3.2,2.3.1,2.3.0,2.2.1,2.2.0,2.1.1,2.1.0,2.0.6,2.0.5,2.0.4,2.0.3,2.0.2,2.0.1,1.0.0 diff --git a/release/src/site/markdown/docs/2.5.0/release-notes.md b/release/src/site/markdown/docs/2.5.0/release-notes.md new file mode 100644 index 0000000..befe49f --- /dev/null +++ b/release/src/site/markdown/docs/2.5.0/release-notes.md @@ -0,0 +1,21 @@ +# OC SSO Notification Service - Release Notes + +| Field | Value | +|:--------------------------|:----------------------------| +| Application | OC SSO Notification Service | +| Version | 2.5.0 | +| Changes since version | 2.4.0 | +| Release date | 14-04-2026 (dd-mm-yyyy) | +| Delivery type | Full release | + +## Changes + +| # | Description | +|:---------|:----------------------------------------| +| ENT-5402 | Build release OC SSO Notification 2.5.0 | + +## Configuration changes + +## Known vulnerabilities + +At the time of release, this version of the application had no known vulnerabilities with a CVE of 7 or higher.