Skip to content

Commit

Permalink
Merge pull request #276 from InseeFr/develop
Browse files Browse the repository at this point in the history
feat: develop to main
  • Loading branch information
davdarras authored Dec 6, 2024
2 parents aed291f + 67aa4af commit ddf9536
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 78 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
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'
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<description>Modules for queen back-office</description>

<properties>
<revision>4.3.19</revision>
<revision>4.3.22</revision>
<changelist></changelist>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
Expand All @@ -30,7 +30,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<version>3.3.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand Down
2 changes: 1 addition & 1 deletion queen-application/Dockerfile
Original file line number Diff line number Diff line change
@@ -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/
Expand Down
2 changes: 1 addition & 1 deletion queen-application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<commons-io.version>2.17.0</commons-io.version>
<org-json.version>20240303</org-json.version>
<guava.version>33.3.1-jre</guava.version>
<json-schema-validator.version>1.5.2</json-schema-validator.version>
<json-schema-validator.version>1.5.3</json-schema-validator.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,58 @@

@AllArgsConstructor
public class GrantedAuthorityConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
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<String, List<SimpleGrantedAuthority>> 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<GrantedAuthority> convert(@NonNull Jwt jwt) {
List<String> roles = getRoles(jwt);
List<String> 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<String> getRoles(Jwt jwt) {
private List<String> getUserRoles(Jwt jwt) {
Map<String, Object> claims = jwt.getClaims();

if(oidcProperties.roleClaim().isEmpty()) {
Map<String, Object> realmAccess = jwt.getClaim(REALM_ACCESS);
return (List<String>) realmAccess.get(ROLES);
return (List<String>) realmAccess.get(REALM_ACCESS_ROLE);
}
return (List<String>) claims.get(oidcProperties.roleClaim());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion queen-application/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY}</maxHistory>
</rollingPolicy>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ class GrantedAuthorityConverterTest {

private OidcProperties oidcProperties;

private Map<String, Object> 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";
Expand All @@ -35,24 +33,20 @@ 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
@DisplayName("Given a JWT, when converting null or empty JWT role, then converting ignore these roles")
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<String, Object> claims = new HashMap<>();
List<String> 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<GrantedAuthority> authorities = converter.convert(jwt);
assertThat(authorities).isEmpty();
}
Expand All @@ -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<String, Object> claims = new HashMap<>();
List<String> 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<GrantedAuthority> 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<String> tokenRoles = List.of(dummyRole, "role-not-used", dummyRole2, "role-not-used-2");
Jwt jwt = createJwt(tokenRoles);

Collection<GrantedAuthority> 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<String, Object> claims = new HashMap<>();
List<String> 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<GrantedAuthority> 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<String, Object> claims = new HashMap<>();
Map<String, Object> roleClaims = new HashMap<>();
List<String> 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<String, Object> jwtHeaders = new HashMap<>();
jwtHeaders.put("header", "headerValue");

Jwt jwt = new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
Collection<GrantedAuthority> 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<Arguments> provideJWTRoleWithAppRoleAssociated() {
Expand All @@ -122,4 +135,16 @@ private static Stream<Arguments> provideJWTRoleWithAppRoleAssociated() {
Arguments.of(JWT_ROLE_WEBCLIENT, AuthorityRoleEnum.WEBCLIENT),
Arguments.of(JWT_ROLE_SURVEY_UNIT, AuthorityRoleEnum.SURVEY_UNIT));
}

private Jwt createJwt(List<String> tokenRoles) {
Map<String, Object> jwtHeaders = new HashMap<>();
jwtHeaders.put("header", "headerValue");

Map<String, Object> claims = new HashMap<>();
Map<String, List<String>> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -51,7 +50,7 @@ public JwtAuthenticationToken getSurveyUnitUser() {
public JwtAuthenticationToken getAuthenticatedUser(AuthorityRoleEnum... roles) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (AuthorityRoleEnum role : roles) {
authorities.add(new SimpleGrantedAuthority(AuthConstants.ROLE_PREFIX + role.name()));
authorities.add(new SimpleGrantedAuthority(role.securityRole()));
}

Map<String, Object> headers = Map.of("typ", "JWT");
Expand All @@ -63,7 +62,7 @@ public JwtAuthenticationToken getAuthenticatedUser(AuthorityRoleEnum... roles) {

public AnonymousAuthenticationToken getNotAuthenticatedUser() {
Map<String, String> 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;
}
Expand Down
4 changes: 4 additions & 0 deletions queen-infra-depositproof/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>

Expand Down

0 comments on commit ddf9536

Please sign in to comment.