diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml
new file mode 100644
index 00000000..dba11795
--- /dev/null
+++ b/.github/workflows/trivy.yml
@@ -0,0 +1,28 @@
+name: Trivy Analysis
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+jobs:
+ build:
+ name: Build and analyze
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: 'temurin'
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@0.28.0
+ env:
+ TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2
+ with:
+ format: 'table'
+ scan-type: 'repo'
+ exit-code: '1'
+ vuln-type: 'os,library'
+ severity: 'CRITICAL,HIGH'
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..dd3738a2
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,14 @@
+repos:
+ - repo: https://github.com/compilerla/conventional-pre-commit
+ rev: v3.6.0
+ hooks:
+ - id: conventional-pre-commit
+ stages: [commit-msg]
+ args: [] # optional: list of Conventional Commits types to allow e.g. [feat, fix, ci, chore, test]
+ - repo: local
+ hooks:
+ - id: trivy-scan
+ name: Trivy scan
+ entry: trivy fs . --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 1
+ language: system
+ pass_filenames: false
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 6933d3e0..8aa7b8b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
Modules for queen back-office
- 4.3.19
+ 4.3.22
21
21
@@ -30,7 +30,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.3.4
+ 3.3.6
diff --git a/queen-application/Dockerfile b/queen-application/Dockerfile
index 14bf3335..addc9f92 100644
--- a/queen-application/Dockerfile
+++ b/queen-application/Dockerfile
@@ -1,4 +1,4 @@
-FROM eclipse-temurin:21.0.4_7-jre-alpine
+FROM eclipse-temurin:21.0.5_11-jre-alpine
ENV PATH_TO_JAR=/opt/app/app.jar
WORKDIR /opt/app/
diff --git a/queen-application/pom.xml b/queen-application/pom.xml
index da10d019..9a28111f 100644
--- a/queen-application/pom.xml
+++ b/queen-application/pom.xml
@@ -19,7 +19,7 @@
2.17.0
20240303
33.3.1-jre
- 1.5.2
+ 1.5.3
diff --git a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthConstants.java b/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthConstants.java
deleted file mode 100644
index 5fe63ad2..00000000
--- a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthConstants.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package fr.insee.queen.application.configuration.auth;
-
-public class AuthConstants {
- private AuthConstants() {
- throw new IllegalStateException("Constants class");
- }
-
- public static final String ROLE_PREFIX = "ROLE_";
-}
diff --git a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthorityRoleEnum.java b/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthorityRoleEnum.java
index adcbca98..998c4bf5 100644
--- a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthorityRoleEnum.java
+++ b/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/AuthorityRoleEnum.java
@@ -6,5 +6,11 @@ public enum AuthorityRoleEnum {
REVIEWER,
REVIEWER_ALTERNATIVE,
INTERVIEWER,
- SURVEY_UNIT
+ SURVEY_UNIT;
+
+ public static final String ROLE_PREFIX = "ROLE_";
+
+ public String securityRole() {
+ return ROLE_PREFIX + this.name();
+ }
}
diff --git a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverter.java b/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverter.java
index c766521e..229ac0a0 100644
--- a/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverter.java
+++ b/queen-application/src/main/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverter.java
@@ -14,52 +14,58 @@
@AllArgsConstructor
public class GrantedAuthorityConverter implements Converter> {
+ public static final String REALM_ACCESS_ROLE = "roles";
public static final String REALM_ACCESS = "realm_access";
- public static final String ROLES = "roles";
+ private final Map> roles;
private final OidcProperties oidcProperties;
- private final RoleProperties roleProperties;
+ public GrantedAuthorityConverter(OidcProperties oidcProperties, RoleProperties roleProperties) {
+ this.roles = new HashMap<>();
+ this.oidcProperties = oidcProperties;
+ initRole(roleProperties.surveyUnit(), AuthorityRoleEnum.SURVEY_UNIT);
+ initRole(roleProperties.interviewer(), AuthorityRoleEnum.INTERVIEWER);
+ initRole(roleProperties.reviewer(), AuthorityRoleEnum.REVIEWER);
+ initRole(roleProperties.reviewerAlternative(), AuthorityRoleEnum.REVIEWER_ALTERNATIVE);
+ initRole(roleProperties.admin(), AuthorityRoleEnum.ADMIN);
+ initRole(roleProperties.webclient(), AuthorityRoleEnum.WEBCLIENT);
+ }
+
+ @SuppressWarnings("unchecked")
@Override
public Collection convert(@NonNull Jwt jwt) {
- List roles = getRoles(jwt);
+ List userRoles = getUserRoles(jwt);
- return roles.stream()
- .map(role -> {
- if(role == null || role.isEmpty()) {
- return null;
- }
- if (role.equals(roleProperties.surveyUnit())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.SURVEY_UNIT);
- }
- if (role.equals(roleProperties.reviewer())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.REVIEWER);
- }
- if (role.equals(roleProperties.reviewerAlternative())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.REVIEWER_ALTERNATIVE);
- }
- if (role.equals(roleProperties.interviewer())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.INTERVIEWER);
- }
- if (role.equals(roleProperties.admin())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.ADMIN);
- }
- if (role.equals(roleProperties.webclient())) {
- return new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.WEBCLIENT);
- }
- return null;
- })
- .filter(Objects::nonNull)
+ return userRoles.stream()
+ .filter(this.roles::containsKey)
+ .map(this.roles::get)
+ .flatMap(List::stream)
+ .distinct()
.collect(Collectors.toCollection(ArrayList::new));
}
+ private void initRole(String configRole, AuthorityRoleEnum authorityRole) {
+ // config role is not set
+ if(configRole == null || configRole.isBlank()) {
+ return;
+ }
+
+ this.roles.compute(configRole, (key, grantedAuthorities) -> {
+ if(grantedAuthorities == null) {
+ grantedAuthorities = new ArrayList<>();
+ }
+ grantedAuthorities.add(new SimpleGrantedAuthority(authorityRole.securityRole()));
+ return grantedAuthorities;
+ });
+ }
+
@SuppressWarnings("unchecked")
- private List getRoles(Jwt jwt) {
+ private List getUserRoles(Jwt jwt) {
Map claims = jwt.getClaims();
if(oidcProperties.roleClaim().isEmpty()) {
Map realmAccess = jwt.getClaim(REALM_ACCESS);
- return (List) realmAccess.get(ROLES);
+ return (List) realmAccess.get(REALM_ACCESS_ROLE);
}
return (List) claims.get(oidcProperties.roleClaim());
}
diff --git a/queen-application/src/main/java/fr/insee/queen/application/configuration/log/LogInterceptor.java b/queen-application/src/main/java/fr/insee/queen/application/configuration/log/LogInterceptor.java
index fb979710..7d0a4364 100644
--- a/queen-application/src/main/java/fr/insee/queen/application/configuration/log/LogInterceptor.java
+++ b/queen-application/src/main/java/fr/insee/queen/application/configuration/log/LogInterceptor.java
@@ -28,7 +28,7 @@ public boolean preHandle(HttpServletRequest request, @Nonnull HttpServletRespons
Authentication authentication = authenticationHelper.getAuthenticationPrincipal();
- String userId = authentication.getName();
+ String userId = authentication.getName().toUpperCase();
MDC.put("id", fishTag);
MDC.put("path", operationPath);
diff --git a/queen-application/src/main/resources/logback-spring.xml b/queen-application/src/main/resources/logback-spring.xml
index 3f3b11da..b2c7a81a 100644
--- a/queen-application/src/main/resources/logback-spring.xml
+++ b/queen-application/src/main/resources/logback-spring.xml
@@ -9,7 +9,7 @@
${LOG_FILE}
- ${LOG_FILE}.%d{yyyy-MM-dd}.gz
+ ${LOG_FILE}.%d{yyyy-MM-dd}.log.gz
${LOGBACK_ROLLINGPOLICY_MAX_HISTORY}
diff --git a/queen-application/src/test/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverterTest.java b/queen-application/src/test/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverterTest.java
index f0e39f61..2f32fafa 100644
--- a/queen-application/src/test/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverterTest.java
+++ b/queen-application/src/test/java/fr/insee/queen/application/configuration/auth/GrantedAuthorityConverterTest.java
@@ -24,8 +24,6 @@ class GrantedAuthorityConverterTest {
private OidcProperties oidcProperties;
- private Map jwtHeaders;
-
private static final String JWT_ROLE_INTERVIEWER = "interviewer";
private static final String JWT_ROLE_REVIEWER = "reviewer";
private static final String JWT_ROLE_REVIEWER_ALTERNATIVE = "reviewerAlternative";
@@ -35,9 +33,8 @@ class GrantedAuthorityConverterTest {
@BeforeEach
void init() {
- oidcProperties = new OidcProperties(true, "host", "url", "realm", "principal-attribute", "roleClaim", "client-id");
- jwtHeaders = new HashMap<>();
- jwtHeaders.put("header", "headerValue");
+ oidcProperties = new OidcProperties(true, "host", "url", "realm", "principal-attribute", "", "client-id");
+
}
@Test
@@ -45,14 +42,11 @@ void init() {
void testConverter01() {
RoleProperties roleProperties = new RoleProperties("", null, JWT_ROLE_ADMIN, JWT_ROLE_WEBCLIENT, JWT_ROLE_REVIEWER_ALTERNATIVE, JWT_ROLE_SURVEY_UNIT);
converter = new GrantedAuthorityConverter(oidcProperties, roleProperties);
- Map claims = new HashMap<>();
List tokenRoles = new ArrayList<>();
tokenRoles.add(null);
tokenRoles.add("");
- claims.put(oidcProperties.roleClaim(), tokenRoles);
-
- Jwt jwt = new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
+ Jwt jwt = createJwt(tokenRoles);
Collection authorities = converter.convert(jwt);
assertThat(authorities).isEmpty();
}
@@ -62,55 +56,74 @@ void testConverter01() {
void testConverter02() {
RoleProperties roleProperties = new RoleProperties(JWT_ROLE_INTERVIEWER, JWT_ROLE_REVIEWER, JWT_ROLE_ADMIN, JWT_ROLE_WEBCLIENT, JWT_ROLE_REVIEWER_ALTERNATIVE, JWT_ROLE_SURVEY_UNIT);
converter = new GrantedAuthorityConverter(oidcProperties, roleProperties);
- Map claims = new HashMap<>();
List tokenRoles = List.of("dummyRole1", roleProperties.reviewer(), "dummyRole2", roleProperties.interviewer(), "dummyRole3", roleProperties.surveyUnit());
- claims.put(oidcProperties.roleClaim(), tokenRoles);
- Jwt jwt = new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
+ Jwt jwt = createJwt(tokenRoles);
Collection authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(3)
.containsExactlyInAnyOrder(
- new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.INTERVIEWER),
- new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.SURVEY_UNIT),
- new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.REVIEWER));
+ new SimpleGrantedAuthority(AuthorityRoleEnum.INTERVIEWER.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.SURVEY_UNIT.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.REVIEWER.securityRole()));
+ }
+
+ @Test
+ @DisplayName("Given a JWT, when converting roles, then accept a config role can be used for multiple app roles")
+ void testConverter03() {
+ String dummyRole = "dummyRole";
+ String dummyRole2 = "dummyRole2";
+ RoleProperties roleProperties = new RoleProperties(dummyRole, dummyRole, dummyRole2, dummyRole2, null, dummyRole2);
+ oidcProperties = new OidcProperties(true, "host", "url", "realm", "principal-attribute", "", "client-id");
+ converter = new GrantedAuthorityConverter(oidcProperties, roleProperties);
+
+ List tokenRoles = List.of(dummyRole, "role-not-used", dummyRole2, "role-not-used-2");
+ Jwt jwt = createJwt(tokenRoles);
+
+ Collection authorities = converter.convert(jwt);
+ assertThat(authorities)
+ .hasSize(5)
+ .contains(
+ new SimpleGrantedAuthority(AuthorityRoleEnum.INTERVIEWER.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.REVIEWER.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.ADMIN.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.WEBCLIENT.securityRole()),
+ new SimpleGrantedAuthority(AuthorityRoleEnum.SURVEY_UNIT.securityRole()));
}
@ParameterizedTest
@MethodSource("provideJWTRoleWithAppRoleAssociated")
@DisplayName("Given a JWT, when converting roles, then assure each JWT role is converted to equivalent app role")
- void testConverter03(String jwtRole, AuthorityRoleEnum appRole) {
+ void testConverter04(String jwtRole, AuthorityRoleEnum appRole) {
RoleProperties roleProperties = new RoleProperties(JWT_ROLE_INTERVIEWER, JWT_ROLE_REVIEWER, JWT_ROLE_ADMIN, JWT_ROLE_WEBCLIENT, JWT_ROLE_REVIEWER_ALTERNATIVE, JWT_ROLE_SURVEY_UNIT);
converter = new GrantedAuthorityConverter(oidcProperties, roleProperties);
- Map claims = new HashMap<>();
List tokenRoles = List.of(jwtRole);
- claims.put(oidcProperties.roleClaim(), tokenRoles);
- Jwt jwt = new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
+ Jwt jwt = createJwt(tokenRoles);
Collection authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(1)
- .contains(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + appRole));
+ .contains(new SimpleGrantedAuthority(appRole.securityRole()));
}
@Test
- @DisplayName("Given a JWT, when no role claim is defined, then default role claim is used")
- void testConverter04() {
- oidcProperties = new OidcProperties(true, "host", "url", "realm", "principal-attribute", "", "client-id");
+ @DisplayName("Given a JWT, when role claim is defined, then role claim is used to retrieve roles")
+ void testConverter05() {
+ oidcProperties = new OidcProperties(true, "host", "url", "realm", "principal-attribute", "roleClaim", "client-id");
RoleProperties roleProperties = new RoleProperties(JWT_ROLE_INTERVIEWER, JWT_ROLE_REVIEWER, JWT_ROLE_ADMIN, JWT_ROLE_WEBCLIENT, JWT_ROLE_REVIEWER_ALTERNATIVE, JWT_ROLE_SURVEY_UNIT);
converter = new GrantedAuthorityConverter(oidcProperties, roleProperties);
Map claims = new HashMap<>();
- Map roleClaims = new HashMap<>();
List tokenRoles = List.of(JWT_ROLE_INTERVIEWER, JWT_ROLE_REVIEWER);
- roleClaims.put(GrantedAuthorityConverter.ROLES, tokenRoles);
- claims.put(GrantedAuthorityConverter.REALM_ACCESS, roleClaims);
+ claims.put(oidcProperties.roleClaim(), tokenRoles);
+ Map jwtHeaders = new HashMap<>();
+ jwtHeaders.put("header", "headerValue");
Jwt jwt = new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
Collection authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(2)
- .contains(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.INTERVIEWER))
- .contains(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + AuthorityRoleEnum.REVIEWER));
+ .contains(new SimpleGrantedAuthority(AuthorityRoleEnum.INTERVIEWER.securityRole()))
+ .contains(new SimpleGrantedAuthority(AuthorityRoleEnum.REVIEWER.securityRole()));
}
private static Stream provideJWTRoleWithAppRoleAssociated() {
@@ -122,4 +135,16 @@ private static Stream provideJWTRoleWithAppRoleAssociated() {
Arguments.of(JWT_ROLE_WEBCLIENT, AuthorityRoleEnum.WEBCLIENT),
Arguments.of(JWT_ROLE_SURVEY_UNIT, AuthorityRoleEnum.SURVEY_UNIT));
}
+
+ private Jwt createJwt(List tokenRoles) {
+ Map jwtHeaders = new HashMap<>();
+ jwtHeaders.put("header", "headerValue");
+
+ Map claims = new HashMap<>();
+ Map> realmRoles = new HashMap<>();
+ realmRoles.put(GrantedAuthorityConverter.REALM_ACCESS_ROLE, tokenRoles);
+ claims.put(GrantedAuthorityConverter.REALM_ACCESS, realmRoles);
+
+ return new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
+ }
}
diff --git a/queen-application/src/test/java/fr/insee/queen/application/utils/AuthenticatedUserTestHelper.java b/queen-application/src/test/java/fr/insee/queen/application/utils/AuthenticatedUserTestHelper.java
index 41a20703..f7ccf73e 100644
--- a/queen-application/src/test/java/fr/insee/queen/application/utils/AuthenticatedUserTestHelper.java
+++ b/queen-application/src/test/java/fr/insee/queen/application/utils/AuthenticatedUserTestHelper.java
@@ -1,6 +1,5 @@
package fr.insee.queen.application.utils;
-import fr.insee.queen.application.configuration.auth.AuthConstants;
import fr.insee.queen.application.configuration.auth.AuthorityRoleEnum;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@@ -51,7 +50,7 @@ public JwtAuthenticationToken getSurveyUnitUser() {
public JwtAuthenticationToken getAuthenticatedUser(AuthorityRoleEnum... roles) {
List authorities = new ArrayList<>();
for (AuthorityRoleEnum role : roles) {
- authorities.add(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + role.name()));
+ authorities.add(new SimpleGrantedAuthority(role.securityRole()));
}
Map headers = Map.of("typ", "JWT");
@@ -63,7 +62,7 @@ public JwtAuthenticationToken getAuthenticatedUser(AuthorityRoleEnum... roles) {
public AnonymousAuthenticationToken getNotAuthenticatedUser() {
Map principal = new HashMap<>();
- AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("id", principal, List.of(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + "ANONYMOUS")));
+ AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("id", principal, List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
auth.setAuthenticated(false);
return auth;
}
diff --git a/queen-infra-depositproof/pom.xml b/queen-infra-depositproof/pom.xml
index 27dd82ce..f1c9a3ba 100644
--- a/queen-infra-depositproof/pom.xml
+++ b/queen-infra-depositproof/pom.xml
@@ -42,6 +42,10 @@
commons-logging
commons-logging
+
+ commons-io
+ commons-io
+