Skip to content

Commit 4563748

Browse files
Merge pull request #185 from InseeFr/develop
Habilitations and refactor raw data save
2 parents f4f805e + c193730 commit 4563748

File tree

79 files changed

+184648
-742
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+184648
-742
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@
138138
<version>${cucumber.version}</version>
139139
<scope>test</scope>
140140
</dependency>
141+
<dependency>
142+
<groupId>io.cucumber</groupId>
143+
<artifactId>cucumber-spring</artifactId>
144+
<version>${cucumber.version}</version>
145+
<scope>test</scope>
146+
</dependency>
141147

142148

143149
<dependency>

qodana.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#-------------------------------------------------------------------------------#
2+
# Qodana analysis is configured by qodana.yaml file #
3+
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
4+
#-------------------------------------------------------------------------------#
5+
version: "1.0"
6+
7+
#Specify inspection profile for code analysis
8+
profile:
9+
name: qodana.starter
10+
11+
#Enable inspections
12+
#include:
13+
# - name: <SomeEnabledInspectionId>
14+
15+
#Disable inspections
16+
#exclude:
17+
# - name: <SomeDisabledInspectionId>
18+
# paths:
19+
# - <path/where/not/run/inspection>
20+
21+
projectJDK: 21 #(Applied in CI/CD pipeline)
22+
23+
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
24+
#bootstrap: sh ./prepare-qodana.sh
25+
26+
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
27+
#plugins:
28+
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
29+
30+
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
31+
linter: jetbrains/qodana-jvm-community:latest

src/main/java/fr/insee/genesis/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ public class Constants {
1010
"(^([0-9]|[0-2][0-9]|3[0-1])[\\-\\/]([0-9]|1[0-2]|0[1-9])[\\-\\/]([0-9]{4})$)";
1111
public static final String FILTER_RESULT_PREFIX = "FILTER_RESULT_";
1212
public static final String MISSING_SUFFIX = "_MISSING";
13+
public static final String MONGODB_LUNATIC_RAWDATA_COLLECTION_NAME = "lunaticjsondata";
1314
private static final String[] ENO_VARIABLES = {"COMMENT_QE","COMMENT_UE","HEURE_REMPL","MIN_REMPL"};
1415

1516
public static final String MONGODB_SCHEDULE_COLLECTION_NAME = "schedules";
1617
public static final String LOOP_NAME_PREFIX = "BOUCLE";
1718
public static final String MONGODB_RESPONSE_COLLECTION_NAME = "responses";
19+
public static final String MONGODB_VARIABLETYPE_COLLECTION_NAME = "variabletypes";
1820
public static final String VOLUMETRY_FOLDER_NAME = "genesis_volumetries";
1921
public static final String VOLUMETRY_FILE_SUFFIX = "_VOLUMETRY";
2022
public static final String VOLUMETRY_FILE_DATE_FORMAT = "yyyy_MM_dd";

src/main/java/fr/insee/genesis/configuration/Config.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import lombok.Getter;
44
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.cache.annotation.EnableCaching;
56
import org.springframework.context.annotation.Configuration;
67

78
import java.nio.file.Path;
89

910
@Configuration
1011
@Getter
12+
@EnableCaching
1113
public class Config {
1214

1315
/******************************************************/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package fr.insee.genesis.configuration.auth.security;
2+
3+
public enum ApplicationRole {
4+
ADMIN,
5+
USER_KRAFTWERK,
6+
USER_PLATINE,
7+
COLLECT_PLATFORM,
8+
READER
9+
}
10+
Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,120 @@
11
package fr.insee.genesis.configuration.auth.security;
22

3-
import fr.insee.genesis.configuration.Config;
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.Setter;
46
import lombok.extern.slf4j.Slf4j;
5-
import org.springframework.beans.factory.annotation.Autowired;
67
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8+
import org.springframework.boot.context.properties.ConfigurationProperties;
79
import org.springframework.context.annotation.Bean;
810
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.core.convert.converter.Converter;
12+
import org.springframework.http.HttpMethod;
913
import org.springframework.security.config.Customizer;
14+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
1015
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1116
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1217
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
1318
import org.springframework.security.config.http.SessionCreationPolicy;
19+
import org.springframework.security.core.GrantedAuthority;
20+
import org.springframework.security.oauth2.jwt.Jwt;
21+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
1422
import org.springframework.security.web.SecurityFilterChain;
23+
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
1524
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
1625

26+
import java.util.Collection;
27+
import java.util.Collections;
28+
import java.util.List;
29+
import java.util.Map;
30+
1731
@Configuration
1832
@EnableWebSecurity
33+
@EnableMethodSecurity(prePostEnabled = true)
1934
@Slf4j
2035
@ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "OIDC")
36+
@ConfigurationProperties(prefix = "fr.insee.genesis.security")
37+
@RequiredArgsConstructor
2138
public class OIDCSecurityConfig {
2239

23-
Config config;
24-
@Autowired
25-
public OIDCSecurityConfig(Config config) {
26-
this.config = config;
27-
}
40+
@Getter
41+
@Setter
42+
private String[] whitelistMatchers;
43+
private static final String ROLE_PREFIX = "ROLE_";
44+
private final RoleConfiguration roleConfiguration;
45+
private final SecurityTokenProperties inseeSecurityTokenProperties;
2846

29-
@Bean
30-
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
31-
http
32-
.csrf(AbstractHttpConfigurer::disable)
33-
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
34-
for (var pattern : config.getWhiteList()) {
35-
http.authorizeHttpRequests(authorize ->
36-
authorize
37-
.requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll()
38-
);
39-
}
40-
http
41-
.authorizeHttpRequests(configurer -> configurer
42-
.anyRequest().authenticated()
43-
)
44-
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
45-
return http.build();
47+
@Bean
48+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
49+
http
50+
.csrf(AbstractHttpConfigurer::disable)
51+
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
52+
for (var pattern : whitelistMatchers) {
53+
http.authorizeHttpRequests(authorize ->
54+
authorize
55+
.requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll()
56+
);
4657
}
58+
http
59+
.authorizeHttpRequests(configurer -> configurer
60+
.requestMatchers(HttpMethod.GET,"/questionnaires/**").hasRole(String.valueOf(ApplicationRole.READER))
61+
.requestMatchers(HttpMethod.GET,"/modes/**").hasRole(String.valueOf(ApplicationRole.READER))
62+
.requestMatchers(HttpMethod.GET,"/interrogations/**").hasRole(String.valueOf(ApplicationRole.READER))
63+
.requestMatchers(HttpMethod.GET,"/campaigns/**").hasRole(String.valueOf(ApplicationRole.READER))
64+
.anyRequest().authenticated()
65+
)
66+
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
67+
return http.build();
68+
}
69+
70+
@Bean
71+
JwtAuthenticationConverter jwtAuthenticationConverter() {
72+
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
73+
jwtAuthenticationConverter.setPrincipalClaimName(inseeSecurityTokenProperties.getOidcClaimUsername());
74+
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter());
75+
return jwtAuthenticationConverter;
76+
}
4777

