Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2f534fd
Add endpoint to transform a dataRelationShip in DDI to Json
JeromeMBourgeois Sep 16, 2024
f405474
Fix test
JeromeMBourgeois Sep 16, 2024
953171b
refactor (mutulualize, separate concerns, remove useless, remove redo…
Sep 23, 2024
4248804
Merge branch 'main' into DataRelationShipToJson
Sep 24, 2024
9cb31a9
refactor (mutulualize, separate concerns, remove useless, remove redo…
Sep 24, 2024
0666808
Merge branch 'main' into DataRelationShipToJson
Nov 19, 2024
73d1560
Merge branch 'main' into DataRelationShipToJson
Nov 25, 2024
3baf087
Merge branch 'main' into DataRelationShipToJson
Nov 25, 2024
767b47e
refactor (mutulualize, separate concerns, remove useless, remove redo…
Nov 25, 2024
6ce3320
refactor (mutulualize, separate concerns, remove useless, remove redo…
Dec 20, 2024
27ce69c
refactor (mutulualize, separate concerns, remove useless, remove redo…
Dec 20, 2024
25cb1f4
refactor (mutulualize, separate concerns, remove useless, remove redo…
Jan 14, 2025
5f1c17a
feat (get ddi relationship to json) #93
Jan 14, 2025
5ffcf38
refactor (mutulualize, separate concerns, remove useless, remove redo…
Jan 15, 2025
1edf75e
Merge branch 'main' into DataRelationShipToJson
Jan 15, 2025
42eee50
fix (fail to process xml returned by colectica when there is a BOM)
Jan 22, 2025
a15232c
fix (fail to refresh bearer token for colectica)
Jan 22, 2025
4197c07
fix (uuid example for GET /Item/ddiFragment/{uuid}/dataRelationship)…
Feb 3, 2025
ba74f83
refactor (sonar issues and unused code)
Feb 27, 2025
136e2eb
refactor (use yaml for properties)
Feb 28, 2025
90025db
refactor (use spring security to retrieve bearer token for colectica)…
Feb 28, 2025
d7d37ce
fix (use spring security to retrieve bearer token for colectica) fix …
Feb 28, 2025
51495fd
fix (tests after RestClient refactor) exclude OAuth2ClientAutoConfi…
Feb 28, 2025
33fc23f
Update pom -> 2.1.2
GtanSndil Mar 3, 2025
63530d0
tests (Authenticated calls to colectica with Spring Security work well)
Mar 3, 2025
7de7991
Merge remote-tracking branch 'origin/115-use-spring-security-client-t…
Mar 3, 2025
8adea9b
Merge branch 'main' into 115-use-spring-security-client-to-manage-aut…
Mar 3, 2025
188c879
fix (mistake in configuration) wrong default profile declaration
Mar 3, 2025
87e76b9
sonar
GtanSndil Mar 4, 2025
8df4d40
Merge branch '115-use-spring-security-client-to-manage-authentication…
GtanSndil Mar 4, 2025
f03b286
sonar: suppress a warning
GtanSndil Mar 4, 2025
fb8990c
Merge branch 'main' into 115-use-spring-security-client-to-manage-aut…
GtanSndil Mar 10, 2025
a0504d4
Merge branch 'main' into 115-use-spring-security-client-to-manage-aut…
Apr 30, 2025
8b198be
fix (mistake while merging pom)
Apr 30, 2025
f28ee70
test (add test for SecurityConfig to reach expected test coverage) re…
May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>fr.insee.rmes</groupId>
<artifactId>ddi-access-services</artifactId>
<version>2.1.4-beta5</version>
<version>4.6.1-beta</version>
<name>DDI Access Services</name>
<description>Rest API to access to Colectica repository services</description>
<properties>
Expand All @@ -23,19 +23,13 @@
<java-jwt.version>4.5.0</java-jwt.version>
<json.version>20250107</json.version>
<jacoco.version>0.8.12</jacoco.version>
<wiremock.version>3.12.0</wiremock.version>
<sonar.organization>inseefr</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.projectKey>InseeFr_DDI-Access-Services</sonar.projectKey>
</properties>
<dependencies>

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
Expand Down Expand Up @@ -76,6 +70,18 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
Expand All @@ -92,6 +98,11 @@
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<!-- SAXON FOR XSLT -->
<dependency>
<groupId>net.sf.saxon</groupId>
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/fr/insee/rmes/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
Expand All @@ -49,6 +49,7 @@ public SecurityConfig(InseeSecurityTokenProperties inseeSecurityTokenProperties,
}

@Bean
@SuppressWarnings("java:S4502")
@Profile("prod")
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
Expand Down Expand Up @@ -115,7 +116,7 @@ Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
List<String> roles = (List<String>) claims.getOrDefault(claimPath[claimPath.length - 1], List.of());
//if we need to add customs roles to every connected user we could define this variable (static or from properties)
//roles.addAll(defaultRolesForUsers);
return Collections.unmodifiableCollection(roles.stream().map(s -> new GrantedAuthority() {
return roles.stream().map(s -> new GrantedAuthority() {
@Override
public String getAuthority() {
return ROLE_PREFIX + s;
Expand All @@ -125,7 +126,7 @@ public String getAuthority() {
public String toString() {
return getAuthority();
}
}).toList());
}).collect(Collectors.toList());
} catch (ClassCastException e) {
return List.of();
}
Expand Down
100 changes: 12 additions & 88 deletions src/main/java/fr/insee/rmes/config/keycloak/KeycloakServices.java
Original file line number Diff line number Diff line change
@@ -1,98 +1,22 @@
package fr.insee.rmes.config.keycloak;

import com.auth0.jwt.JWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.function.Consumer;

@Service
@Slf4j
public class KeycloakServices {

final String secret;

final String resource;

final String server;

final String realm;

private String token;

private Instant expiration;

public KeycloakServices(@Value("${fr.insee.rmes.metadata.keycloak.secret}") String secret, @Value("${fr.insee.rmes.metadata.keycloak.resource}") String resource, @Value("${fr.insee.rmes.metadata.keycloak.server}") String server, @Value("${fr.insee.rmes.metadata.keycloak.realm}") String realm) {
this.secret = secret;
this.resource = resource;
this.server = server;
this.realm = realm;
this.expiration = null;
}

/**
* Permet de récuperer un jeton keycloak
*
* @return jeton
*/
private String getKeycloakAccessToken() {

RestTemplate keycloakClient = new RestTemplate();
String keycloakUrl = server + "/realms/" + realm + "/protocol/openid-connect/token";

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");
body.add("client_id", resource);
body.add("client_secret", secret);
HttpEntity<Object> entity = new HttpEntity<>(body, headers);

Token accessToken = keycloakClient.postForObject(keycloakUrl, entity, Token.class);

log.trace("Keycloak token provided");
return accessToken.accessToken();

}

/**
* check if oidc token has expired
*
* @return boolean
*/
private boolean isCurrentTokenValid() {
return expiration != null && Instant.now().isBefore(expiration.minus(1, ChronoUnit.SECONDS));
}
@Service
public record KeycloakServices(OAuth2AuthorizedClientManager authorizedClientManager) {

public String getFreshToken() {
log.atTrace().log(() -> "Check if token is valid with expiration at " + expirationAsString());
if (!this.isCurrentTokenValid()) {
log.debug("Start refreshing token");
token = getKeycloakAccessToken();
this.expiration = expirationFrom(token);
log.atTrace().log(() -> "New token valid until " + expirationAsString());
}
return token;
}
public static final String CLIENT_REGISTRATION_ID = "colectica-client";

private String expirationAsString() {
Instant expirationInstant = expiration;
return expirationInstant == null ? null : LocalDateTime.ofInstant(expirationInstant, ZoneId.systemDefault()).toString();
public Consumer<RestClient.Builder> configureOidcClientAutoAuthentication() {
return builder -> builder
.requestInterceptor(new OAuth2ClientHttpRequestInterceptor(authorizedClientManager))
.defaultRequest(requestHeadersSpec -> requestHeadersSpec.attributes(clientRegistrationId(CLIENT_REGISTRATION_ID)));
}

private Instant expirationFrom(String token) {
return JWT.decode(token).getExpiresAtAsInstant();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Slf4j
@Service
Expand Down
27 changes: 0 additions & 27 deletions src/main/java/fr/insee/rmes/tocolecticaapi/models/AuthRequest.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,18 @@ public record ColecticaServiceImpl(ElasticService elasticService,
private static final String TRANSACTIONID = "{\"TransactionId\":";

@Autowired
public ColecticaServiceImpl(KeycloakServices kc,
public ColecticaServiceImpl(KeycloakServices keycloakServices,
ElasticService elasticService,
ExportUtils exportUtils,
DDIDerefencer ddiDerefencer,
@Value("${fr.insee.rmes.api.remote.metadata.url}")
String serviceUrl,
@Value("${fr.insee.rmes.api.remote.metadata.agency}")
String agency){
this(elasticService, initRestClient(kc, serviceUrl), exportUtils, ddiDerefencer, agency);
this(elasticService, initRestClient(keycloakServices, serviceUrl), exportUtils, ddiDerefencer, agency);
}

private static RestClient initRestClient(KeycloakServices kc, String serviceUrl) {
private static RestClient initRestClient(KeycloakServices keycloakServices, String serviceUrl) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(100_000); // Temps de connexion en millisecondes
factory.setReadTimeout(100_000); // Temps de lecture en millisecondes
Expand All @@ -86,9 +86,7 @@ private static RestClient initRestClient(KeycloakServices kc, String serviceUrl)
.requestFactory(factory)
.baseUrl(URI.create(serviceUrl + "/").resolve("api/v1/"))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.defaultRequest(requestHeadersSpec -> requestHeadersSpec.header(HttpHeaders.AUTHORIZATION,
"Bearer " + kc.getFreshToken())
)
.apply(keycloakServices.configureOidcClientAutoAuthentication())
.build();
}

Expand Down
23 changes: 14 additions & 9 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fr:
secret: xxxxx
resource: xxxxx
server: "https://keycloak.xxx.fr/"
realm: xxxx
scopes: "message.read,message.write"
cors:
allowedOrigin: "*"
springdoc:
Expand All @@ -65,12 +65,23 @@ spring:
enabled: true
max-request-size: 50MB
profiles:
active: dev
active: prod
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "${fr.insee.rmes.metadata.keycloak.server}/.../protocol/openid-connect/certs"
client:
registration:
colectica-client: #Same value as the constant fr.insee.rmes.config.keycloak.KeycloakServices.CLIENT_REGISTRATION_ID
provider: ddi-as-provider
client-id: ${fr.insee.rmes.metadata.keycloak.resource}
client-secret: ${fr.insee.rmes.metadata.keycloak.secret}
authorization-grant-type: client_credentials
scope: ${fr.insee.rmes.metadata.keycloak.scopes}
provider:
ddi-as-provider: # same value as the key spring.security.client.registration.colectica-client.provider
issuer-uri: "${fr.insee.rmes.metadata.keycloak.server}/..."

springdoc:
swagger-ui:
Expand All @@ -86,13 +97,7 @@ logging:
rollingpolicy:
max-file-size: 1MB
file:
name: XXXX

auth:
api:
url: XXX
username: XXX
password: XXX
path: @project.build.directory@/ddi-as.log

item:
api:
Expand Down
Loading