diff --git a/pom.xml b/pom.xml index f072cf8..18cea2f 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ spring-boot-starter-oauth2-client + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + org.springframework.boot spring-boot-starter-data-jpa -- 2.51.2 From b6e3ceb45e3bb782373210d2e7afed025bb95bbc Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 28 Aug 2023 16:45:09 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=92=20:=20add=20id=5Ftoken=20secur?= =?UTF-8?q?ity=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GitlabIDTokenSecurityConfiguration.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java b/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java new file mode 100644 index 0000000..5d735df --- /dev/null +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java @@ -0,0 +1,24 @@ +package fr.univ_lille.gitlab.classrooms.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Specific configuration to accept Gitlab ID_TOKENS as an authentication mecanism + */ +@Configuration +public class GitlabIDTokenSecurityConfiguration { + + @Bean + SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception { + return http + .securityMatcher("/api/**") + .authorizeHttpRequests(authorize -> authorize.requestMatchers("/api/**").authenticated()) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) + .build(); + } + +} -- 2.51.2 From 839fa515e1e6da0cd317e2564e679f4046639f40 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 28 Aug 2023 17:53:02 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=A7=20:=20configure=20jwt=20decode?= =?UTF-8?q?r=20issuer=20uri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 2 ++ src/test/resources/application.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 932acd3..53db70b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,3 +21,5 @@ spring.datasource.password=${POSTGRESQL_ADDON_PASSWORD} spring.jpa.generate-ddl=false spring.jpa.hibernate.ddl-auto=validate + +spring.security.oauth2.resourceserver.jwt.issuer-uri=${gitlab.url} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index bef1c43..2836134 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -15,3 +15,5 @@ spring.security.oauth2.client.registration.gitlab.redirect-uri={baseUrl}/login/o spring.security.oauth2.client.registration.gitlab.scope[0]=api spring.flyway.enabled=false + +spring.security.oauth2.resourceserver.jwt.issuer-uri=${gitlab.url} -- 2.51.2 From 7bd69f8c85864bb36b2e5157959d09bb4ca54a85 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 28 Aug 2023 18:06:37 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20:=20add=20JWTAuthenticationToke?= =?UTF-8?q?n=20support,=20for=20Gitlab=20ID=20Tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/UserControllerAdvice.java | 5 ++ .../users/UserControllerAdviceTest.java | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/test/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdviceTest.java diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdvice.java b/src/main/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdvice.java index 4448eae..693e81e 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdvice.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdvice.java @@ -1,5 +1,6 @@ package fr.univ_lille.gitlab.classrooms.users; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; @@ -23,6 +24,10 @@ public class UserControllerAdvice { @ModelAttribute("user") ClassroomUser classroomUser(Principal authenticationPrincipal){ try{ + // if user is a JWTAuthenticationToken, (gitlab id token), load the user using the "user_login" claim + if(authenticationPrincipal instanceof JwtAuthenticationToken jwtToken){ + return this.classroomUserService.getClassroomUser(jwtToken.getTokenAttributes().get("user_login").toString()); + } return this.classroomUserService.getClassroomUser(authenticationPrincipal.getName()); } catch(NoSuchElementException e){ diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdviceTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdviceTest.java new file mode 100644 index 0000000..12b3c91 --- /dev/null +++ b/src/test/java/fr/univ_lille/gitlab/classrooms/users/UserControllerAdviceTest.java @@ -0,0 +1,81 @@ +package fr.univ_lille.gitlab.classrooms.users; + +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.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserControllerAdviceTest { + + @InjectMocks + private UserControllerAdvice userControllerAdvice; + + @Mock + private ClassroomUserService classroomUserService; + + @Test + void classroomUser_for_OauthAuthenticationToken() { + var vader = new ClassroomUser("darth.vader", List.of(ClassroomRole.STUDENT)); + when(classroomUserService.getClassroomUser("darth.vader")).thenReturn(vader); + + var oauth2User = new DefaultOAuth2User(null, Map.of("nameAttr", "darth.vader"), "nameAttr"); + var token = new OAuth2AuthenticationToken(oauth2User, null, "oauth"); + + var user = this.userControllerAdvice.classroomUser(token); + + assertThat(user) + .isNotNull() + .isEqualTo(vader); + + verify(classroomUserService).getClassroomUser("darth.vader"); + } + + @Test + void classroomUser_for_JWTAuthenticationToken() { + var vader = new ClassroomUser("darth.vader", List.of(ClassroomRole.STUDENT)); + when(classroomUserService.getClassroomUser("darth.vader")).thenReturn(vader); + + var jwt = Jwt.withTokenValue("token") + .header("alg", "none") + .claim("sub", "user") + .claim("user_login", "darth.vader") + .build(); + var token = new JwtAuthenticationToken(jwt); + + var user = this.userControllerAdvice.classroomUser(token); + + assertThat(user) + .isNotNull() + .isEqualTo(vader); + + verify(classroomUserService).getClassroomUser("darth.vader"); + } + + @Test + void classroomUser_returnsNull_whenUserIsNotFound() { + when(classroomUserService.getClassroomUser(anyString())).thenThrow(new NoSuchElementException()); + + var oauth2User = new DefaultOAuth2User(null, Map.of("nameAttr", "darth.vader"), "nameAttr"); + var token = new OAuth2AuthenticationToken(oauth2User, null, "oauth"); + + var user = this.userControllerAdvice.classroomUser(token); + + assertThat(user).isNull(); + + verify(classroomUserService).getClassroomUser("darth.vader"); + } +}