78+
79+
Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
80+
return new Converter<Jwt, Collection<GrantedAuthority>>() {
81+
@Override
82+
@SuppressWarnings({"unchecked"})
83+
public Collection<GrantedAuthority> convert(Jwt source) {
84+
85+
String[] claimPath = inseeSecurityTokenProperties.getOidcClaimRole().split("\\.");
86+
Map<String, Object> claims = source.getClaims();
87+
try {
88+
for (int i = 0; i < claimPath.length - 1; i++) {
89+
claims = (Map<String, Object>) claims.get(claimPath[i]);
90+
}
91+
if (claims != null) {
92+
List<String> tokenClaims = (List<String>) claims.getOrDefault(claimPath[claimPath.length - 1], List.of());
93+
// Collect distinct values from mapping associated with input keys
94+
List<String> claimedRoles = tokenClaims.stream()
95+
.filter(roleConfiguration.getRolesByClaim()::containsKey) // Ensure the key exists in the mapping
96+
.flatMap(key -> roleConfiguration.getRolesByClaim().get(key).stream()) // Get the list of values associated with the key
97+
.distinct() // Remove duplicates
98+
.toList();
99+
100+
return Collections.unmodifiableCollection(claimedRoles.stream().map(s -> new GrantedAuthority() {
101+
@Override
102+
public String getAuthority() {
103+
return ROLE_PREFIX + s;
104+
}
105+
106+
@Override
107+
public String toString() {
108+
return getAuthority();
109+
}
110+
}).toList());
111+
}
112+
} catch (ClassCastException e) {
113+
// role path not correctly found, assume that no role for this user
114+
return List.of();
115+
}
116+
return List.of();
117+
}
118+
};
119+
}
48120
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package fr.insee.genesis.configuration.auth.security;
2+
3+
import jakarta.annotation.PostConstruct;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
9+
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
10+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
11+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
12+
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
18+
@Configuration
19+
@Slf4j
20+
public class RoleConfiguration {
21+
22+
//Mapping des claims du jeton sur les roles applicatifs
23+
@Value("#{'${app.role.admin.claims}'.split(',')}")
24+
private List<String> adminClaims;
25+
@Value("#{'${app.role.user-kraftwerk.claims}'.split(',')}")
26+
private List<String> userKraftwerkClaims;
27+
@Value("#{'${app.role.user-platine.claims}'.split(',')}")
28+
private List<String> userPlatineClaims;
29+
@Value("#{'${app.role.reader.claims}'.split(',')}")
30+
private List<String> readerClaims;
31+
@Value("#{'${app.role.collect-platform.claims}'.split(',')}")
32+
private List<String> collectPlatformClaims;
33+
34+
public Map<String, List<String>> getRolesByClaim() {
35+
return rolesByClaim;
36+
}
37+
38+
private Map<String, List<String>> rolesByClaim;
39+
40+
//Defines a role hierarchy
41+
//ADMIN implies USER role too
42+
//USER implies READER role too
43+
//so an admin has 2 roles: ADMIN/USER
44+
@Bean
45+
static RoleHierarchy roleHierarchy() {
46+
return RoleHierarchyImpl.withDefaultRolePrefix()
47+
.role(ApplicationRole.ADMIN.toString()).implies(ApplicationRole.USER_KRAFTWERK.toString())
48+
.role(ApplicationRole.ADMIN.toString()).implies(ApplicationRole.USER_PLATINE.toString())
49+
.role(ApplicationRole.ADMIN.toString()).implies(ApplicationRole.COLLECT_PLATFORM.toString())
50+
.role(ApplicationRole.USER_KRAFTWERK.toString()).implies(ApplicationRole.READER.toString())
51+
.role(ApplicationRole.USER_PLATINE.toString()).implies(ApplicationRole.READER.toString())
52+
.build();
53+
}
54+
55+
// and, if using pre-post method security also add
56+
@Bean
57+
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
58+
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
59+
expressionHandler.setRoleHierarchy(roleHierarchy);
60+
return expressionHandler;
61+
}
62+
63+
@PostConstruct
64+
public void initialization() {
65+
66+
rolesByClaim = new HashMap<>();
67+
68+
// Ajout des claims pour le rôle ADMIN
69+
adminClaims.forEach(claim -> rolesByClaim
70+
.computeIfAbsent(claim, k -> new ArrayList<>())
71+
.add(String.valueOf(ApplicationRole.ADMIN)));
72+
73+
// Ajout des claims pour le rôle USER_KRAFTWERK
74+
userKraftwerkClaims.forEach(claim -> rolesByClaim
75+
.computeIfAbsent(claim, k -> new ArrayList<>())
76+
.add(String.valueOf(ApplicationRole.USER_KRAFTWERK)));
77+
78+
// Ajout des claims pour le rôle USER_PLATINE
79+
userPlatineClaims.forEach(claim -> rolesByClaim
80+
.computeIfAbsent(claim, k -> new ArrayList<>())
81+
.add(String.valueOf(ApplicationRole.USER_PLATINE)));
82+
83+
// Ajout des claims pour le rôle COLLECT_PLATFORM
84+
collectPlatformClaims.forEach(claim -> rolesByClaim
85+
.computeIfAbsent(claim, k -> new ArrayList<>())
86+
.add(String.valueOf(ApplicationRole.COLLECT_PLATFORM)));
87+
88+
// Ajout des claims pour le rôle READER
89+
readerClaims.forEach(claim -> rolesByClaim
90+
.computeIfAbsent(claim, k -> new ArrayList<>())
91+
.add(String.valueOf(ApplicationRole.READER)));
92+
93+
94+
log.info("Roles configuration : {}", rolesByClaim);
95+
}
96+
97+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package fr.insee.genesis.configuration.auth.security;
2+
3+
import lombok.Data;
4+
import org.springframework.boot.context.properties.ConfigurationProperties;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@Configuration
8+
@ConfigurationProperties(
9+
prefix = "fr.insee.genesis.security.token"
10+
)
11+
@Data
12+
public class SecurityTokenProperties {
13+
14+
//Chemin pour récupérer la liste des rôles dans le jwt (token)
15+
private String oidcClaimRole;
16+
//Chemin pour récupérer le username dans le jwt (token)
17+
private String oidcClaimUsername;
18+
19+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package fr.insee.genesis.controller.dto.rawdata;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record LunaticJsonRawDataUnprocessedDto(String campaignId, String interrogationId){}

0 commit comments

Comments
 (0)