From 14042c885560f6c8092c319b77efde935cb8aa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 6 Aug 2023 15:34:43 +0200 Subject: [PATCH 01/39] [AC-7] Changed microservice name in source files --- .github/workflows/prd-build-deploy.yml | 4 ++-- .github/workflows/stg-build-deploy.yml | 4 ++-- docker/prd/prd-build-deploy-job.yml | 4 ++-- docker/prd/prd-build-deploy.Dockerfile | 2 +- docker/prd/prd-run-database-job.yml | 8 ++++---- docker/stg/stg-build-deploy-job.yml | 4 ++-- docker/stg/stg-build-deploy.Dockerfile | 2 +- docker/stg/stg-run-database-job.yml | 8 ++++---- pom.xml | 6 +++--- .../AuthApplication.java} | 6 +++--- .../controller/ExampleController.java | 8 ++++---- .../{example => auth}/data/entity/Example.java | 2 +- .../data/repository/ExampleRepostiory.java | 4 ++-- .../mapper/example/ExampleMapper.java | 4 ++-- src/main/resources/application-dev.yml | 6 +++--- src/main/resources/application-prd.yml | 12 ++++++------ src/main/resources/application-stg.yml | 12 ++++++------ .../AuthApplicationIT.java} | 6 +++--- .../controller/ExampleControllerIT.java | 4 ++-- .../reusablecontainers/DatabaseContainerIT.java | 2 +- 20 files changed, 54 insertions(+), 54 deletions(-) rename src/main/java/xyz/aimcup/{example/ExampleApplication.java => auth/AuthApplication.java} (70%) rename src/main/java/xyz/aimcup/{example => auth}/controller/ExampleController.java (84%) rename src/main/java/xyz/aimcup/{example => auth}/data/entity/Example.java (96%) rename src/main/java/xyz/aimcup/{example => auth}/data/repository/ExampleRepostiory.java (71%) rename src/main/java/xyz/aimcup/{example => auth}/mapper/example/ExampleMapper.java (78%) rename src/test/java/xyz/aimcup/{example/ExampleApplicationIT.java => auth/AuthApplicationIT.java} (56%) rename src/test/java/xyz/aimcup/{example => auth}/controller/ExampleControllerIT.java (93%) rename src/test/java/xyz/aimcup/{example => auth}/reusablecontainers/DatabaseContainerIT.java (96%) diff --git a/.github/workflows/prd-build-deploy.yml b/.github/workflows/prd-build-deploy.yml index b283181..4f769c9 100644 --- a/.github/workflows/prd-build-deploy.yml +++ b/.github/workflows/prd-build-deploy.yml @@ -14,9 +14,9 @@ jobs: run: | docker-compose -f docker/prd/prd-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.EXAMPLE_DB_PRD_PASSWORD }}" + POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_PRD_PASSWORD }}" - name: Build and deploy the Docker image run: | docker-compose -f docker/prd/prd-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.EXAMPLE_DB_PRD_PASSWORD }}" \ No newline at end of file + POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_PRD_PASSWORD }}" \ No newline at end of file diff --git a/.github/workflows/stg-build-deploy.yml b/.github/workflows/stg-build-deploy.yml index ce5643a..c31092c 100644 --- a/.github/workflows/stg-build-deploy.yml +++ b/.github/workflows/stg-build-deploy.yml @@ -17,9 +17,9 @@ jobs: run: | docker-compose -f docker/stg/stg-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.EXAMPLE_DB_STG_PASSWORD }} + POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_STG_PASSWORD }} - name: Build and deploy the Docker image run: | docker-compose -f docker/stg/stg-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.EXAMPLE_DB_STG_PASSWORD }} \ No newline at end of file + POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_STG_PASSWORD }} \ No newline at end of file diff --git a/docker/prd/prd-build-deploy-job.yml b/docker/prd/prd-build-deploy-job.yml index f00a690..33cd12b 100644 --- a/docker/prd/prd-build-deploy-job.yml +++ b/docker/prd/prd-build-deploy-job.yml @@ -1,12 +1,12 @@ version: '3.8' services: - example-microservice: + auth-microservice: build: context: ../.. dockerfile: docker/prd/prd-build-deploy.Dockerfile ports: - - "8101:8101" + - "8501:8501" environment: - EXAMPLE_DB_PRD_PASSWORD=${POSTGRES_PASSWORD} diff --git a/docker/prd/prd-build-deploy.Dockerfile b/docker/prd/prd-build-deploy.Dockerfile index 3bae33b..002b3be 100644 --- a/docker/prd/prd-build-deploy.Dockerfile +++ b/docker/prd/prd-build-deploy.Dockerfile @@ -5,5 +5,5 @@ RUN mvn -f /acservice/pom.xml clean package -P prd FROM arm32v7/eclipse-temurin:17 COPY --from=build /acservice/target/*.jar app.jar -EXPOSE 8101 +EXPOSE 8501 ENTRYPOINT ["java","-Dspring.profiles.active=prd","-jar","/app.jar"] diff --git a/docker/prd/prd-run-database-job.yml b/docker/prd/prd-run-database-job.yml index 67a6879..457b4ab 100644 --- a/docker/prd/prd-run-database-job.yml +++ b/docker/prd/prd-run-database-job.yml @@ -1,20 +1,20 @@ version: '3.8' services: - db-example-microservice: + db-auth-microservice: image: postgres:15.1 restart: always environment: - POSTGRES_USER: example-db-username + POSTGRES_USER: auth POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: example-db-name + POSTGRES_DB: auth healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s timeout: 5s retries: 5 ports: - - "5401:5432" + - "5701:5432" networks: default: diff --git a/docker/stg/stg-build-deploy-job.yml b/docker/stg/stg-build-deploy-job.yml index a94d4d0..8f9a5be 100644 --- a/docker/stg/stg-build-deploy-job.yml +++ b/docker/stg/stg-build-deploy-job.yml @@ -1,12 +1,12 @@ version: '3.8' services: - example-microservice: + auth-microservice: build: context: ../.. dockerfile: docker/stg/stg-build-deploy.Dockerfile ports: - - "8201:8201" + - "8502:8502" environment: - EXAMPLE_DB_STG_PASSWORD=${POSTGRES_PASSWORD} diff --git a/docker/stg/stg-build-deploy.Dockerfile b/docker/stg/stg-build-deploy.Dockerfile index d8f1e5b..981f680 100644 --- a/docker/stg/stg-build-deploy.Dockerfile +++ b/docker/stg/stg-build-deploy.Dockerfile @@ -5,5 +5,5 @@ RUN mvn -f /acservice/pom.xml clean package -P stg FROM arm32v7/eclipse-temurin:17 COPY --from=build /acservice/target/*.jar app.jar -EXPOSE 8201 +EXPOSE 8502 ENTRYPOINT ["java","-Dspring.profiles.active=stg","-jar","/app.jar"] diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index fbb95be..b997353 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -1,20 +1,20 @@ version: '3.8' services: - db-example-microservice: + db-auth-microservice: image: postgres:15.1 restart: always environment: - POSTGRES_USER: example-db-username + POSTGRES_USER: auth POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: example-db-name + POSTGRES_DB: auth healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s timeout: 5s retries: 5 ports: - - "5501:5432" + - "5702:5432" networks: default: diff --git a/pom.xml b/pom.xml index c8b5254..044288e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ xyz.aimcup - template-repository - 1.0.0 - template-repository + auth-microservice + 1.0.0-SNAPSHOT + auth-microservice 17 2022.0.3 diff --git a/src/main/java/xyz/aimcup/example/ExampleApplication.java b/src/main/java/xyz/aimcup/auth/AuthApplication.java similarity index 70% rename from src/main/java/xyz/aimcup/example/ExampleApplication.java rename to src/main/java/xyz/aimcup/auth/AuthApplication.java index 7f90d46..8f5fa43 100644 --- a/src/main/java/xyz/aimcup/example/ExampleApplication.java +++ b/src/main/java/xyz/aimcup/auth/AuthApplication.java @@ -1,4 +1,4 @@ -package xyz.aimcup.example; +package xyz.aimcup.auth; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -6,8 +6,8 @@ @SpringBootApplication @EnableDiscoveryClient -public class ExampleApplication { +public class AuthApplication { public static void main(String[] args) { - SpringApplication.run(ExampleApplication.class, args); + SpringApplication.run(AuthApplication.class, args); } } diff --git a/src/main/java/xyz/aimcup/example/controller/ExampleController.java b/src/main/java/xyz/aimcup/auth/controller/ExampleController.java similarity index 84% rename from src/main/java/xyz/aimcup/example/controller/ExampleController.java rename to src/main/java/xyz/aimcup/auth/controller/ExampleController.java index fa5bf8f..b360dec 100644 --- a/src/main/java/xyz/aimcup/example/controller/ExampleController.java +++ b/src/main/java/xyz/aimcup/auth/controller/ExampleController.java @@ -1,4 +1,4 @@ -package xyz.aimcup.example.controller; +package xyz.aimcup.auth.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -6,9 +6,9 @@ import xyz.aimcup.generated.ExamplesApi; import xyz.aimcup.generated.model.ExampleDataRequest; import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.example.data.entity.Example; -import xyz.aimcup.example.data.repository.ExampleRepostiory; -import xyz.aimcup.example.mapper.example.ExampleMapper; +import xyz.aimcup.auth.data.entity.Example; +import xyz.aimcup.auth.data.repository.ExampleRepostiory; +import xyz.aimcup.auth.mapper.example.ExampleMapper; import java.util.List; diff --git a/src/main/java/xyz/aimcup/example/data/entity/Example.java b/src/main/java/xyz/aimcup/auth/data/entity/Example.java similarity index 96% rename from src/main/java/xyz/aimcup/example/data/entity/Example.java rename to src/main/java/xyz/aimcup/auth/data/entity/Example.java index 1221d81..6e6f50f 100644 --- a/src/main/java/xyz/aimcup/example/data/entity/Example.java +++ b/src/main/java/xyz/aimcup/auth/data/entity/Example.java @@ -1,4 +1,4 @@ -package xyz.aimcup.example.data.entity; +package xyz.aimcup.auth.data.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/xyz/aimcup/example/data/repository/ExampleRepostiory.java b/src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java similarity index 71% rename from src/main/java/xyz/aimcup/example/data/repository/ExampleRepostiory.java rename to src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java index a5679af..fc24b9e 100644 --- a/src/main/java/xyz/aimcup/example/data/repository/ExampleRepostiory.java +++ b/src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java @@ -1,8 +1,8 @@ -package xyz.aimcup.example.data.repository; +package xyz.aimcup.auth.data.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import xyz.aimcup.example.data.entity.Example; +import xyz.aimcup.auth.data.entity.Example; import java.util.UUID; diff --git a/src/main/java/xyz/aimcup/example/mapper/example/ExampleMapper.java b/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java similarity index 78% rename from src/main/java/xyz/aimcup/example/mapper/example/ExampleMapper.java rename to src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java index f57d31f..0bf7bbf 100644 --- a/src/main/java/xyz/aimcup/example/mapper/example/ExampleMapper.java +++ b/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java @@ -1,8 +1,8 @@ -package xyz.aimcup.example.mapper.example; +package xyz.aimcup.auth.mapper.example; import org.mapstruct.Mapper; import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.example.data.entity.Example; +import xyz.aimcup.auth.data.entity.Example; import java.util.List; diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index b7cee23..3559954 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -4,12 +4,12 @@ eureka: server: servlet: context-path: /example-path - port: 0 + port: 8080 spring: application: - name: example-microservice + name: auth-microservice datasource: - url: jdbc:postgresql://localhost:5455/example-db-name + url: jdbc:postgresql://localhost:5432/auth username: testUser password: testPassword driver-class-name: org.postgresql.Driver diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index 9e1cfca..cc8344d 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -4,15 +4,15 @@ eureka: defaultZone: http://172.18.0.1:8761/eureka server: servlet: - context-path: /example-path - port: 8101 + context-path: /auth + port: 8501 spring: application: - name: example-microservice + name: auth-microservice datasource: - url: jdbc:postgresql://172.18.0.1:5401/example-db-name - username: example-db-username - password: ${EXAMPLE_DB_PRD_PASSWORD} + url: jdbc:postgresql://172.18.0.1:5701/auth + username: auth + password: ${AUTH_DB_PRD_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index b39523f..e8d57a3 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -4,15 +4,15 @@ eureka: defaultZone: http://172.18.0.1:8762/eureka server: servlet: - context-path: /example-path - port: 8201 + context-path: /auth + port: 8502 spring: application: - name: example-microservice + name: auth-microservice datasource: - url: jdbc:postgresql://172.18.0.1:5501/example-db-name - username: example-db-username - password: ${EXAMPLE_DB_STG_PASSWORD} + url: jdbc:postgresql://172.18.0.1:5702/auth + username: auth + password: ${AUTH_DB_STG_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 diff --git a/src/test/java/xyz/aimcup/example/ExampleApplicationIT.java b/src/test/java/xyz/aimcup/auth/AuthApplicationIT.java similarity index 56% rename from src/test/java/xyz/aimcup/example/ExampleApplicationIT.java rename to src/test/java/xyz/aimcup/auth/AuthApplicationIT.java index 2dcae3a..7bc30aa 100644 --- a/src/test/java/xyz/aimcup/example/ExampleApplicationIT.java +++ b/src/test/java/xyz/aimcup/auth/AuthApplicationIT.java @@ -1,11 +1,11 @@ -package xyz.aimcup.example; +package xyz.aimcup.auth; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import xyz.aimcup.example.reusablecontainers.DatabaseContainerIT; +import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; @SpringBootTest -class ExampleApplicationIT extends DatabaseContainerIT { +class AuthApplicationIT extends DatabaseContainerIT { @Test @SuppressWarnings("squid:S2699") diff --git a/src/test/java/xyz/aimcup/example/controller/ExampleControllerIT.java b/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java similarity index 93% rename from src/test/java/xyz/aimcup/example/controller/ExampleControllerIT.java rename to src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java index 7b583a3..21fc23d 100644 --- a/src/test/java/xyz/aimcup/example/controller/ExampleControllerIT.java +++ b/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java @@ -1,4 +1,4 @@ -package xyz.aimcup.example.controller; +package xyz.aimcup.auth.controller; import org.junit.jupiter.api.Test; @@ -7,7 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import xyz.aimcup.generated.model.ExampleDataRequest; import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.example.reusablecontainers.DatabaseContainerIT; +import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; import java.util.List; diff --git a/src/test/java/xyz/aimcup/example/reusablecontainers/DatabaseContainerIT.java b/src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java similarity index 96% rename from src/test/java/xyz/aimcup/example/reusablecontainers/DatabaseContainerIT.java rename to src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java index 33ffbf5..09e22f6 100644 --- a/src/test/java/xyz/aimcup/example/reusablecontainers/DatabaseContainerIT.java +++ b/src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java @@ -1,4 +1,4 @@ -package xyz.aimcup.example.reusablecontainers; +package xyz.aimcup.auth.reusablecontainers; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; From b654c4b614161cfe17950eb4cfed4382ccd14507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 6 Aug 2023 19:09:00 +0200 Subject: [PATCH 02/39] [AC-7] Added OAuth2 and prepared database for users and roles --- pom.xml | 26 +++++- .../configuration/SecurityConfiguration.java | 70 ++++++++++++++++ .../auth/controller/ExampleController.java | 70 ++++++++-------- .../xyz/aimcup/auth/data/entity/Example.java | 50 ------------ .../xyz/aimcup/auth/data/entity/Role.java | 44 ++++++++++ .../xyz/aimcup/auth/data/entity/RoleName.java | 6 ++ .../xyz/aimcup/auth/data/entity/User.java | 59 ++++++++++++++ .../data/repository/ExampleRepostiory.java | 11 --- .../auth/data/repository/RoleRepository.java | 13 +++ .../auth/data/repository/UserRepository.java | 12 +++ .../auth/mapper/example/ExampleMapper.java | 25 +++--- .../xyz/aimcup/auth/security/CurrentUser.java | 17 ++++ .../security/CustomUserDetailsService.java | 27 +++++++ ...eOAuth2AuthorizationRequestRepository.java | 44 ++++++++++ .../OAuth2AuthenticationSuccessHandler.java | 52 ++++++++++++ .../aimcup/auth/security/OAuth2UserInfo.java | 16 ++++ .../auth/security/OAuth2UserService.java | 51 ++++++++++++ .../auth/security/OsuOAuth2UserInfo.java | 24 ++++++ .../security/TokenAuthenticationFilter.java | 56 +++++++++++++ .../aimcup/auth/security/TokenProvider.java | 65 +++++++++++++++ .../aimcup/auth/security/UserPrincipal.java | 80 +++++++++++++++++++ .../xyz/aimcup/auth/util/CookieUtils.java | 60 ++++++++++++++ src/main/resources/application-dev.yml | 27 ++++++- .../V1_0__create_user_roles_table.sql | 27 +++++++ .../V1_0__init_migration_to_change.sql | 5 -- .../db/migration/V1_1__insert_roles.sql | 6 ++ 26 files changed, 823 insertions(+), 120 deletions(-) create mode 100644 src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java delete mode 100644 src/main/java/xyz/aimcup/auth/data/entity/Example.java create mode 100644 src/main/java/xyz/aimcup/auth/data/entity/Role.java create mode 100644 src/main/java/xyz/aimcup/auth/data/entity/RoleName.java create mode 100644 src/main/java/xyz/aimcup/auth/data/entity/User.java delete mode 100644 src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java create mode 100644 src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java create mode 100644 src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java create mode 100644 src/main/java/xyz/aimcup/auth/security/CurrentUser.java create mode 100644 src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java create mode 100644 src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java create mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java create mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java create mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java create mode 100644 src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java create mode 100644 src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java create mode 100644 src/main/java/xyz/aimcup/auth/security/TokenProvider.java create mode 100644 src/main/java/xyz/aimcup/auth/security/UserPrincipal.java create mode 100644 src/main/java/xyz/aimcup/auth/util/CookieUtils.java create mode 100644 src/main/resources/db/migration/V1_0__create_user_roles_table.sql delete mode 100644 src/main/resources/db/migration/V1_0__init_migration_to_change.sql create mode 100644 src/main/resources/db/migration/V1_1__insert_roles.sql diff --git a/pom.xml b/pom.xml index 044288e..9b79e36 100644 --- a/pom.xml +++ b/pom.xml @@ -28,12 +28,32 @@ spring-cloud-starter-netflix-eureka-client - org.springframework.security - spring-security-oauth2-resource-server + org.springframework.boot + spring-boot-starter-data-jpa + + org.springframework.boot - spring-boot-starter-data-jpa + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.1 diff --git a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java new file mode 100644 index 0000000..677d5d6 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java @@ -0,0 +1,70 @@ +package xyz.aimcup.auth.configuration; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import xyz.aimcup.auth.security.CustomUserDetailsService; +import xyz.aimcup.auth.security.OAuth2AuthenticationSuccessHandler; +import xyz.aimcup.auth.security.OAuth2UserService; +import xyz.aimcup.auth.security.TokenAuthenticationFilter; +import xyz.aimcup.auth.security.TokenProvider; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfiguration { + private final OAuth2UserService oAuth2UserService; + private final CustomUserDetailsService customUserDetailsService; + private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; + private final TokenAuthenticationFilter tokenAuthenticationFilter; + private final TokenProvider tokenProvider; + + @Bean + AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setUserDetailsService(customUserDetailsService); + return daoAuthenticationProvider; + } + +// @Bean +// public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() { +// return new HttpCookieOAuth2AuthorizationRequestRepository(); +// } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + .cors(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> { + auth.requestMatchers("/secured") + .authenticated() + .anyRequest() + .permitAll(); + }) + .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .oauth2Login(oauth2 -> oauth2 + .userInfoEndpoint(userInfo -> { + // save user to database after successful login + userInfo.userService(oAuth2UserService); + }) + // generate jwt token and redirect user to previous page with token as a parameter in url + .successHandler(oAuth2AuthenticationSuccessHandler) + ) +// .oauth2ResourceServer(oauth2 -> { +// oauth2.jwt(); +// }) + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .build(); + } +} diff --git a/src/main/java/xyz/aimcup/auth/controller/ExampleController.java b/src/main/java/xyz/aimcup/auth/controller/ExampleController.java index b360dec..376c907 100644 --- a/src/main/java/xyz/aimcup/auth/controller/ExampleController.java +++ b/src/main/java/xyz/aimcup/auth/controller/ExampleController.java @@ -1,36 +1,34 @@ -package xyz.aimcup.auth.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestController; -import xyz.aimcup.generated.ExamplesApi; -import xyz.aimcup.generated.model.ExampleDataRequest; -import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.auth.data.entity.Example; -import xyz.aimcup.auth.data.repository.ExampleRepostiory; -import xyz.aimcup.auth.mapper.example.ExampleMapper; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -public class ExampleController implements ExamplesApi { - - private final ExampleRepostiory exampleRepostiory; - private final ExampleMapper exampleMapper; - - @Override - public ResponseEntity addNewExamples(ExampleDataRequest exampleDataRequest) { - exampleRepostiory.save(Example.builder() - .data(exampleDataRequest.getData()) - .build()); - return ResponseEntity.ok("Example added"); - } - - @Override - public ResponseEntity> getExamples() { - List examples = exampleRepostiory.findAll(); - List exampleDataResponses = exampleMapper.examplesToExampleResponses(examples); - return ResponseEntity.ok(exampleDataResponses); - } -} +//package xyz.aimcup.auth.controller; +// +//import lombok.RequiredArgsConstructor; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.RestController; +//import xyz.aimcup.generated.ExamplesApi; +//import xyz.aimcup.generated.model.ExampleDataRequest; +//import xyz.aimcup.generated.model.ExampleDataResponse; +//import xyz.aimcup.auth.mapper.example.ExampleMapper; +// +//import java.util.List; +// +//@RestController +//@RequiredArgsConstructor +//public class ExampleController implements ExamplesApi { +// +// private final ExampleRepostiory exampleRepostiory; +// private final ExampleMapper exampleMapper; +// +// @Override +// public ResponseEntity addNewExamples(ExampleDataRequest exampleDataRequest) { +// exampleRepostiory.save(Example.builder() +// .data(exampleDataRequest.getData()) +// .build()); +// return ResponseEntity.ok("Example added"); +// } +// +// @Override +// public ResponseEntity> getExamples() { +// List examples = exampleRepostiory.findAll(); +// List exampleDataResponses = exampleMapper.examplesToExampleResponses(examples); +// return ResponseEntity.ok(exampleDataResponses); +// } +//} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/Example.java b/src/main/java/xyz/aimcup/auth/data/entity/Example.java deleted file mode 100644 index 6e6f50f..0000000 --- a/src/main/java/xyz/aimcup/auth/data/entity/Example.java +++ /dev/null @@ -1,50 +0,0 @@ -package xyz.aimcup.auth.data.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Objects; -import java.util.UUID; - -@Getter -@Setter -@Entity -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Example { - - @Id - @Column(name = "id") - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(name = "data") - private String data; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Example example = (Example) o; - - if (!Objects.equals(id, example.id)) return false; - return Objects.equals(data, example.data); - } - - @Override - public int hashCode() { - int result = id != null ? id.hashCode() : 0; - result = 31 * result + (data != null ? data.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/Role.java b/src/main/java/xyz/aimcup/auth/data/entity/Role.java new file mode 100644 index 0000000..0f478f8 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/entity/Role.java @@ -0,0 +1,44 @@ +package xyz.aimcup.auth.data.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.NaturalId; +import org.springframework.security.core.GrantedAuthority; + +import java.util.UUID; + + +@Entity +@Getter +@Setter +public class Role implements GrantedAuthority { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private UUID id; + + @Enumerated(EnumType.STRING) + @NaturalId + @Column(length = 60, name = "name") + private RoleName name; + + public Role() { + + } + + public Role(UUID id, RoleName name) { + this.id = id; + this.name = name; + } + + @Override + public String getAuthority() { + return this.name.toString(); + } +} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java b/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java new file mode 100644 index 0000000..532a453 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java @@ -0,0 +1,6 @@ +package xyz.aimcup.auth.data.entity; + +public enum RoleName { + ROLE_USER, + ROLE_ADMIN +} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/User.java b/src/main/java/xyz/aimcup/auth/data/entity/User.java new file mode 100644 index 0000000..0a54eb8 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/entity/User.java @@ -0,0 +1,59 @@ +package xyz.aimcup.auth.data.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.UuidGenerator; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@Table(name="\"user\"") +@NoArgsConstructor +public class User { + @Id + @GeneratedValue + @UuidGenerator + @Setter(AccessLevel.NONE) + @Column(name = "id") + private UUID id; + + @NotBlank + @Column(name = "username") + private String username; + + @NotNull + @Column(name = "osu_id") + private Long osuId; + + @NotNull + @Column(name = "is_restricted") + private Boolean isRestricted; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "user_roles", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles = new HashSet<>(); +} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java b/src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java deleted file mode 100644 index fc24b9e..0000000 --- a/src/main/java/xyz/aimcup/auth/data/repository/ExampleRepostiory.java +++ /dev/null @@ -1,11 +0,0 @@ -package xyz.aimcup.auth.data.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import xyz.aimcup.auth.data.entity.Example; - -import java.util.UUID; - -@Repository -public interface ExampleRepostiory extends JpaRepository { -} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java new file mode 100644 index 0000000..508bf59 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java @@ -0,0 +1,13 @@ +package xyz.aimcup.auth.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import xyz.aimcup.auth.data.entity.Role; +import xyz.aimcup.auth.data.entity.RoleName; + +import java.util.Optional; + +@Repository +public interface RoleRepository extends JpaRepository { + Optional findByName(RoleName roleName); +} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java new file mode 100644 index 0000000..8c7c942 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java @@ -0,0 +1,12 @@ +package xyz.aimcup.auth.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import xyz.aimcup.auth.data.entity.User; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByOsuId(Long osuId); +} diff --git a/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java b/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java index 0bf7bbf..07c9fac 100644 --- a/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java +++ b/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java @@ -1,13 +1,12 @@ -package xyz.aimcup.auth.mapper.example; - -import org.mapstruct.Mapper; -import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.auth.data.entity.Example; - -import java.util.List; - -@Mapper(componentModel = "spring") -public interface ExampleMapper { - ExampleDataResponse exampleToExampleResponse(Example example); - List examplesToExampleResponses(List examples); -} +//package xyz.aimcup.auth.mapper.example; +// +//import org.mapstruct.Mapper; +//import xyz.aimcup.generated.model.ExampleDataResponse; +// +//import java.util.List; +// +//@Mapper(componentModel = "spring") +//public interface ExampleMapper { +// ExampleDataResponse exampleToExampleResponse(Example example); +// List examplesToExampleResponses(List examples); +//} diff --git a/src/main/java/xyz/aimcup/auth/security/CurrentUser.java b/src/main/java/xyz/aimcup/auth/security/CurrentUser.java new file mode 100644 index 0000000..3453e94 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/CurrentUser.java @@ -0,0 +1,17 @@ +package xyz.aimcup.auth.security; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@AuthenticationPrincipal +public @interface CurrentUser { + +} \ No newline at end of file diff --git a/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java b/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java new file mode 100644 index 0000000..eea70d6 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java @@ -0,0 +1,27 @@ +package xyz.aimcup.auth.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AccountStatusUserDetailsChecker; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import xyz.aimcup.auth.data.repository.UserRepository; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userRepository.findByOsuId(Long.parseLong(username)) + .map(user -> { + final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker(); + UserDetails userDetails = UserPrincipal.create(user); + detailsChecker.check(userDetails); + return userDetails; + }) + .orElseThrow(() -> new RuntimeException("User not found")); + } +} diff --git a/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java b/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java new file mode 100644 index 0000000..0475cd3 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java @@ -0,0 +1,44 @@ +//package xyz.aimcup.auth.security; +// +//import com.nimbusds.oauth2.sdk.util.StringUtils; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +//import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +//import org.springframework.stereotype.Component; +//import xyz.aimcup.auth.util.CookieUtils; +// +//@Component +//public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository { +// public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; +// public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri"; +// private static final int cookieExpireSeconds = 180; +// +// @Override +// public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { +// return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) +// .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class)) +// .orElse(null); +// } +// +// @Override +// public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { +// if (authorizationRequest == null) { +// CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); +// CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); +// return; +// } +// +// CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds); +// String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME); +// if (StringUtils.isNotBlank(redirectUriAfterLogin)) { +// CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds); +// } +// } +// +// @Override +// public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { +// return this.loadAuthorizationRequest(request); +// } +// +//} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..ff43588 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,52 @@ +package xyz.aimcup.auth.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final TokenProvider tokenProvider; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + String targetUrl = determineTargetUrl(request, response, authentication); + + if (response.isCommitted()) { + logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); + return; + } + + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } + + @Override + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { +// Optional redirectUri = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) +// .map(Cookie::getValue); + +// if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) { +// throw new BadRequestException("Sorry! We've got an Unauthorized Redirect URI and can't proceed with the authentication"); +// } + +// String targetUrl = redirectUri.orElse(getDefaultTargetUrl()); + String targetUrl = getDefaultTargetUrl(); + + String token = tokenProvider.createToken(authentication); + + return UriComponentsBuilder.fromUriString(targetUrl) + .queryParam("token", token) + .build().toUriString(); + } + +} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java new file mode 100644 index 0000000..37ecf38 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java @@ -0,0 +1,16 @@ +package xyz.aimcup.auth.security; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; + +@AllArgsConstructor +@Getter +public abstract class OAuth2UserInfo { + protected Map attributes; + + public abstract Long getOsuId(); + public abstract String getUsername(); + public abstract Boolean isRestricted(); +} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java new file mode 100644 index 0000000..ac0ed4a --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java @@ -0,0 +1,51 @@ +package xyz.aimcup.auth.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import xyz.aimcup.auth.data.entity.RoleName; +import xyz.aimcup.auth.data.entity.User; +import xyz.aimcup.auth.data.repository.RoleRepository; +import xyz.aimcup.auth.data.repository.UserRepository; + +import java.util.Collections; + +@Service +@RequiredArgsConstructor +public class OAuth2UserService extends DefaultOAuth2UserService { + private final UserRepository userRepository; + private final RoleRepository roleRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + return processOAuth2User(userRequest, oAuth2User); + } + + private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) { + OAuth2UserInfo oAuth2UserInfo = new OsuOAuth2UserInfo(oAuth2User.getAttributes()); + var user = userRepository.findByOsuId(oAuth2UserInfo.getOsuId()) + .map(this::updateUser) + .orElseGet(() -> registerUser(oAuth2UserInfo)); + return UserPrincipal.create(user, oAuth2User.getAttributes()); + } + + private User updateUser(User user) { + return userRepository.save(user); + } + + private User registerUser(OAuth2UserInfo oAuth2UserInfo) { + var userRole = roleRepository.findByName(RoleName.ROLE_USER) + .orElseThrow(() -> new RuntimeException("User Role not set.")); + var user = User.builder() + .osuId(oAuth2UserInfo.getOsuId()) + .username(oAuth2UserInfo.getUsername()) + .isRestricted(oAuth2UserInfo.isRestricted()) + .roles(Collections.singleton(userRole)) + .build(); + return userRepository.save(user); + } +} diff --git a/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java b/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java new file mode 100644 index 0000000..f9cd579 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java @@ -0,0 +1,24 @@ +package xyz.aimcup.auth.security; + +import java.util.Map; + +public class OsuOAuth2UserInfo extends OAuth2UserInfo { + public OsuOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public Long getOsuId() { + return Long.parseLong(attributes.get("id").toString()); + } + + @Override + public String getUsername() { + return attributes.get("username").toString(); + } + + @Override + public Boolean isRestricted() { + return Boolean.parseBoolean(attributes.get("is_restricted").toString()); + } +} diff --git a/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java b/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java new file mode 100644 index 0000000..65f628e --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java @@ -0,0 +1,56 @@ +package xyz.aimcup.auth.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class TokenAuthenticationFilter extends OncePerRequestFilter { + private final TokenProvider tokenProvider; + private final CustomUserDetailsService customUserDetailsService; + + private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + String jwt = getJwtFromRequest(request); + + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { + Long userOsuId = tokenProvider.getUserOsuIdFromToken(jwt); + + UserDetails userDetails = customUserDetailsService.loadUserByUsername(userOsuId.toString()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception ex) { + logger.error("Could not set user authentication in security context", ex); + } + + filterChain.doFilter(request, response); + } + + private String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java new file mode 100644 index 0000000..8a9b200 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java @@ -0,0 +1,65 @@ +package xyz.aimcup.auth.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; + +import java.util.Date; + +@Service +public class TokenProvider { + + private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class); + private static final int TOKEN_EXPIRATION_MSEC = 604800000; // 7 days + private static final String TOKEN_SECRET = "secret"; + + public String createToken(Authentication authentication) { + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + TOKEN_EXPIRATION_MSEC); + + return Jwts.builder() + .setSubject(Long.toString(userPrincipal.getOsuId())) + .setIssuedAt(new Date()) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) + .compact(); + } + + public Long getUserOsuIdFromToken(String token) { + Claims claims = Jwts.parser() + .setSigningKey(TOKEN_SECRET) + .parseClaimsJws(token) + .getBody(); + + return Long.parseLong(claims.getSubject()); + } + + public boolean validateToken(String authToken) { + try { + Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(authToken); + return true; + } catch (SignatureException ex) { + logger.error("Invalid JWT signature"); + } catch (MalformedJwtException ex) { + logger.error("Invalid JWT token"); + } catch (ExpiredJwtException ex) { + logger.error("Expired JWT token"); + } catch (UnsupportedJwtException ex) { + logger.error("Unsupported JWT token"); + } catch (IllegalArgumentException ex) { + logger.error("JWT claims string is empty."); + } + return false; + } + +} diff --git a/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java b/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java new file mode 100644 index 0000000..9c82d02 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java @@ -0,0 +1,80 @@ +package xyz.aimcup.auth.security; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; +import xyz.aimcup.auth.data.entity.User; + +import java.util.Collection; +import java.util.Map; + +@Builder +@Getter +@Setter +public class UserPrincipal implements UserDetails, OAuth2User { + + private String username; + private Long osuId; + + private Boolean active; + private Collection authorities; + private Map attributes; + + public static UserPrincipal create(User user) { + return UserPrincipal.builder() + .username(user.getUsername()) + .osuId(user.getOsuId()) + .active(!user.getIsRestricted()) + .authorities(user.getRoles()) + .build(); + } + + public static UserPrincipal create(User user, Map attributes) { + UserPrincipal userPrincipal = UserPrincipal.create(user); + userPrincipal.setAttributes(attributes); + return userPrincipal; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + throw new RuntimeException("Not implemented"); + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getName() { + return this.username; + } +} diff --git a/src/main/java/xyz/aimcup/auth/util/CookieUtils.java b/src/main/java/xyz/aimcup/auth/util/CookieUtils.java new file mode 100644 index 0000000..dec236e --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/util/CookieUtils.java @@ -0,0 +1,60 @@ +package xyz.aimcup.auth.util; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.util.SerializationUtils; + +import java.util.Base64; +import java.util.Optional; + +public class CookieUtils { + + public static Optional getCookie(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + return Optional.of(cookie); + } + } + } + + return Optional.empty(); + } + + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(maxAge); + response.addCookie(cookie); + } + + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) { + for (Cookie cookie: cookies) { + if (cookie.getName().equals(name)) { + cookie.setValue(""); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + + public static String serialize(Object object) { + return Base64.getUrlEncoder() + .encodeToString(SerializationUtils.serialize(object)); + } + + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast(SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()))); + } + + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 3559954..9101404 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,8 +2,8 @@ eureka: client: enabled: false server: - servlet: - context-path: /example-path +# servlet: +# context-path: /auth port: 8080 spring: application: @@ -27,3 +27,26 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + hbm2ddl: + auto: validate + security: + oauth2: + client: + registration: + osu: + clientId: 9552 + clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt + redirectUri: "http://localhost:8080/login/oauth2/code/osu" + authorizationGrantType: authorization_code + scope: + - identify + - public + provider: + osu: + authorizationUri: https://osu.ppy.sh/oauth/authorize + tokenUri: https://osu.ppy.sh/oauth/token + userInfoUri: https://osu.ppy.sh/api/v2/me + userNameAttribute: id + resourceserver: + jwt: + issuer-uri: http://localhost:8888/issuer \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_0__create_user_roles_table.sql b/src/main/resources/db/migration/V1_0__create_user_roles_table.sql new file mode 100644 index 0000000..8ed83fc --- /dev/null +++ b/src/main/resources/db/migration/V1_0__create_user_roles_table.sql @@ -0,0 +1,27 @@ +CREATE TABLE "user" +( + id UUID PRIMARY KEY, + username VARCHAR NOT NULL, + osu_id BIGINT NOT NULL, + is_restricted BOOLEAN NOT NULL, + CONSTRAINT user_username_unique UNIQUE (username), + CONSTRAINT user_osuId_unique UNIQUE (osu_id) +); + +CREATE INDEX idx_user_username ON "user" (username); +CREATE INDEX idx_user_osuId ON "user" (osu_id); + +CREATE TABLE role +( + id UUID PRIMARY KEY, + name VARCHAR(60) NOT NULL +); + +CREATE INDEX idx_role_name ON role (name); + +CREATE TABLE user_roles +( + user_id UUID NOT NULL, + role_id UUID NOT NULL, + PRIMARY KEY (user_id, role_id) +); diff --git a/src/main/resources/db/migration/V1_0__init_migration_to_change.sql b/src/main/resources/db/migration/V1_0__init_migration_to_change.sql deleted file mode 100644 index 63dcfea..0000000 --- a/src/main/resources/db/migration/V1_0__init_migration_to_change.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE example -( - id UUID PRIMARY KEY, - data text NOT NULL -); \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_1__insert_roles.sql b/src/main/resources/db/migration/V1_1__insert_roles.sql new file mode 100644 index 0000000..d2fbc83 --- /dev/null +++ b/src/main/resources/db/migration/V1_1__insert_roles.sql @@ -0,0 +1,6 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +INSERT INTO role (id, name) +VALUES + (UUID_GENERATE_V4(), 'ROLE_USER'), + (UUID_GENERATE_V4(), 'ROLE_ADMIN'); \ No newline at end of file From 702035152c1119c1253b12ad2e2d29fdce4da02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 6 Aug 2023 19:10:36 +0200 Subject: [PATCH 03/39] [AC-7] Comment out test containers --- .../auth/controller/ExampleControllerIT.java | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java b/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java index 21fc23d..6c793f8 100644 --- a/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java +++ b/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java @@ -1,48 +1,48 @@ -package xyz.aimcup.auth.controller; - - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import xyz.aimcup.generated.model.ExampleDataRequest; -import xyz.aimcup.generated.model.ExampleDataResponse; -import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(MockitoExtension.class) -class ExampleControllerIT extends DatabaseContainerIT { - @Autowired - private ExampleController exampleController; - - @Test - void shouldAddExampleToDatabase() { - // given - ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); - exampleDataRequest.setData("example data"); - - // when - String response = exampleController.addNewExamples(exampleDataRequest).getBody(); - - // then - assertThat(response).isEqualTo("Example added"); - } - - @Test - void shouldFindOnlyOneExampleInDatabase() { - // given - ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); - exampleDataRequest.setData("example data 2"); - - // when - exampleController.addNewExamples(exampleDataRequest).getBody(); - exampleController.addNewExamples(exampleDataRequest).getBody(); - List exampleList = exampleController.getExamples().getBody(); - - // then - assertThat(exampleList).hasSizeGreaterThan(1); - } -} \ No newline at end of file +//package xyz.aimcup.auth.controller; +// +// +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.junit.jupiter.MockitoExtension; +//import org.springframework.beans.factory.annotation.Autowired; +//import xyz.aimcup.generated.model.ExampleDataRequest; +//import xyz.aimcup.generated.model.ExampleDataResponse; +//import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; +// +//import java.util.List; +// +//import static org.assertj.core.api.Assertions.assertThat; +// +//@ExtendWith(MockitoExtension.class) +//class ExampleControllerIT extends DatabaseContainerIT { +// @Autowired +// private ExampleController exampleController; +// +// @Test +// void shouldAddExampleToDatabase() { +// // given +// ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); +// exampleDataRequest.setData("example data"); +// +// // when +// String response = exampleController.addNewExamples(exampleDataRequest).getBody(); +// +// // then +// assertThat(response).isEqualTo("Example added"); +// } +// +// @Test +// void shouldFindOnlyOneExampleInDatabase() { +// // given +// ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); +// exampleDataRequest.setData("example data 2"); +// +// // when +// exampleController.addNewExamples(exampleDataRequest).getBody(); +// exampleController.addNewExamples(exampleDataRequest).getBody(); +// List exampleList = exampleController.getExamples().getBody(); +// +// // then +// assertThat(exampleList).hasSizeGreaterThan(1); +// } +//} \ No newline at end of file From adb479658586f3fc065c1e8acf5370d3ef722807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Mon, 7 Aug 2023 21:54:05 +0200 Subject: [PATCH 04/39] [AC-7] POC: Passing spring beans in endpoint methods using openapi 3 --- pom.xml | 3 ++ .../auth/controller/ExampleController.java | 34 ------------------- .../auth/controller/UserController.java | 19 +++++++++++ .../xyz/aimcup/auth/service/IUserService.java | 8 +++++ .../aimcup/auth/service/impl/UserService.java | 20 +++++++++++ .../models/example/example-data-request.yaml | 5 --- .../role-response.yaml} | 4 +-- .../models/security/user-principal.yaml | 2 ++ .../openapi/models/user/user-response.yaml | 16 +++++++++ src/main/resources/openapi/openapi.yaml | 4 +-- .../openapi/paths/example/example.yaml | 33 ------------------ .../resources/openapi/paths/user/user.yaml | 14 ++++++++ 12 files changed, 86 insertions(+), 76 deletions(-) delete mode 100644 src/main/java/xyz/aimcup/auth/controller/ExampleController.java create mode 100644 src/main/java/xyz/aimcup/auth/controller/UserController.java create mode 100644 src/main/java/xyz/aimcup/auth/service/IUserService.java create mode 100644 src/main/java/xyz/aimcup/auth/service/impl/UserService.java delete mode 100644 src/main/resources/openapi/models/example/example-data-request.yaml rename src/main/resources/openapi/models/{example/example-data-response.yaml => roles/role-response.yaml} (75%) create mode 100644 src/main/resources/openapi/models/security/user-principal.yaml create mode 100644 src/main/resources/openapi/models/user/user-response.yaml delete mode 100644 src/main/resources/openapi/paths/example/example.yaml create mode 100644 src/main/resources/openapi/paths/user/user.yaml diff --git a/pom.xml b/pom.xml index 9b79e36..6a4b4ff 100644 --- a/pom.xml +++ b/pom.xml @@ -205,6 +205,9 @@ false true + + UserPrincipal=xyz.aimcup.auth.security.UserPrincipal + diff --git a/src/main/java/xyz/aimcup/auth/controller/ExampleController.java b/src/main/java/xyz/aimcup/auth/controller/ExampleController.java deleted file mode 100644 index 376c907..0000000 --- a/src/main/java/xyz/aimcup/auth/controller/ExampleController.java +++ /dev/null @@ -1,34 +0,0 @@ -//package xyz.aimcup.auth.controller; -// -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.RestController; -//import xyz.aimcup.generated.ExamplesApi; -//import xyz.aimcup.generated.model.ExampleDataRequest; -//import xyz.aimcup.generated.model.ExampleDataResponse; -//import xyz.aimcup.auth.mapper.example.ExampleMapper; -// -//import java.util.List; -// -//@RestController -//@RequiredArgsConstructor -//public class ExampleController implements ExamplesApi { -// -// private final ExampleRepostiory exampleRepostiory; -// private final ExampleMapper exampleMapper; -// -// @Override -// public ResponseEntity addNewExamples(ExampleDataRequest exampleDataRequest) { -// exampleRepostiory.save(Example.builder() -// .data(exampleDataRequest.getData()) -// .build()); -// return ResponseEntity.ok("Example added"); -// } -// -// @Override -// public ResponseEntity> getExamples() { -// List examples = exampleRepostiory.findAll(); -// List exampleDataResponses = exampleMapper.examplesToExampleResponses(examples); -// return ResponseEntity.ok(exampleDataResponses); -// } -//} diff --git a/src/main/java/xyz/aimcup/auth/controller/UserController.java b/src/main/java/xyz/aimcup/auth/controller/UserController.java new file mode 100644 index 0000000..d47965e --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/controller/UserController.java @@ -0,0 +1,19 @@ +package xyz.aimcup.auth.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import xyz.aimcup.auth.security.UserPrincipal; +import xyz.aimcup.generated.UserApi; +import xyz.aimcup.generated.model.UserResponseDTO; + +@RestController +@RequiredArgsConstructor +public class UserController implements UserApi { + + @Override + public ResponseEntity getUser() { + return null; + } +} diff --git a/src/main/java/xyz/aimcup/auth/service/IUserService.java b/src/main/java/xyz/aimcup/auth/service/IUserService.java new file mode 100644 index 0000000..a21e8e9 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/IUserService.java @@ -0,0 +1,8 @@ +package xyz.aimcup.auth.service; + +import xyz.aimcup.auth.data.entity.User; +import xyz.aimcup.auth.security.UserPrincipal; + +public interface IUserService { + User getUserFromPrincipal(UserPrincipal userPrincipal); +} diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java new file mode 100644 index 0000000..99d07a0 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java @@ -0,0 +1,20 @@ +package xyz.aimcup.auth.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import xyz.aimcup.auth.data.entity.User; +import xyz.aimcup.auth.data.repository.UserRepository; +import xyz.aimcup.auth.security.UserPrincipal; +import xyz.aimcup.auth.service.IUserService; + +@Service +@RequiredArgsConstructor +public class UserService implements IUserService { + private final UserRepository userRepository; + + @Override + public User getUserFromPrincipal(UserPrincipal userPrincipal) { + return userRepository.findByOsuId(userPrincipal.getOsuId()) + .orElseThrow(() -> new RuntimeException("User not found!")); + } +} diff --git a/src/main/resources/openapi/models/example/example-data-request.yaml b/src/main/resources/openapi/models/example/example-data-request.yaml deleted file mode 100644 index e83cd6a..0000000 --- a/src/main/resources/openapi/models/example/example-data-request.yaml +++ /dev/null @@ -1,5 +0,0 @@ -ExampleDataRequest: - type: object - properties: - data: - type: string \ No newline at end of file diff --git a/src/main/resources/openapi/models/example/example-data-response.yaml b/src/main/resources/openapi/models/roles/role-response.yaml similarity index 75% rename from src/main/resources/openapi/models/example/example-data-response.yaml rename to src/main/resources/openapi/models/roles/role-response.yaml index a6122e0..5987693 100644 --- a/src/main/resources/openapi/models/example/example-data-response.yaml +++ b/src/main/resources/openapi/models/roles/role-response.yaml @@ -1,8 +1,8 @@ -ExampleDataResponse: +RoleResponseDTO: type: object properties: id: type: string format: uuid - data: + name: type: string diff --git a/src/main/resources/openapi/models/security/user-principal.yaml b/src/main/resources/openapi/models/security/user-principal.yaml new file mode 100644 index 0000000..e7db3ca --- /dev/null +++ b/src/main/resources/openapi/models/security/user-principal.yaml @@ -0,0 +1,2 @@ +UserPrincipal: + type: object \ No newline at end of file diff --git a/src/main/resources/openapi/models/user/user-response.yaml b/src/main/resources/openapi/models/user/user-response.yaml new file mode 100644 index 0000000..551f0ca --- /dev/null +++ b/src/main/resources/openapi/models/user/user-response.yaml @@ -0,0 +1,16 @@ +UserResponseDTO: + type: object + properties: + id: + type: string + format: uuid + username: + type: string + osuId: + type: integer + isRestricted: + type: boolean + roles: + type: array + items: + $ref: '../roles/role-response.yaml#/RoleResponseDTO' \ No newline at end of file diff --git a/src/main/resources/openapi/openapi.yaml b/src/main/resources/openapi/openapi.yaml index a43db3e..1cfff59 100644 --- a/src/main/resources/openapi/openapi.yaml +++ b/src/main/resources/openapi/openapi.yaml @@ -11,5 +11,5 @@ tags: description: Sample-OpenApi paths: - /examples: - $ref: './paths/example/example.yaml' + /user: + $ref: './paths/user/user.yaml' diff --git a/src/main/resources/openapi/paths/example/example.yaml b/src/main/resources/openapi/paths/example/example.yaml deleted file mode 100644 index 064f438..0000000 --- a/src/main/resources/openapi/paths/example/example.yaml +++ /dev/null @@ -1,33 +0,0 @@ -get: - tags: - - Example - summary: Get example object - operationId: get-examples - responses: - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '../../models/example/example-data-response.yaml#/ExampleDataResponse' - -post: - tags: - - Example - summary: Add example object - operationId: add-new-examples - requestBody: - content: - application/json: - schema: - $ref: '../../models/example/example-data-request.yaml#/ExampleDataRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - type: string - description: Created message \ No newline at end of file diff --git a/src/main/resources/openapi/paths/user/user.yaml b/src/main/resources/openapi/paths/user/user.yaml new file mode 100644 index 0000000..1a07167 --- /dev/null +++ b/src/main/resources/openapi/paths/user/user.yaml @@ -0,0 +1,14 @@ +get: + tags: + - User + summary: Get user by JWT token + operationId: get-user + security: + - bearerAuth: [ ] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../models/user/user-response.yaml#/UserResponseDTO' \ No newline at end of file From faa5cad907c74ee06404c7c6c78370a10ae74630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Fri, 25 Aug 2023 10:38:16 +0200 Subject: [PATCH 05/39] [AC-7] Use Referer header in authentication success handler --- .../OAuth2AuthenticationSuccessHandler.java | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java index ff43588..0ec01ca 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java @@ -3,50 +3,48 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; +import java.io.IOException; import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; -import java.io.IOException; - @Component -@RequiredArgsConstructor -public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - - private final TokenProvider tokenProvider; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - String targetUrl = determineTargetUrl(request, response, authentication); - - if (response.isCommitted()) { - logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); - return; - } - - clearAuthenticationAttributes(request); - getRedirectStrategy().sendRedirect(request, response, targetUrl); - } - - @Override - protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { -// Optional redirectUri = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) -// .map(Cookie::getValue); - -// if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) { -// throw new BadRequestException("Sorry! We've got an Unauthorized Redirect URI and can't proceed with the authentication"); -// } - -// String targetUrl = redirectUri.orElse(getDefaultTargetUrl()); - String targetUrl = getDefaultTargetUrl(); - - String token = tokenProvider.createToken(authentication); - - return UriComponentsBuilder.fromUriString(targetUrl) - .queryParam("token", token) - .build().toUriString(); +public class OAuth2AuthenticationSuccessHandler + extends SavedRequestAwareAuthenticationSuccessHandler { + private final TokenProvider tokenProvider; + + public OAuth2AuthenticationSuccessHandler(TokenProvider tokenProvider) { + super(); + this.tokenProvider = tokenProvider; + setUseReferer(true); + } + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + + String targetUrl = determineTargetUrl(request, response, authentication); + + if (response.isCommitted()) { + logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); + return; } + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } + + @Override + protected String determineTargetUrl( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + String targetUrl = getTargetUrlParameter(); + String token = tokenProvider.createToken(authentication); + + return UriComponentsBuilder.fromUriString(targetUrl) + .queryParam("token", token) + .build() + .toUriString(); + } } From 1eb1f74cb939ef408500897507303e5c96671103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Fri, 25 Aug 2023 14:05:49 +0200 Subject: [PATCH 06/39] [AC-7] Call target url determination on Referer header --- .../auth/security/OAuth2AuthenticationSuccessHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java index 0ec01ca..8b0e467 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java @@ -1,6 +1,5 @@ package xyz.aimcup.auth.security; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -39,7 +38,7 @@ public void onAuthenticationSuccess( @Override protected String determineTargetUrl( HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - String targetUrl = getTargetUrlParameter(); + String targetUrl = super.determineTargetUrl(request,response); String token = tokenProvider.createToken(authentication); return UriComponentsBuilder.fromUriString(targetUrl) From 8c17e27d726793b863596f12bb3a70c97431c994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 29 Aug 2023 22:35:02 +0200 Subject: [PATCH 07/39] [AC-7] Updated oauth2 --- .../configuration/SecurityConfiguration.java | 63 +++++++++++-------- .../auth/controller/UserController.java | 5 ++ ...eOAuth2AuthorizationRequestRepository.java | 44 ------------- .../OAuth2AuthenticationSuccessHandler.java | 60 +++++++++--------- .../auth/security/OAuth2UserService.java | 20 +++--- .../aimcup/auth/security/TokenProvider.java | 9 +++ src/main/resources/application-dev.yml | 9 +-- src/main/resources/application-stg.yml | 20 +++++- 8 files changed, 110 insertions(+), 120 deletions(-) delete mode 100644 src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java diff --git a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java index 677d5d6..453b6da 100644 --- a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java @@ -1,5 +1,6 @@ package xyz.aimcup.auth.configuration; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,21 +12,24 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import xyz.aimcup.auth.security.CustomUserDetailsService; import xyz.aimcup.auth.security.OAuth2AuthenticationSuccessHandler; import xyz.aimcup.auth.security.OAuth2UserService; import xyz.aimcup.auth.security.TokenAuthenticationFilter; -import xyz.aimcup.auth.security.TokenProvider; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { + private final OAuth2UserService oAuth2UserService; private final CustomUserDetailsService customUserDetailsService; private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; private final TokenAuthenticationFilter tokenAuthenticationFilter; - private final TokenProvider tokenProvider; +// private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Bean AuthenticationProvider authenticationProvider() { @@ -34,37 +38,44 @@ AuthenticationProvider authenticationProvider() { return daoAuthenticationProvider; } -// @Bean -// public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() { -// return new HttpCookieOAuth2AuthorizationRequestRepository(); -// } @Bean SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity - .cors(AbstractHttpConfigurer::disable) - .csrf(AbstractHttpConfigurer::disable) - .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> { - auth.requestMatchers("/secured") - .authenticated() - .anyRequest() - .permitAll(); +// .cors(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> { + auth.requestMatchers("/secured").authenticated().anyRequest().permitAll(); + }) + .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> { + // save user to database after successful login + userInfo.userService(oAuth2UserService); }) - .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .oauth2Login(oauth2 -> oauth2 - .userInfoEndpoint(userInfo -> { - // save user to database after successful login - userInfo.userService(oAuth2UserService); - }) - // generate jwt token and redirect user to previous page with token as a parameter in url - .successHandler(oAuth2AuthenticationSuccessHandler) - ) +// .authorizationEndpoint(authorization -> { +// // save authorization request to cookie +// authorization.authorizationRequestRepository( +// httpCookieOAuth2AuthorizationRequestRepository); +// }) + // generate jwt token and redirect user to previous page with token as a parameter in url + .successHandler(oAuth2AuthenticationSuccessHandler)) // .oauth2ResourceServer(oauth2 -> { // oauth2.jwt(); // }) - .httpBasic(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable) - .build(); + .httpBasic(AbstractHttpConfigurer::disable).formLogin(AbstractHttpConfigurer::disable) + .build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("*")); + configuration.setAllowedMethods(List.of("*")); + configuration.setAllowedHeaders(List.of("*")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; } } diff --git a/src/main/java/xyz/aimcup/auth/controller/UserController.java b/src/main/java/xyz/aimcup/auth/controller/UserController.java index d47965e..f662830 100644 --- a/src/main/java/xyz/aimcup/auth/controller/UserController.java +++ b/src/main/java/xyz/aimcup/auth/controller/UserController.java @@ -1,6 +1,8 @@ package xyz.aimcup.auth.controller; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -12,8 +14,11 @@ @RequiredArgsConstructor public class UserController implements UserApi { + private final HttpServletRequest httpRequest; + @Override public ResponseEntity getUser() { + httpRequest.getRequestURI(); return null; } } diff --git a/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java b/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java deleted file mode 100644 index 0475cd3..0000000 --- a/src/main/java/xyz/aimcup/auth/security/HttpCookieOAuth2AuthorizationRequestRepository.java +++ /dev/null @@ -1,44 +0,0 @@ -//package xyz.aimcup.auth.security; -// -//import com.nimbusds.oauth2.sdk.util.StringUtils; -//import jakarta.servlet.http.HttpServletRequest; -//import jakarta.servlet.http.HttpServletResponse; -//import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -//import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -//import org.springframework.stereotype.Component; -//import xyz.aimcup.auth.util.CookieUtils; -// -//@Component -//public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository { -// public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; -// public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri"; -// private static final int cookieExpireSeconds = 180; -// -// @Override -// public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { -// return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) -// .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class)) -// .orElse(null); -// } -// -// @Override -// public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { -// if (authorizationRequest == null) { -// CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); -// CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); -// return; -// } -// -// CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds); -// String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME); -// if (StringUtils.isNotBlank(redirectUriAfterLogin)) { -// CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds); -// } -// } -// -// @Override -// public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { -// return this.loadAuthorizationRequest(request); -// } -// -//} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java index 8b0e467..a240579 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java @@ -3,47 +3,43 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; @Component -public class OAuth2AuthenticationSuccessHandler - extends SavedRequestAwareAuthenticationSuccessHandler { - private final TokenProvider tokenProvider; +public class OAuth2AuthenticationSuccessHandler extends + SavedRequestAwareAuthenticationSuccessHandler { - public OAuth2AuthenticationSuccessHandler(TokenProvider tokenProvider) { - super(); - this.tokenProvider = tokenProvider; - setUseReferer(true); - } + private final TokenProvider tokenProvider; - @Override - public void onAuthenticationSuccess( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException { - - String targetUrl = determineTargetUrl(request, response, authentication); - - if (response.isCommitted()) { - logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); - return; + public OAuth2AuthenticationSuccessHandler(TokenProvider tokenProvider) { + super(); + this.tokenProvider = tokenProvider; + setUseReferer(true); } - clearAuthenticationAttributes(request); - getRedirectStrategy().sendRedirect(request, response, targetUrl); - } - - @Override - protected String determineTargetUrl( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - String targetUrl = super.determineTargetUrl(request,response); - String token = tokenProvider.createToken(authentication); + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + String targetUrl = determineTargetUrl(request, response, authentication); + if (response.isCommitted()) { + logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); + return; + } + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } - return UriComponentsBuilder.fromUriString(targetUrl) - .queryParam("token", token) - .build() - .toUriString(); - } + @Override + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + String token = tokenProvider.createToken(authentication); + response.addCookie(tokenProvider.createCookie(token)); + String targetUrl = super.determineTargetUrl(request, response); + return UriComponentsBuilder.fromUriString(targetUrl).build().toUriString(); + } } diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java index ac0ed4a..bbbab1b 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java @@ -16,6 +16,7 @@ @Service @RequiredArgsConstructor public class OAuth2UserService extends DefaultOAuth2UserService { + private final UserRepository userRepository; private final RoleRepository roleRepository; @@ -25,11 +26,11 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic return processOAuth2User(userRequest, oAuth2User); } - private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) { + private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, + OAuth2User oAuth2User) { OAuth2UserInfo oAuth2UserInfo = new OsuOAuth2UserInfo(oAuth2User.getAttributes()); - var user = userRepository.findByOsuId(oAuth2UserInfo.getOsuId()) - .map(this::updateUser) - .orElseGet(() -> registerUser(oAuth2UserInfo)); + var user = userRepository.findByOsuId(oAuth2UserInfo.getOsuId()).map(this::updateUser) + .orElseGet(() -> registerUser(oAuth2UserInfo)); return UserPrincipal.create(user, oAuth2User.getAttributes()); } @@ -39,13 +40,10 @@ private User updateUser(User user) { private User registerUser(OAuth2UserInfo oAuth2UserInfo) { var userRole = roleRepository.findByName(RoleName.ROLE_USER) - .orElseThrow(() -> new RuntimeException("User Role not set.")); - var user = User.builder() - .osuId(oAuth2UserInfo.getOsuId()) - .username(oAuth2UserInfo.getUsername()) - .isRestricted(oAuth2UserInfo.isRestricted()) - .roles(Collections.singleton(userRole)) - .build(); + .orElseThrow(() -> new RuntimeException("User Role not set.")); + var user = User.builder().osuId(oAuth2UserInfo.getOsuId()) + .username(oAuth2UserInfo.getUsername()).isRestricted(oAuth2UserInfo.isRestricted()) + .roles(Collections.singleton(userRole)).build(); return userRepository.save(user); } } diff --git a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java index 8a9b200..4a317bf 100644 --- a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java +++ b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java @@ -7,6 +7,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.http.Cookie; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; @@ -62,4 +63,12 @@ public boolean validateToken(String authToken) { return false; } + public Cookie createCookie(String token) { + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setMaxAge(TOKEN_EXPIRATION_MSEC); + cookie.setPath("/"); + return cookie; + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 9101404..3f3f15b 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,8 +2,8 @@ eureka: client: enabled: false server: -# servlet: -# context-path: /auth + servlet: + context-path: /user port: 8080 spring: application: @@ -36,7 +36,7 @@ spring: osu: clientId: 9552 clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt - redirectUri: "http://localhost:8080/login/oauth2/code/osu" + redirectUri: "http://localhost:8080/user/login/oauth2/code/osu" authorizationGrantType: authorization_code scope: - identify @@ -47,6 +47,3 @@ spring: tokenUri: https://osu.ppy.sh/oauth/token userInfoUri: https://osu.ppy.sh/api/v2/me userNameAttribute: id - resourceserver: - jwt: - issuer-uri: http://localhost:8888/issuer \ No newline at end of file diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index e8d57a3..27ccbb2 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -4,7 +4,7 @@ eureka: defaultZone: http://172.18.0.1:8762/eureka server: servlet: - context-path: /auth + context-path: /user port: 8502 spring: application: @@ -28,3 +28,21 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + security: + oauth2: + client: + registration: + osu: + clientId: 24331 + clientSecret: lwMOhyx28qc7EayzzkTZ7tKwaUe66SxXTqtE9VkT + redirectUri: "https://api-stg.aimcup.xyz/user/login/oauth2/code/osu" + authorizationGrantType: authorization_code + scope: + - identify + - public + provider: + osu: + authorizationUri: https://osu.ppy.sh/oauth/authorize + tokenUri: https://osu.ppy.sh/oauth/token + userInfoUri: https://osu.ppy.sh/api/v2/me + userNameAttribute: id From e6af9adbaf5473efb3f2477f2ecf3941b85367e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 29 Aug 2023 22:46:59 +0200 Subject: [PATCH 08/39] [AC-7] Changed name from auth to user --- .github/workflows/prd-build-deploy.yml | 4 ++-- .github/workflows/stg-build-deploy.yml | 4 ++-- docker/prd/prd-run-database-job.yml | 4 ++-- docker/stg/stg-run-database-job.yml | 4 ++-- src/main/resources/application-prd.yml | 8 ++++---- src/main/resources/application-stg.yml | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/prd-build-deploy.yml b/.github/workflows/prd-build-deploy.yml index 4f769c9..99edbee 100644 --- a/.github/workflows/prd-build-deploy.yml +++ b/.github/workflows/prd-build-deploy.yml @@ -14,9 +14,9 @@ jobs: run: | docker-compose -f docker/prd/prd-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_PRD_PASSWORD }}" + POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }}" - name: Build and deploy the Docker image run: | docker-compose -f docker/prd/prd-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_PRD_PASSWORD }}" \ No newline at end of file + POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }}" \ No newline at end of file diff --git a/.github/workflows/stg-build-deploy.yml b/.github/workflows/stg-build-deploy.yml index c31092c..59832ce 100644 --- a/.github/workflows/stg-build-deploy.yml +++ b/.github/workflows/stg-build-deploy.yml @@ -17,9 +17,9 @@ jobs: run: | docker-compose -f docker/stg/stg-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_STG_PASSWORD }} + POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }} - name: Build and deploy the Docker image run: | docker-compose -f docker/stg/stg-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.AUTH_DB_STG_PASSWORD }} \ No newline at end of file + POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }} \ No newline at end of file diff --git a/docker/prd/prd-run-database-job.yml b/docker/prd/prd-run-database-job.yml index 457b4ab..9df5ad1 100644 --- a/docker/prd/prd-run-database-job.yml +++ b/docker/prd/prd-run-database-job.yml @@ -5,9 +5,9 @@ services: image: postgres:15.1 restart: always environment: - POSTGRES_USER: auth + POSTGRES_USER: user POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: auth + POSTGRES_DB: user healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index b997353..19612a7 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -5,9 +5,9 @@ services: image: postgres:15.1 restart: always environment: - POSTGRES_USER: auth + POSTGRES_USER: user POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: auth + POSTGRES_DB: user healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index cc8344d..3b60ebd 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -4,15 +4,15 @@ eureka: defaultZone: http://172.18.0.1:8761/eureka server: servlet: - context-path: /auth + context-path: /user port: 8501 spring: application: - name: auth-microservice + name: user-microservice datasource: url: jdbc:postgresql://172.18.0.1:5701/auth - username: auth - password: ${AUTH_DB_PRD_PASSWORD} + username: user + password: ${USER_DB_STG_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index 27ccbb2..ca5677c 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -8,11 +8,11 @@ server: port: 8502 spring: application: - name: auth-microservice + name: user-microservice datasource: - url: jdbc:postgresql://172.18.0.1:5702/auth - username: auth - password: ${AUTH_DB_STG_PASSWORD} + url: jdbc:postgresql://172.18.0.1:5702/user + username: user + password: ${USER_DB_STG_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 From a040c47331d42dc0c87e31131ee04bfc88d0b032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 29 Aug 2023 22:53:02 +0200 Subject: [PATCH 09/39] [AC-7] Bumped version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6a4b4ff..434f564 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ xyz.aimcup auth-microservice - 1.0.0-SNAPSHOT + 1.0.1-SNAPSHOT auth-microservice 17 From 4b1cc51e30414063781d18007adc106c03ff12c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 29 Aug 2023 23:17:44 +0200 Subject: [PATCH 10/39] [AC-7] Added hibernate entity validation --- src/main/resources/application-stg.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index ca5677c..b263742 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -28,6 +28,8 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + hbm2ddl: + auto: validate security: oauth2: client: From c4e3f55a78a935861099a8d235e64e5197f971ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Thu, 31 Aug 2023 16:34:17 +0200 Subject: [PATCH 11/39] [AC-7] Added jacoco --- .github/workflows/stg-check-build.yml | 32 +++++++++++++++++++++- pom.xml | 37 +++++++++++++++++++++++--- src/main/resources/application-prd.yml | 5 ++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/.github/workflows/stg-check-build.yml b/.github/workflows/stg-check-build.yml index 6f9b922..e953d06 100644 --- a/.github/workflows/stg-check-build.yml +++ b/.github/workflows/stg-check-build.yml @@ -5,6 +5,8 @@ on: branches: [ "stage" ] pull_request: branches: [ "stage" ] + workflow_dispatch: + branches: [ "stage" ] jobs: build: @@ -17,4 +19,32 @@ jobs: java-version: '17' distribution: 'temurin' - name: Maven Verify - run: mvn --batch-mode --update-snapshots verify \ No newline at end of file + run: mvn --batch-mode --update-snapshots verify -Pstg + - name: Upload coverage + uses: actions/upload-artifact@v3 + with: + name: test-case reports + path: target/site/jacoco + codacy-coverage-reporter: + runs-on: ubuntu-latest + name: codacy-coverage-reporter + needs: [ build ] + steps: + - uses: actions/checkout@v3 + - name: download reports + uses: actions/download-artifact@v3 + with: + name: test-case reports + path: target/site/jacoco + - name: Display structure of downloaded files + run: ls -R + - name: Run codacy-coverage-reporter + uses: codacy/codacy-coverage-reporter-action@v1 + env: + CODACY_API_TOKEN: ${{ secrets.CODACY_API_TOKEN }} + CODACY_ORGANIZATION_PROVIDER: gh + CODACY_USERNAME: AimCup + CODACY_PROJECT_NAME: ${{ github.event.repository.name }} + with: + api-token: $CODACY_API_TOKEN + coverage-reports: ${{ github.workspace }}/target/site/jacoco/jacoco.xml \ No newline at end of file diff --git a/pom.xml b/pom.xml index 434f564..f700b57 100644 --- a/pom.xml +++ b/pom.xml @@ -76,8 +76,8 @@ org.springdoc - springdoc-openapi-ui - 1.7.0 + springdoc-openapi-starter-webmvc-ui + 2.2.0 @@ -151,6 +151,37 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + + xyz/aimcup/generated/**/* + xyz/aimcup/*/data/**/* + xyz/aimcup/*/mapper/**/* + + + + + before-integration-test-execution + pre-integration-test + + prepare-agent + + + failsafe.jacoco.args + + + + after-integration-test-execution + post-integration-test + + report + + + + org.springframework.boot spring-boot-maven-plugin @@ -167,7 +198,7 @@ org.apache.maven.plugins maven-failsafe-plugin - -Dspring.profiles.active=test + -Dspring.profiles.active=test ${failsafe.jacoco.args} **/*IT.java diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index 3b60ebd..a74eda5 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -28,3 +28,8 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect +springdoc: + api-docs: + enabled: false + swagger-ui: + enabled: false \ No newline at end of file From a76fb3c7a6293b1e046e664002087a9fa5d881ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Thu, 31 Aug 2023 16:40:36 +0200 Subject: [PATCH 12/39] [AC-7] Added oauth2 data to test config --- src/test/resources/application-test.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 4875cf1..ff73e19 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -25,3 +25,21 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + security: + oauth2: + client: + registration: + osu: + clientId: 24331 + clientSecret: lwMOhyx28qc7EayzzkTZ7tKwaUe66SxXTqtE9VkT + redirectUri: "https://api-stg.aimcup.xyz/user/login/oauth2/code/osu" + authorizationGrantType: authorization_code + scope: + - identify + - public + provider: + osu: + authorizationUri: https://osu.ppy.sh/oauth/authorize + tokenUri: https://osu.ppy.sh/oauth/token + userInfoUri: https://osu.ppy.sh/api/v2/me + userNameAttribute: id \ No newline at end of file From 7776fba6bc671300ef16d983efef0f7f6b57af41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Thu, 31 Aug 2023 16:54:43 +0200 Subject: [PATCH 13/39] [AC-7] Changed const variables regarding database --- docker/prd/prd-build-deploy-job.yml | 2 +- docker/stg/stg-build-deploy-job.yml | 2 +- src/main/resources/application-prd.yml | 2 +- src/test/resources/application-test.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/prd/prd-build-deploy-job.yml b/docker/prd/prd-build-deploy-job.yml index 33cd12b..acee466 100644 --- a/docker/prd/prd-build-deploy-job.yml +++ b/docker/prd/prd-build-deploy-job.yml @@ -8,7 +8,7 @@ services: ports: - "8501:8501" environment: - - EXAMPLE_DB_PRD_PASSWORD=${POSTGRES_PASSWORD} + - USER_DB_PRD_PASSWORD=${POSTGRES_PASSWORD} networks: default: diff --git a/docker/stg/stg-build-deploy-job.yml b/docker/stg/stg-build-deploy-job.yml index 8f9a5be..e570743 100644 --- a/docker/stg/stg-build-deploy-job.yml +++ b/docker/stg/stg-build-deploy-job.yml @@ -8,7 +8,7 @@ services: ports: - "8502:8502" environment: - - EXAMPLE_DB_STG_PASSWORD=${POSTGRES_PASSWORD} + - USER_DB_STG_PASSWORD=${POSTGRES_PASSWORD} networks: diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index a74eda5..a2ad138 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -12,7 +12,7 @@ spring: datasource: url: jdbc:postgresql://172.18.0.1:5701/auth username: user - password: ${USER_DB_STG_PASSWORD} + password: ${USER_DB_PRD_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index ff73e19..916a23b 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -3,11 +3,11 @@ eureka: enabled: false server: servlet: - context-path: /example-path + context-path: /user port: 0 spring: application: - name: example-microservice + name: user-microservice datasource: driver-class-name: org.postgresql.Driver hikari: From 617ef931786cc0f38520aa4ab51a30a1329d4d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 24 Sep 2023 13:02:23 +0200 Subject: [PATCH 14/39] [AC-7] asd --- pom.xml | 11 +++- .../java/xyz/aimcup/auth/AuthApplication.java | 2 + .../auth/controller/UserController.java | 18 +++--- .../xyz/aimcup/auth/data/entity/Role.java | 44 -------------- .../xyz/aimcup/auth/data/entity/RoleName.java | 6 -- .../xyz/aimcup/auth/data/entity/User.java | 59 ------------------- .../auth/data/repository/RoleRepository.java | 4 +- .../auth/data/repository/UserRepository.java | 5 +- .../auth/mapper/example/ExampleMapper.java | 12 ---- .../auth/mapper/example/UserMapper.java | 10 ++++ .../auth/security/OAuth2UserService.java | 13 ++-- .../aimcup/auth/security/UserPrincipal.java | 3 +- .../xyz/aimcup/auth/service/IUserService.java | 3 +- .../aimcup/auth/service/impl/UserService.java | 13 +++- src/main/resources/application-dev.yml | 2 +- src/main/resources/openapi/openapi.yaml | 9 +-- .../resources/openapi/paths/user/user.yaml | 8 ++- 17 files changed, 70 insertions(+), 152 deletions(-) delete mode 100644 src/main/java/xyz/aimcup/auth/data/entity/Role.java delete mode 100644 src/main/java/xyz/aimcup/auth/data/entity/RoleName.java delete mode 100644 src/main/java/xyz/aimcup/auth/data/entity/User.java delete mode 100644 src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java create mode 100644 src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java diff --git a/pom.xml b/pom.xml index f700b57..632486b 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ xyz.aimcup - auth-microservice + user-microservice 1.0.1-SNAPSHOT - auth-microservice + user-microservice 17 2022.0.3 @@ -112,6 +112,12 @@ test + + xyz.aimcup + security-config + 0.0.10-TEST + + @@ -230,6 +236,7 @@ xyz.aimcup.generated.model false + true @lombok.NoArgsConstructor @lombok.Builder @lombok.AllArgsConstructor true true diff --git a/src/main/java/xyz/aimcup/auth/AuthApplication.java b/src/main/java/xyz/aimcup/auth/AuthApplication.java index 8f5fa43..170ab24 100644 --- a/src/main/java/xyz/aimcup/auth/AuthApplication.java +++ b/src/main/java/xyz/aimcup/auth/AuthApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient +@EntityScan(basePackages = {"xyz.aimcup.security.domain"}) public class AuthApplication { public static void main(String[] args) { SpringApplication.run(AuthApplication.class, args); diff --git a/src/main/java/xyz/aimcup/auth/controller/UserController.java b/src/main/java/xyz/aimcup/auth/controller/UserController.java index f662830..3c445e7 100644 --- a/src/main/java/xyz/aimcup/auth/controller/UserController.java +++ b/src/main/java/xyz/aimcup/auth/controller/UserController.java @@ -1,24 +1,24 @@ package xyz.aimcup.auth.controller; -import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpRequest; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import xyz.aimcup.auth.security.UserPrincipal; +import xyz.aimcup.auth.mapper.example.UserMapper; +import xyz.aimcup.auth.service.IUserService; import xyz.aimcup.generated.UserApi; import xyz.aimcup.generated.model.UserResponseDTO; +import xyz.aimcup.security.domain.User; @RestController @RequiredArgsConstructor public class UserController implements UserApi { - - private final HttpServletRequest httpRequest; + private final IUserService userService; + private final UserMapper userMapper; @Override - public ResponseEntity getUser() { - httpRequest.getRequestURI(); - return null; + public ResponseEntity me(String authorization) { + User user = userService.getUserFromBearerToken(authorization); + UserResponseDTO userResponseDTO = userMapper.userToUserResponseDto(user); + return ResponseEntity.ok(userResponseDTO); } } diff --git a/src/main/java/xyz/aimcup/auth/data/entity/Role.java b/src/main/java/xyz/aimcup/auth/data/entity/Role.java deleted file mode 100644 index 0f478f8..0000000 --- a/src/main/java/xyz/aimcup/auth/data/entity/Role.java +++ /dev/null @@ -1,44 +0,0 @@ -package xyz.aimcup.auth.data.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.NaturalId; -import org.springframework.security.core.GrantedAuthority; - -import java.util.UUID; - - -@Entity -@Getter -@Setter -public class Role implements GrantedAuthority { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private UUID id; - - @Enumerated(EnumType.STRING) - @NaturalId - @Column(length = 60, name = "name") - private RoleName name; - - public Role() { - - } - - public Role(UUID id, RoleName name) { - this.id = id; - this.name = name; - } - - @Override - public String getAuthority() { - return this.name.toString(); - } -} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java b/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java deleted file mode 100644 index 532a453..0000000 --- a/src/main/java/xyz/aimcup/auth/data/entity/RoleName.java +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.aimcup.auth.data.entity; - -public enum RoleName { - ROLE_USER, - ROLE_ADMIN -} diff --git a/src/main/java/xyz/aimcup/auth/data/entity/User.java b/src/main/java/xyz/aimcup/auth/data/entity/User.java deleted file mode 100644 index 0a54eb8..0000000 --- a/src/main/java/xyz/aimcup/auth/data/entity/User.java +++ /dev/null @@ -1,59 +0,0 @@ -package xyz.aimcup.auth.data.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.hibernate.annotations.UuidGenerator; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -@Entity -@Getter -@Setter -@Builder -@AllArgsConstructor -@Table(name="\"user\"") -@NoArgsConstructor -public class User { - @Id - @GeneratedValue - @UuidGenerator - @Setter(AccessLevel.NONE) - @Column(name = "id") - private UUID id; - - @NotBlank - @Column(name = "username") - private String username; - - @NotNull - @Column(name = "osu_id") - private Long osuId; - - @NotNull - @Column(name = "is_restricted") - private Boolean isRestricted; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "user_roles", - joinColumns = @JoinColumn(name = "user_id"), - inverseJoinColumns = @JoinColumn(name = "role_id")) - private Set roles = new HashSet<>(); -} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java index 508bf59..cb89f17 100644 --- a/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java +++ b/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java @@ -2,10 +2,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import xyz.aimcup.auth.data.entity.Role; -import xyz.aimcup.auth.data.entity.RoleName; import java.util.Optional; +import xyz.aimcup.security.domain.Role; +import xyz.aimcup.security.domain.RoleName; @Repository public interface RoleRepository extends JpaRepository { diff --git a/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java index 8c7c942..3f9d360 100644 --- a/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java +++ b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java @@ -1,12 +1,13 @@ package xyz.aimcup.auth.data.repository; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import xyz.aimcup.auth.data.entity.User; import java.util.Optional; +import xyz.aimcup.security.domain.User; @Repository -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { Optional findByOsuId(Long osuId); } diff --git a/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java b/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java deleted file mode 100644 index 07c9fac..0000000 --- a/src/main/java/xyz/aimcup/auth/mapper/example/ExampleMapper.java +++ /dev/null @@ -1,12 +0,0 @@ -//package xyz.aimcup.auth.mapper.example; -// -//import org.mapstruct.Mapper; -//import xyz.aimcup.generated.model.ExampleDataResponse; -// -//import java.util.List; -// -//@Mapper(componentModel = "spring") -//public interface ExampleMapper { -// ExampleDataResponse exampleToExampleResponse(Example example); -// List examplesToExampleResponses(List examples); -//} diff --git a/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java b/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java new file mode 100644 index 0000000..69c1d3c --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java @@ -0,0 +1,10 @@ +package xyz.aimcup.auth.mapper.example; + +import org.mapstruct.Mapper; +import xyz.aimcup.generated.model.UserResponseDTO; +import xyz.aimcup.security.domain.User; + +@Mapper(componentModel = "spring") +public interface UserMapper { + UserResponseDTO userToUserResponseDto(User user); +} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java index bbbab1b..a0be85f 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java @@ -6,12 +6,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import xyz.aimcup.auth.data.entity.RoleName; -import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.auth.data.repository.RoleRepository; import xyz.aimcup.auth.data.repository.UserRepository; import java.util.Collections; +import xyz.aimcup.security.domain.RoleName; +import xyz.aimcup.security.domain.User; @Service @RequiredArgsConstructor @@ -41,9 +41,12 @@ private User updateUser(User user) { private User registerUser(OAuth2UserInfo oAuth2UserInfo) { var userRole = roleRepository.findByName(RoleName.ROLE_USER) .orElseThrow(() -> new RuntimeException("User Role not set.")); - var user = User.builder().osuId(oAuth2UserInfo.getOsuId()) - .username(oAuth2UserInfo.getUsername()).isRestricted(oAuth2UserInfo.isRestricted()) - .roles(Collections.singleton(userRole)).build(); + var user = User.builder() + .osuId(oAuth2UserInfo.getOsuId()) + .username(oAuth2UserInfo.getUsername()) + .isRestricted(oAuth2UserInfo.isRestricted()) + .roles(Collections.singleton(userRole)) + .build(); return userRepository.save(user); } } diff --git a/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java b/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java index 9c82d02..9e0c96e 100644 --- a/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java +++ b/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java @@ -1,15 +1,16 @@ package xyz.aimcup.auth.security; +import java.math.BigDecimal; import lombok.Builder; import lombok.Getter; import lombok.Setter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; -import xyz.aimcup.auth.data.entity.User; import java.util.Collection; import java.util.Map; +import xyz.aimcup.security.domain.User; @Builder @Getter diff --git a/src/main/java/xyz/aimcup/auth/service/IUserService.java b/src/main/java/xyz/aimcup/auth/service/IUserService.java index a21e8e9..bd4664f 100644 --- a/src/main/java/xyz/aimcup/auth/service/IUserService.java +++ b/src/main/java/xyz/aimcup/auth/service/IUserService.java @@ -1,8 +1,9 @@ package xyz.aimcup.auth.service; -import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.auth.security.UserPrincipal; +import xyz.aimcup.security.domain.User; public interface IUserService { User getUserFromPrincipal(UserPrincipal userPrincipal); + User getUserFromBearerToken(String token); } diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java index 99d07a0..cfbc361 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java @@ -2,19 +2,30 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.auth.data.repository.UserRepository; +import xyz.aimcup.auth.security.TokenProvider; import xyz.aimcup.auth.security.UserPrincipal; import xyz.aimcup.auth.service.IUserService; +import xyz.aimcup.security.domain.User; @Service @RequiredArgsConstructor public class UserService implements IUserService { private final UserRepository userRepository; + private final TokenProvider tokenProvider; @Override public User getUserFromPrincipal(UserPrincipal userPrincipal) { return userRepository.findByOsuId(userPrincipal.getOsuId()) .orElseThrow(() -> new RuntimeException("User not found!")); } + + @Override + public User getUserFromBearerToken(String token) { + if (token == null) { + return null; + } + Long osuId = tokenProvider.getUserOsuIdFromToken(token); + return userRepository.findByOsuId(osuId).orElse(null); + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 3f3f15b..a571fa6 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -7,7 +7,7 @@ server: port: 8080 spring: application: - name: auth-microservice + name: user-microservice datasource: url: jdbc:postgresql://localhost:5432/auth username: testUser diff --git a/src/main/resources/openapi/openapi.yaml b/src/main/resources/openapi/openapi.yaml index 1cfff59..6d543cf 100644 --- a/src/main/resources/openapi/openapi.yaml +++ b/src/main/resources/openapi/openapi.yaml @@ -1,15 +1,12 @@ openapi: 3.0.2 info: - title: Sample OpenAPI Specification for Spring Boot API + title: Auth microservice version: 1.0.0 - description: A sample Spring Boot API using OpenAPI specification + description: OpenAPI specification for Auth microservice contact: name: AimCup email: aimcupdev@gmail.com -tags: - - name: Sample-OpenApi - description: Sample-OpenApi paths: - /user: + /me: $ref: './paths/user/user.yaml' diff --git a/src/main/resources/openapi/paths/user/user.yaml b/src/main/resources/openapi/paths/user/user.yaml index 1a07167..6e5e077 100644 --- a/src/main/resources/openapi/paths/user/user.yaml +++ b/src/main/resources/openapi/paths/user/user.yaml @@ -2,9 +2,15 @@ get: tags: - User summary: Get user by JWT token - operationId: get-user + operationId: me + description: Get user by JWT token security: - bearerAuth: [ ] + parameters: + - in: header + name: Authorization + schema: + type: string responses: '200': description: OK From f803b5fa791cd6703413ad5652dfc6baf33e4c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:37:12 +0200 Subject: [PATCH 15/39] [AC-7] Removed Token Authentication Filter and User Principal (migrated to Security Configuration dependency) --- .../security/TokenAuthenticationFilter.java | 56 ------------- .../aimcup/auth/security/UserPrincipal.java | 81 ------------------- 2 files changed, 137 deletions(-) delete mode 100644 src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/UserPrincipal.java diff --git a/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java b/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java deleted file mode 100644 index 65f628e..0000000 --- a/src/main/java/xyz/aimcup/auth/security/TokenAuthenticationFilter.java +++ /dev/null @@ -1,56 +0,0 @@ -package xyz.aimcup.auth.security; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -@Component -@RequiredArgsConstructor -public class TokenAuthenticationFilter extends OncePerRequestFilter { - private final TokenProvider tokenProvider; - private final CustomUserDetailsService customUserDetailsService; - - private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class); - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - try { - String jwt = getJwtFromRequest(request); - - if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { - Long userOsuId = tokenProvider.getUserOsuIdFromToken(jwt); - - UserDetails userDetails = customUserDetailsService.loadUserByUsername(userOsuId.toString()); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } catch (Exception ex) { - logger.error("Could not set user authentication in security context", ex); - } - - filterChain.doFilter(request, response); - } - - private String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } -} diff --git a/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java b/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java deleted file mode 100644 index 9e0c96e..0000000 --- a/src/main/java/xyz/aimcup/auth/security/UserPrincipal.java +++ /dev/null @@ -1,81 +0,0 @@ -package xyz.aimcup.auth.security; - -import java.math.BigDecimal; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.core.user.OAuth2User; - -import java.util.Collection; -import java.util.Map; -import xyz.aimcup.security.domain.User; - -@Builder -@Getter -@Setter -public class UserPrincipal implements UserDetails, OAuth2User { - - private String username; - private Long osuId; - - private Boolean active; - private Collection authorities; - private Map attributes; - - public static UserPrincipal create(User user) { - return UserPrincipal.builder() - .username(user.getUsername()) - .osuId(user.getOsuId()) - .active(!user.getIsRestricted()) - .authorities(user.getRoles()) - .build(); - } - - public static UserPrincipal create(User user, Map attributes) { - UserPrincipal userPrincipal = UserPrincipal.create(user); - userPrincipal.setAttributes(attributes); - return userPrincipal; - } - - @Override - public Collection getAuthorities() { - return this.authorities; - } - - @Override - public String getPassword() { - throw new RuntimeException("Not implemented"); - } - - @Override - public String getUsername() { - return this.username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public String getName() { - return this.username; - } -} From 1e469afd1b34b60107f9e5473a2961bc226965c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:37:43 +0200 Subject: [PATCH 16/39] [AC-7] Bump Security Configuration version --- pom.xml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 632486b..00ab6bb 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,12 @@ 17 2022.0.3 + + + github-organization + https://maven.pkg.github.com/aimcup + + @@ -115,7 +121,8 @@ xyz.aimcup security-config - 0.0.10-TEST + 0.1.0 + provided @@ -237,7 +244,9 @@ false true - @lombok.NoArgsConstructor @lombok.Builder @lombok.AllArgsConstructor + @lombok.NoArgsConstructor @lombok.Builder + @lombok.AllArgsConstructor + true true false @@ -272,5 +281,4 @@ - From 8ee6a6badb8abb19bca7e7d330f7d1d61633757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:38:18 +0200 Subject: [PATCH 17/39] [AC-7] Added @ComponentScan to look for Security Configuration Beans --- src/main/java/xyz/aimcup/auth/AuthApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/xyz/aimcup/auth/AuthApplication.java b/src/main/java/xyz/aimcup/auth/AuthApplication.java index 170ab24..c7d26d5 100644 --- a/src/main/java/xyz/aimcup/auth/AuthApplication.java +++ b/src/main/java/xyz/aimcup/auth/AuthApplication.java @@ -4,10 +4,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @EnableDiscoveryClient @EntityScan(basePackages = {"xyz.aimcup.security.domain"}) +@ComponentScan(basePackages = {"xyz.aimcup.security", "xyz.aimcup.auth"}) public class AuthApplication { public static void main(String[] args) { SpringApplication.run(AuthApplication.class, args); From 5834d40e3c49f71c0229aaad1d7b4bf356ad69ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:38:50 +0200 Subject: [PATCH 18/39] [AC-7] Changed imports to use classes from Security Configuration --- .../auth/configuration/SecurityConfiguration.java | 7 ++----- .../xyz/aimcup/auth/controller/UserController.java | 12 +++++++++++- .../auth/security/CustomUserDetailsService.java | 1 + .../xyz/aimcup/auth/security/OAuth2UserService.java | 1 + .../java/xyz/aimcup/auth/security/TokenProvider.java | 1 + .../java/xyz/aimcup/auth/service/IUserService.java | 2 +- .../xyz/aimcup/auth/service/impl/UserService.java | 2 +- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java index 453b6da..8852a3f 100644 --- a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java @@ -1,6 +1,5 @@ package xyz.aimcup.auth.configuration; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,14 +10,14 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import xyz.aimcup.auth.security.CustomUserDetailsService; import xyz.aimcup.auth.security.OAuth2AuthenticationSuccessHandler; import xyz.aimcup.auth.security.OAuth2UserService; -import xyz.aimcup.auth.security.TokenAuthenticationFilter; + +import java.util.List; @Configuration @EnableWebSecurity @@ -28,7 +27,6 @@ public class SecurityConfiguration { private final OAuth2UserService oAuth2UserService; private final CustomUserDetailsService customUserDetailsService; private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; - private final TokenAuthenticationFilter tokenAuthenticationFilter; // private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Bean @@ -49,7 +47,6 @@ SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Except .authorizeHttpRequests(auth -> { auth.requestMatchers("/secured").authenticated().anyRequest().permitAll(); }) - .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> { // save user to database after successful login userInfo.userService(oAuth2UserService); diff --git a/src/main/java/xyz/aimcup/auth/controller/UserController.java b/src/main/java/xyz/aimcup/auth/controller/UserController.java index 3c445e7..0fd4a82 100644 --- a/src/main/java/xyz/aimcup/auth/controller/UserController.java +++ b/src/main/java/xyz/aimcup/auth/controller/UserController.java @@ -2,12 +2,15 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import xyz.aimcup.auth.mapper.example.UserMapper; +import xyz.aimcup.auth.mapper.user.UserMapper; import xyz.aimcup.auth.service.IUserService; import xyz.aimcup.generated.UserApi; import xyz.aimcup.generated.model.UserResponseDTO; import xyz.aimcup.security.domain.User; +import xyz.aimcup.security.principal.UserPrincipal; @RestController @RequiredArgsConstructor @@ -21,4 +24,11 @@ public ResponseEntity me(String authorization) { UserResponseDTO userResponseDTO = userMapper.userToUserResponseDto(user); return ResponseEntity.ok(userResponseDTO); } + + + @GetMapping("/test") + public ResponseEntity test() { + var user = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return ResponseEntity.ok(user.getUsername()); + } } diff --git a/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java b/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java index eea70d6..a622de1 100644 --- a/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java +++ b/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java @@ -7,6 +7,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import xyz.aimcup.auth.data.repository.UserRepository; +import xyz.aimcup.security.principal.UserPrincipal; @Service @RequiredArgsConstructor diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java index a0be85f..71db65e 100644 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java +++ b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java @@ -12,6 +12,7 @@ import java.util.Collections; import xyz.aimcup.security.domain.RoleName; import xyz.aimcup.security.domain.User; +import xyz.aimcup.security.principal.UserPrincipal; @Service @RequiredArgsConstructor diff --git a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java index 4a317bf..a626155 100644 --- a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java +++ b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import xyz.aimcup.security.principal.UserPrincipal; import java.util.Date; diff --git a/src/main/java/xyz/aimcup/auth/service/IUserService.java b/src/main/java/xyz/aimcup/auth/service/IUserService.java index bd4664f..310bc80 100644 --- a/src/main/java/xyz/aimcup/auth/service/IUserService.java +++ b/src/main/java/xyz/aimcup/auth/service/IUserService.java @@ -1,7 +1,7 @@ package xyz.aimcup.auth.service; -import xyz.aimcup.auth.security.UserPrincipal; import xyz.aimcup.security.domain.User; +import xyz.aimcup.security.principal.UserPrincipal; public interface IUserService { User getUserFromPrincipal(UserPrincipal userPrincipal); diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java index cfbc361..2356997 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java @@ -4,9 +4,9 @@ import org.springframework.stereotype.Service; import xyz.aimcup.auth.data.repository.UserRepository; import xyz.aimcup.auth.security.TokenProvider; -import xyz.aimcup.auth.security.UserPrincipal; import xyz.aimcup.auth.service.IUserService; import xyz.aimcup.security.domain.User; +import xyz.aimcup.security.principal.UserPrincipal; @Service @RequiredArgsConstructor From fa19b0fba825b29969d4d3394dbbeacbdb5a9d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:39:01 +0200 Subject: [PATCH 19/39] [AC-7] Changed package name --- .../xyz/aimcup/auth/mapper/{example => user}/UserMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/xyz/aimcup/auth/mapper/{example => user}/UserMapper.java (85%) diff --git a/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java similarity index 85% rename from src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java rename to src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java index 69c1d3c..ed9bcc5 100644 --- a/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java +++ b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java @@ -1,4 +1,4 @@ -package xyz.aimcup.auth.mapper.example; +package xyz.aimcup.auth.mapper.user; import org.mapstruct.Mapper; import xyz.aimcup.generated.model.UserResponseDTO; From 40e1226518f4a9f8a0004c607e7645bfb08f1ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Wed, 11 Oct 2023 21:39:01 +0200 Subject: [PATCH 20/39] [AC-7] Bump SC to 0.1.1 --- pom.xml | 2 +- .../xyz/aimcup/auth/mapper/{example => user}/UserMapper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/xyz/aimcup/auth/mapper/{example => user}/UserMapper.java (85%) diff --git a/pom.xml b/pom.xml index 00ab6bb..e4666bc 100644 --- a/pom.xml +++ b/pom.xml @@ -121,7 +121,7 @@ xyz.aimcup security-config - 0.1.0 + 0.1.1 provided diff --git a/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java similarity index 85% rename from src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java rename to src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java index 69c1d3c..ed9bcc5 100644 --- a/src/main/java/xyz/aimcup/auth/mapper/example/UserMapper.java +++ b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java @@ -1,4 +1,4 @@ -package xyz.aimcup.auth.mapper.example; +package xyz.aimcup.auth.mapper.user; import org.mapstruct.Mapper; import xyz.aimcup.generated.model.UserResponseDTO; From db3555bda781451f7f41ae4ef55f62fa2e005c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 10 Dec 2023 18:13:12 +0100 Subject: [PATCH 21/39] [AC-57] Retrieving Osu! users and saving them to Keycloak if necessary --- pom.xml | 41 ++++--- .../java/xyz/aimcup/auth/AuthApplication.java | 5 +- .../RestTemplateConfiguration.java | 14 +++ .../configuration/SecurityConfiguration.java | 73 ++--------- .../auth/controller/UserController.java | 25 ++-- .../xyz/aimcup/auth/data/entity/User.java | 30 +++++ .../auth/data/repository/RoleRepository.java | 13 -- .../auth/data/repository/UserRepository.java | 3 +- .../auth/exception/BadRequestException.java | 28 +++++ .../exception/ExternalApiCallException.java | 14 +++ .../feign/FeignExceptionErrorDecoder.java | 22 ++++ .../aimcup/auth/feign/osu/FeignOsuClient.java | 15 +++ .../osu/FeignOsuClientConfiguration.java | 51 ++++++++ .../xyz/aimcup/auth/feign/osu/OsuClient.java | 8 ++ .../auth/feign/osu/impl/OsuClientImpl.java | 30 +++++ .../feign/osu/model/OsuAccessTokenPojo.java | 17 +++ .../aimcup/auth/feign/osu/model/OsuToken.java | 20 ++++ .../aimcup/auth/feign/osu/model/OsuUser.java | 15 +++ .../auth/feign/osu/model/OsuUserExtended.java | 31 +++++ .../aimcup/auth/mapper/user/UserMapper.java | 10 +- .../xyz/aimcup/auth/security/CurrentUser.java | 17 --- .../security/CustomUserDetailsService.java | 28 ----- .../OAuth2AuthenticationSuccessHandler.java | 45 ------- .../aimcup/auth/security/OAuth2UserInfo.java | 16 --- .../auth/security/OAuth2UserService.java | 53 -------- .../auth/security/OsuOAuth2UserInfo.java | 24 ---- .../aimcup/auth/security/TokenProvider.java | 75 ------------ .../xyz/aimcup/auth/service/IUserService.java | 9 -- .../aimcup/auth/service/KeycloakService.java | 16 +++ .../xyz/aimcup/auth/service/UserService.java | 10 ++ .../service/impl/KeycloakServiceImpl.java | 66 ++++++++++ .../aimcup/auth/service/impl/UserService.java | 31 ----- .../auth/service/impl/UserServiceImpl.java | 44 +++++++ src/main/resources/application-dev.yml | 39 +++--- src/main/resources/application.yml | 7 +- .../V1_0__create_user_roles_table.sql | 17 +-- .../db/migration/V1_1__insert_roles.sql | 6 - src/main/resources/openapi/openapi.yaml | 2 + .../openapi/paths/user/user-by-osu-id.yaml | 19 +++ .../resources/openapi/paths/user/user.yaml | 7 -- .../xyz/aimcup/auth/AuthApplicationIT.java | 15 --- .../auth/controller/ExampleControllerIT.java | 48 -------- .../feign/osu/impl/OsuClientImplTest.java | 99 +++++++++++++++ .../DatabaseContainerIT.java | 31 ----- .../service/impl/UserServiceImplTest.java | 113 ++++++++++++++++++ src/test/resources/application-test.yml | 45 ------- 46 files changed, 758 insertions(+), 589 deletions(-) create mode 100644 src/main/java/xyz/aimcup/auth/configuration/RestTemplateConfiguration.java create mode 100644 src/main/java/xyz/aimcup/auth/data/entity/User.java delete mode 100644 src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java create mode 100644 src/main/java/xyz/aimcup/auth/exception/BadRequestException.java create mode 100644 src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/FeignExceptionErrorDecoder.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClient.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/OsuClient.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImpl.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/model/OsuToken.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUser.java create mode 100644 src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUserExtended.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/CurrentUser.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java delete mode 100644 src/main/java/xyz/aimcup/auth/security/TokenProvider.java delete mode 100644 src/main/java/xyz/aimcup/auth/service/IUserService.java create mode 100644 src/main/java/xyz/aimcup/auth/service/KeycloakService.java create mode 100644 src/main/java/xyz/aimcup/auth/service/UserService.java create mode 100644 src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java delete mode 100644 src/main/java/xyz/aimcup/auth/service/impl/UserService.java create mode 100644 src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java delete mode 100644 src/main/resources/db/migration/V1_1__insert_roles.sql create mode 100644 src/main/resources/openapi/paths/user/user-by-osu-id.yaml delete mode 100644 src/test/java/xyz/aimcup/auth/AuthApplicationIT.java delete mode 100644 src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java create mode 100644 src/test/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImplTest.java delete mode 100644 src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java create mode 100644 src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java delete mode 100644 src/test/resources/application-test.yml diff --git a/pom.xml b/pom.xml index e4666bc..1ec7923 100644 --- a/pom.xml +++ b/pom.xml @@ -16,12 +16,6 @@ 17 2022.0.3 - - - github-organization - https://maven.pkg.github.com/aimcup - - @@ -86,6 +80,29 @@ 2.2.0 + + + org.keycloak + keycloak-admin-client + 22.0.5 + + + org.jboss + jandex + + + + + org.keycloak + keycloak-adapter-core + 22.0.5 + + + org.keycloak + keycloak-common + 22.0.5 + + org.projectlombok @@ -97,7 +114,10 @@ mapstruct 1.5.5.Final - + + org.springframework.cloud + spring-cloud-starter-openfeign + @@ -118,13 +138,6 @@ test - - xyz.aimcup - security-config - 0.1.1 - provided - - diff --git a/src/main/java/xyz/aimcup/auth/AuthApplication.java b/src/main/java/xyz/aimcup/auth/AuthApplication.java index c7d26d5..cea6af9 100644 --- a/src/main/java/xyz/aimcup/auth/AuthApplication.java +++ b/src/main/java/xyz/aimcup/auth/AuthApplication.java @@ -4,12 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient -@EntityScan(basePackages = {"xyz.aimcup.security.domain"}) -@ComponentScan(basePackages = {"xyz.aimcup.security", "xyz.aimcup.auth"}) +@EnableFeignClients public class AuthApplication { public static void main(String[] args) { SpringApplication.run(AuthApplication.class, args); diff --git a/src/main/java/xyz/aimcup/auth/configuration/RestTemplateConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/RestTemplateConfiguration.java new file mode 100644 index 0000000..399c514 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/configuration/RestTemplateConfiguration.java @@ -0,0 +1,14 @@ +package xyz.aimcup.auth.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfiguration { + + @Bean + public RestTemplate httpClient() { + return new RestTemplate(); + } +} diff --git a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java index 8852a3f..f50b2ee 100644 --- a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java @@ -1,78 +1,27 @@ package xyz.aimcup.auth.configuration; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import xyz.aimcup.auth.security.CustomUserDetailsService; -import xyz.aimcup.auth.security.OAuth2AuthenticationSuccessHandler; -import xyz.aimcup.auth.security.OAuth2UserService; - -import java.util.List; @Configuration @EnableWebSecurity -@RequiredArgsConstructor +@EnableMethodSecurity(securedEnabled = true) public class SecurityConfiguration { - private final OAuth2UserService oAuth2UserService; - private final CustomUserDetailsService customUserDetailsService; - private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; -// private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; - - @Bean - AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); - daoAuthenticationProvider.setUserDetailsService(customUserDetailsService); - return daoAuthenticationProvider; - } - - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { - return httpSecurity -// .cors(AbstractHttpConfigurer::disable) - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(AbstractHttpConfigurer::disable) - .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> { - auth.requestMatchers("/secured").authenticated().anyRequest().permitAll(); - }) - .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> { - // save user to database after successful login - userInfo.userService(oAuth2UserService); - }) -// .authorizationEndpoint(authorization -> { -// // save authorization request to cookie -// authorization.authorizationRequestRepository( -// httpCookieOAuth2AuthorizationRequestRepository); -// }) - // generate jwt token and redirect user to previous page with token as a parameter in url - .successHandler(oAuth2AuthenticationSuccessHandler)) -// .oauth2ResourceServer(oauth2 -> { -// oauth2.jwt(); -// }) - .httpBasic(AbstractHttpConfigurer::disable).formLogin(AbstractHttpConfigurer::disable) - .build(); - } - @Bean - CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of("*")); - configuration.setAllowedMethods(List.of("*")); - configuration.setAllowedHeaders(List.of("*")); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; + public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .oauth2Client(Customizer.withDefaults()) + .oauth2Login(Customizer.withDefaults()); + httpSecurity + .sessionManagement(Customizer.withDefaults()); + httpSecurity.csrf(AbstractHttpConfigurer::disable); + return httpSecurity.build(); } } diff --git a/src/main/java/xyz/aimcup/auth/controller/UserController.java b/src/main/java/xyz/aimcup/auth/controller/UserController.java index 0fd4a82..b56035e 100644 --- a/src/main/java/xyz/aimcup/auth/controller/UserController.java +++ b/src/main/java/xyz/aimcup/auth/controller/UserController.java @@ -2,33 +2,32 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.RestController; +import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.auth.mapper.user.UserMapper; -import xyz.aimcup.auth.service.IUserService; +import xyz.aimcup.auth.service.UserService; import xyz.aimcup.generated.UserApi; import xyz.aimcup.generated.model.UserResponseDTO; -import xyz.aimcup.security.domain.User; -import xyz.aimcup.security.principal.UserPrincipal; @RestController @RequiredArgsConstructor public class UserController implements UserApi { - private final IUserService userService; + private final UserService userService; private final UserMapper userMapper; + @Secured("OIDC_USER") @Override - public ResponseEntity me(String authorization) { - User user = userService.getUserFromBearerToken(authorization); + public ResponseEntity getUserByOsuId(String osuId) { + User user = userService.getUserByOsuId(osuId); UserResponseDTO userResponseDTO = userMapper.userToUserResponseDto(user); return ResponseEntity.ok(userResponseDTO); } - - @GetMapping("/test") - public ResponseEntity test() { - var user = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return ResponseEntity.ok(user.getUsername()); + // TODO: AC-61 + @Override + public ResponseEntity me() { + return null; } + } diff --git a/src/main/java/xyz/aimcup/auth/data/entity/User.java b/src/main/java/xyz/aimcup/auth/data/entity/User.java new file mode 100644 index 0000000..01a77e6 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/data/entity/User.java @@ -0,0 +1,30 @@ +package xyz.aimcup.auth.data.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Entity +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "username", unique = true) + private String username; + + @Column(name = "osu_id", unique = true) + private String osuId; + + @Column(name = "is_restricted") + private Boolean isRestricted; +} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java deleted file mode 100644 index cb89f17..0000000 --- a/src/main/java/xyz/aimcup/auth/data/repository/RoleRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package xyz.aimcup.auth.data.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; -import xyz.aimcup.security.domain.Role; -import xyz.aimcup.security.domain.RoleName; - -@Repository -public interface RoleRepository extends JpaRepository { - Optional findByName(RoleName roleName); -} diff --git a/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java index 3f9d360..cdcd0f9 100644 --- a/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java +++ b/src/main/java/xyz/aimcup/auth/data/repository/UserRepository.java @@ -3,11 +3,10 @@ import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import xyz.aimcup.auth.data.entity.User; import java.util.Optional; -import xyz.aimcup.security.domain.User; @Repository public interface UserRepository extends JpaRepository { - Optional findByOsuId(Long osuId); } diff --git a/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java b/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java new file mode 100644 index 0000000..eb994e7 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java @@ -0,0 +1,28 @@ +package xyz.aimcup.auth.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class BadRequestException extends RuntimeException { + private String resourceName; + private String fieldName; + private Object fieldValue; + + public BadRequestException(String resourceName, String fieldName, Object fieldValue) { + super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); + this.resourceName = resourceName; + this.fieldName = fieldName; + this.fieldValue = fieldValue; + } + + public BadRequestException(String message) { + super(message); + } + + public BadRequestException() { + super("Bad request."); + } +} diff --git a/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java b/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java new file mode 100644 index 0000000..3e26139 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java @@ -0,0 +1,14 @@ +package xyz.aimcup.auth.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class ExternalApiCallException extends RuntimeException { + + public ExternalApiCallException() { + super("External API call failed."); + } +} diff --git a/src/main/java/xyz/aimcup/auth/feign/FeignExceptionErrorDecoder.java b/src/main/java/xyz/aimcup/auth/feign/FeignExceptionErrorDecoder.java new file mode 100644 index 0000000..82e2a65 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/FeignExceptionErrorDecoder.java @@ -0,0 +1,22 @@ +package xyz.aimcup.auth.feign; + +import feign.Response; +import feign.codec.ErrorDecoder; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class FeignExceptionErrorDecoder implements ErrorDecoder { + private final ErrorDecoder errorDecoder = new Default(); + + @Override + public Exception decode(String s, Response response) { + switch (response.status()) { + case 400: + return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad request"); + case 404: + return new ResponseStatusException(HttpStatus.NOT_FOUND, "User with provided id not found"); + default: + return errorDecoder.decode(s, response); + } + } +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClient.java b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClient.java new file mode 100644 index 0000000..d5b9b50 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClient.java @@ -0,0 +1,15 @@ +package xyz.aimcup.auth.feign.osu; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; + +@FeignClient(name = "osu-api", + url = "https://osu.ppy.sh/api/v2/", + configuration = FeignOsuClientConfiguration.class) +public interface FeignOsuClient { + + @GetMapping("/users/{id}/osu") + OsuUserExtended getUserById(@PathVariable String id); +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java new file mode 100644 index 0000000..bdd4953 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java @@ -0,0 +1,51 @@ +package xyz.aimcup.auth.feign.osu; + +import feign.RequestInterceptor; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import xyz.aimcup.auth.feign.osu.model.OsuToken; + +@Log4j2 +@RequiredArgsConstructor +class FeignOsuClientConfiguration { + private final RestTemplate restTemplate; + + @Value("${osu.api.clientId}") + private String clientId; + + @Value("${osu.api.clientSecret}") + private String clientSecret; + + @Bean + @Scope("prototype") + public RequestInterceptor osuAccessTokenInterceptor() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Accept", MediaType.APPLICATION_FORM_URLENCODED_VALUE); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("client_id", clientId); + map.add("client_secret", clientSecret); + map.add("grant_type", "client_credentials"); + map.add("scope", "public"); + HttpEntity> entity = new HttpEntity<>(map, headers); + ResponseEntity response = + restTemplate.exchange("https://osu.ppy.sh/oauth/token", + HttpMethod.POST, + entity, + OsuToken.class); + if (response.getBody() == null) { + log.error("Failed to get access token from osu api"); + throw new RuntimeException("Failed to get access token from osu api"); + } + String accessToken = response.getBody().getAccessToken(); + return requestTemplate -> requestTemplate.header("Authorization", "Bearer " + accessToken); + } +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/OsuClient.java b/src/main/java/xyz/aimcup/auth/feign/osu/OsuClient.java new file mode 100644 index 0000000..69dda45 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/OsuClient.java @@ -0,0 +1,8 @@ +package xyz.aimcup.auth.feign.osu; + +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; + +public interface OsuClient { + OsuUserExtended getUserById(String id); + +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImpl.java b/src/main/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImpl.java new file mode 100644 index 0000000..e61ea96 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImpl.java @@ -0,0 +1,30 @@ +package xyz.aimcup.auth.feign.osu.impl; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; +import xyz.aimcup.auth.feign.osu.FeignOsuClient; +import xyz.aimcup.auth.feign.osu.OsuClient; +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; + +@Service +@RequiredArgsConstructor +public class OsuClientImpl implements OsuClient { + private final FeignOsuClient osuClient; + + @Override + public OsuUserExtended getUserById(String id) { + try { + return osuClient.getUserById(id); + } catch (FeignException.NotFound e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User with id " + id + " not found"); + } catch (FeignException.BadRequest e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad request"); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "External API call failed"); + } + } + +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java new file mode 100644 index 0000000..2291d6f --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java @@ -0,0 +1,17 @@ +package xyz.aimcup.auth.feign.osu.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class OsuAccessTokenPojo { + + private String clientId; + + private String clientSecret; + + private String grantType; + + private String scope; +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuToken.java b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuToken.java new file mode 100644 index 0000000..c70824a --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuToken.java @@ -0,0 +1,20 @@ +package xyz.aimcup.auth.feign.osu.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class OsuToken { + + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("expires_in") + private int expiresIn; + + @JsonProperty("refresh_token") + private String refreshToken; +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUser.java b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUser.java new file mode 100644 index 0000000..604eac3 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUser.java @@ -0,0 +1,15 @@ +package xyz.aimcup.auth.feign.osu.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OsuUser { + private Long id; + private String username; + @JsonProperty("avatar_url") + private String avatarUrl; + +} diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUserExtended.java b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUserExtended.java new file mode 100644 index 0000000..31e9eba --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuUserExtended.java @@ -0,0 +1,31 @@ +package xyz.aimcup.auth.feign.osu.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OsuUserExtended extends OsuUser { + + @JsonProperty("cover_url") + private String coverUrl; + + @JsonProperty("discord") + private String discord; + + @JsonProperty("has_supported") + private Boolean hasSupported; + + @JsonProperty("interests") + private String interests; + + @JsonProperty("join_date") + private String joinDate; + + @JsonProperty("location") + private String location; + + @JsonProperty("title") + private String title; +} diff --git a/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java index ed9bcc5..30bfe56 100644 --- a/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java +++ b/src/main/java/xyz/aimcup/auth/mapper/user/UserMapper.java @@ -1,10 +1,18 @@ package xyz.aimcup.auth.mapper.user; +import org.keycloak.representations.idm.UserRepresentation; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.generated.model.UserResponseDTO; -import xyz.aimcup.security.domain.User; @Mapper(componentModel = "spring") public interface UserMapper { UserResponseDTO userToUserResponseDto(User user); + + + @Mapping(target = "osuId", source = "username") + @Mapping(target = "username", source = "username") + @Mapping(target = "isRestricted", source = "enabled") + User userRepresentationToUser(UserRepresentation userRepresentation); } diff --git a/src/main/java/xyz/aimcup/auth/security/CurrentUser.java b/src/main/java/xyz/aimcup/auth/security/CurrentUser.java deleted file mode 100644 index 3453e94..0000000 --- a/src/main/java/xyz/aimcup/auth/security/CurrentUser.java +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.aimcup.auth.security; - -import org.springframework.security.core.annotation.AuthenticationPrincipal; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.PARAMETER, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@AuthenticationPrincipal -public @interface CurrentUser { - -} \ No newline at end of file diff --git a/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java b/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java deleted file mode 100644 index a622de1..0000000 --- a/src/main/java/xyz/aimcup/auth/security/CustomUserDetailsService.java +++ /dev/null @@ -1,28 +0,0 @@ -package xyz.aimcup.auth.security; - -import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.AccountStatusUserDetailsChecker; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import xyz.aimcup.auth.data.repository.UserRepository; -import xyz.aimcup.security.principal.UserPrincipal; - -@Service -@RequiredArgsConstructor -public class CustomUserDetailsService implements UserDetailsService { - private final UserRepository userRepository; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return userRepository.findByOsuId(Long.parseLong(username)) - .map(user -> { - final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker(); - UserDetails userDetails = UserPrincipal.create(user); - detailsChecker.check(userDetails); - return userDetails; - }) - .orElseThrow(() -> new RuntimeException("User not found")); - } -} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java b/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java deleted file mode 100644 index a240579..0000000 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2AuthenticationSuccessHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package xyz.aimcup.auth.security; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponentsBuilder; - -@Component -public class OAuth2AuthenticationSuccessHandler extends - SavedRequestAwareAuthenticationSuccessHandler { - - private final TokenProvider tokenProvider; - - public OAuth2AuthenticationSuccessHandler(TokenProvider tokenProvider) { - super(); - this.tokenProvider = tokenProvider; - setUseReferer(true); - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - String targetUrl = determineTargetUrl(request, response, authentication); - if (response.isCommitted()) { - logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); - return; - } - clearAuthenticationAttributes(request); - getRedirectStrategy().sendRedirect(request, response, targetUrl); - } - - @Override - protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - String token = tokenProvider.createToken(authentication); - response.addCookie(tokenProvider.createCookie(token)); - String targetUrl = super.determineTargetUrl(request, response); - return UriComponentsBuilder.fromUriString(targetUrl).build().toUriString(); - } -} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java deleted file mode 100644 index 37ecf38..0000000 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2UserInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.aimcup.auth.security; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Map; - -@AllArgsConstructor -@Getter -public abstract class OAuth2UserInfo { - protected Map attributes; - - public abstract Long getOsuId(); - public abstract String getUsername(); - public abstract Boolean isRestricted(); -} diff --git a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java b/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java deleted file mode 100644 index 71db65e..0000000 --- a/src/main/java/xyz/aimcup/auth/security/OAuth2UserService.java +++ /dev/null @@ -1,53 +0,0 @@ -package xyz.aimcup.auth.security; - -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; -import xyz.aimcup.auth.data.repository.RoleRepository; -import xyz.aimcup.auth.data.repository.UserRepository; - -import java.util.Collections; -import xyz.aimcup.security.domain.RoleName; -import xyz.aimcup.security.domain.User; -import xyz.aimcup.security.principal.UserPrincipal; - -@Service -@RequiredArgsConstructor -public class OAuth2UserService extends DefaultOAuth2UserService { - - private final UserRepository userRepository; - private final RoleRepository roleRepository; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - return processOAuth2User(userRequest, oAuth2User); - } - - private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, - OAuth2User oAuth2User) { - OAuth2UserInfo oAuth2UserInfo = new OsuOAuth2UserInfo(oAuth2User.getAttributes()); - var user = userRepository.findByOsuId(oAuth2UserInfo.getOsuId()).map(this::updateUser) - .orElseGet(() -> registerUser(oAuth2UserInfo)); - return UserPrincipal.create(user, oAuth2User.getAttributes()); - } - - private User updateUser(User user) { - return userRepository.save(user); - } - - private User registerUser(OAuth2UserInfo oAuth2UserInfo) { - var userRole = roleRepository.findByName(RoleName.ROLE_USER) - .orElseThrow(() -> new RuntimeException("User Role not set.")); - var user = User.builder() - .osuId(oAuth2UserInfo.getOsuId()) - .username(oAuth2UserInfo.getUsername()) - .isRestricted(oAuth2UserInfo.isRestricted()) - .roles(Collections.singleton(userRole)) - .build(); - return userRepository.save(user); - } -} diff --git a/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java b/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java deleted file mode 100644 index f9cd579..0000000 --- a/src/main/java/xyz/aimcup/auth/security/OsuOAuth2UserInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.aimcup.auth.security; - -import java.util.Map; - -public class OsuOAuth2UserInfo extends OAuth2UserInfo { - public OsuOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public Long getOsuId() { - return Long.parseLong(attributes.get("id").toString()); - } - - @Override - public String getUsername() { - return attributes.get("username").toString(); - } - - @Override - public Boolean isRestricted() { - return Boolean.parseBoolean(attributes.get("is_restricted").toString()); - } -} diff --git a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java b/src/main/java/xyz/aimcup/auth/security/TokenProvider.java deleted file mode 100644 index a626155..0000000 --- a/src/main/java/xyz/aimcup/auth/security/TokenProvider.java +++ /dev/null @@ -1,75 +0,0 @@ -package xyz.aimcup.auth.security; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.SignatureException; -import io.jsonwebtoken.UnsupportedJwtException; -import jakarta.servlet.http.Cookie; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import xyz.aimcup.security.principal.UserPrincipal; - -import java.util.Date; - -@Service -public class TokenProvider { - - private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class); - private static final int TOKEN_EXPIRATION_MSEC = 604800000; // 7 days - private static final String TOKEN_SECRET = "secret"; - - public String createToken(Authentication authentication) { - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + TOKEN_EXPIRATION_MSEC); - - return Jwts.builder() - .setSubject(Long.toString(userPrincipal.getOsuId())) - .setIssuedAt(new Date()) - .setExpiration(expiryDate) - .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) - .compact(); - } - - public Long getUserOsuIdFromToken(String token) { - Claims claims = Jwts.parser() - .setSigningKey(TOKEN_SECRET) - .parseClaimsJws(token) - .getBody(); - - return Long.parseLong(claims.getSubject()); - } - - public boolean validateToken(String authToken) { - try { - Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(authToken); - return true; - } catch (SignatureException ex) { - logger.error("Invalid JWT signature"); - } catch (MalformedJwtException ex) { - logger.error("Invalid JWT token"); - } catch (ExpiredJwtException ex) { - logger.error("Expired JWT token"); - } catch (UnsupportedJwtException ex) { - logger.error("Unsupported JWT token"); - } catch (IllegalArgumentException ex) { - logger.error("JWT claims string is empty."); - } - return false; - } - - public Cookie createCookie(String token) { - Cookie cookie = new Cookie("token", token); - cookie.setHttpOnly(true); - cookie.setSecure(true); - cookie.setMaxAge(TOKEN_EXPIRATION_MSEC); - cookie.setPath("/"); - return cookie; - } -} diff --git a/src/main/java/xyz/aimcup/auth/service/IUserService.java b/src/main/java/xyz/aimcup/auth/service/IUserService.java deleted file mode 100644 index 310bc80..0000000 --- a/src/main/java/xyz/aimcup/auth/service/IUserService.java +++ /dev/null @@ -1,9 +0,0 @@ -package xyz.aimcup.auth.service; - -import xyz.aimcup.security.domain.User; -import xyz.aimcup.security.principal.UserPrincipal; - -public interface IUserService { - User getUserFromPrincipal(UserPrincipal userPrincipal); - User getUserFromBearerToken(String token); -} diff --git a/src/main/java/xyz/aimcup/auth/service/KeycloakService.java b/src/main/java/xyz/aimcup/auth/service/KeycloakService.java new file mode 100644 index 0000000..f489e86 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/KeycloakService.java @@ -0,0 +1,16 @@ +package xyz.aimcup.auth.service; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import xyz.aimcup.auth.feign.osu.model.OsuUser; + +import java.util.UUID; + +public interface KeycloakService { + Keycloak getKeycloak(); + + UserRepresentation createUser(OsuUser osuUser); + + UUID createUser(UserRepresentation userRepresentation); + +} diff --git a/src/main/java/xyz/aimcup/auth/service/UserService.java b/src/main/java/xyz/aimcup/auth/service/UserService.java new file mode 100644 index 0000000..420ce68 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/UserService.java @@ -0,0 +1,10 @@ +package xyz.aimcup.auth.service; + + +import xyz.aimcup.auth.data.entity.User; + +public interface UserService { + + User getUserByOsuId(String osuId); + +} diff --git a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java new file mode 100644 index 0000000..223eb36 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java @@ -0,0 +1,66 @@ +package xyz.aimcup.auth.service.impl; + +import jakarta.ws.rs.core.Response; +import lombok.RequiredArgsConstructor; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import xyz.aimcup.auth.feign.osu.model.OsuUser; +import xyz.aimcup.auth.service.KeycloakService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class KeycloakServiceImpl implements KeycloakService { + @Value("${aimcup.keycloak.serverUrl}") + private String serverUrl; + + @Value("${aimcup.keycloak.realm}") + private String realm; + + @Value("${aimcup.keycloak.clientId}") + private String clientId; + + @Value("${aimcup.keycloak.clientSecret}") + private String clientSecret; + + public Keycloak getKeycloak() { + return KeycloakBuilder.builder() + .serverUrl(serverUrl) + .grantType(OAuth2Constants.CLIENT_CREDENTIALS) + .realm(realm) + .clientId(clientId) + .clientSecret(clientSecret) + .build(); + } + + @Override + public UserRepresentation createUser(OsuUser osuUser) { + UserRepresentation userRepresentation = new UserRepresentation(); + userRepresentation.setUsername(osuUser.getId().toString()); + userRepresentation.setEnabled(true); + + // attributes + Map> attributes = new HashMap<>(); + attributes.put("username", List.of(osuUser.getUsername())); + userRepresentation.setAttributes(attributes); + UUID userId = this.createUser(userRepresentation); + userRepresentation.setId(userId.toString()); + return userRepresentation; + } + + @Override + public UUID createUser(UserRepresentation userRepresentation) { + try (Response response = this.getKeycloak().realm(realm).users().create(userRepresentation)) { + return UUID.fromString(response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1")); + } + } + +} diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java b/src/main/java/xyz/aimcup/auth/service/impl/UserService.java deleted file mode 100644 index 2356997..0000000 --- a/src/main/java/xyz/aimcup/auth/service/impl/UserService.java +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.aimcup.auth.service.impl; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import xyz.aimcup.auth.data.repository.UserRepository; -import xyz.aimcup.auth.security.TokenProvider; -import xyz.aimcup.auth.service.IUserService; -import xyz.aimcup.security.domain.User; -import xyz.aimcup.security.principal.UserPrincipal; - -@Service -@RequiredArgsConstructor -public class UserService implements IUserService { - private final UserRepository userRepository; - private final TokenProvider tokenProvider; - - @Override - public User getUserFromPrincipal(UserPrincipal userPrincipal) { - return userRepository.findByOsuId(userPrincipal.getOsuId()) - .orElseThrow(() -> new RuntimeException("User not found!")); - } - - @Override - public User getUserFromBearerToken(String token) { - if (token == null) { - return null; - } - Long osuId = tokenProvider.getUserOsuIdFromToken(token); - return userRepository.findByOsuId(osuId).orElse(null); - } -} diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..fa29fe2 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java @@ -0,0 +1,44 @@ +package xyz.aimcup.auth.service.impl; + +import lombok.RequiredArgsConstructor; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import xyz.aimcup.auth.data.entity.User; +import xyz.aimcup.auth.feign.osu.OsuClient; +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; +import xyz.aimcup.auth.mapper.user.UserMapper; +import xyz.aimcup.auth.service.KeycloakService; +import xyz.aimcup.auth.service.UserService; + + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final KeycloakService keycloakService; + private final OsuClient osuClient; + private final UserMapper userMapper; + + @Value("${aimcup.keycloak.realm}") + private String realm; + + @Override + public User getUserByOsuId(String osuId) { + Keycloak keycloak = keycloakService.getKeycloak(); + return keycloak.realm(realm) + .users() + .searchByUsername(osuId, true) + .stream() + .filter(u -> u.getUsername().equals(osuId)) + .findFirst() + .map(userMapper::userRepresentationToUser) + .orElseGet(() -> createUserByOsuId(osuId)); + } + + private User createUserByOsuId(String osuId) { + OsuUserExtended osuUser = osuClient.getUserById(osuId); + UserRepresentation userRepresentation = keycloakService.createUser(osuUser); + return userMapper.userRepresentationToUser(userRepresentation); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index a571fa6..28943bc 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -32,18 +32,29 @@ spring: security: oauth2: client: - registration: - osu: - clientId: 9552 - clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt - redirectUri: "http://localhost:8080/user/login/oauth2/code/osu" - authorizationGrantType: authorization_code - scope: - - identify - - public provider: - osu: - authorizationUri: https://osu.ppy.sh/oauth/authorize - tokenUri: https://osu.ppy.sh/oauth/token - userInfoUri: https://osu.ppy.sh/api/v2/me - userNameAttribute: id + aimcup: + issuer-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm + registration: + aimcup: + provider: aimcup + client-name: aimcup + client-id: aimcup + client-secret: MUSDaD5wr0p06E8rwlVXkteKagnr29lU + scope: profile,openid,offline_access + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + main: + allow-bean-definition-overriding: true + +aimcup: + keycloak: + serverUrl: https://keycloak-stg.aimcup.xyz + realm: aimcup-realm + clientId: ac-management + clientSecret: msZLKuDHgERVggrmZA33xcm2yoqkcYqI + +logging: + level: + org: + springframework: + security: DEBUG \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9d5fb50..0309ce6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1 +1,6 @@ -spring.profiles.active: @spring.profiles.active@ \ No newline at end of file +spring.profiles.active: @spring.profiles.active@ + +osu: + api: + clientId: ${OSU_API_CLIENT_ID} + clientSecret: ${OSU_API_CLIENT_SECRET} diff --git a/src/main/resources/db/migration/V1_0__create_user_roles_table.sql b/src/main/resources/db/migration/V1_0__create_user_roles_table.sql index 8ed83fc..a55c2e7 100644 --- a/src/main/resources/db/migration/V1_0__create_user_roles_table.sql +++ b/src/main/resources/db/migration/V1_0__create_user_roles_table.sql @@ -2,7 +2,7 @@ CREATE TABLE "user" ( id UUID PRIMARY KEY, username VARCHAR NOT NULL, - osu_id BIGINT NOT NULL, + osu_id VARCHAR NOT NULL, is_restricted BOOLEAN NOT NULL, CONSTRAINT user_username_unique UNIQUE (username), CONSTRAINT user_osuId_unique UNIQUE (osu_id) @@ -10,18 +10,3 @@ CREATE TABLE "user" CREATE INDEX idx_user_username ON "user" (username); CREATE INDEX idx_user_osuId ON "user" (osu_id); - -CREATE TABLE role -( - id UUID PRIMARY KEY, - name VARCHAR(60) NOT NULL -); - -CREATE INDEX idx_role_name ON role (name); - -CREATE TABLE user_roles -( - user_id UUID NOT NULL, - role_id UUID NOT NULL, - PRIMARY KEY (user_id, role_id) -); diff --git a/src/main/resources/db/migration/V1_1__insert_roles.sql b/src/main/resources/db/migration/V1_1__insert_roles.sql deleted file mode 100644 index d2fbc83..0000000 --- a/src/main/resources/db/migration/V1_1__insert_roles.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -INSERT INTO role (id, name) -VALUES - (UUID_GENERATE_V4(), 'ROLE_USER'), - (UUID_GENERATE_V4(), 'ROLE_ADMIN'); \ No newline at end of file diff --git a/src/main/resources/openapi/openapi.yaml b/src/main/resources/openapi/openapi.yaml index 6d543cf..cf04973 100644 --- a/src/main/resources/openapi/openapi.yaml +++ b/src/main/resources/openapi/openapi.yaml @@ -10,3 +10,5 @@ info: paths: /me: $ref: './paths/user/user.yaml' + /{osuId}: + $ref: './paths/user/user-by-osu-id.yaml' diff --git a/src/main/resources/openapi/paths/user/user-by-osu-id.yaml b/src/main/resources/openapi/paths/user/user-by-osu-id.yaml new file mode 100644 index 0000000..75de0a0 --- /dev/null +++ b/src/main/resources/openapi/paths/user/user-by-osu-id.yaml @@ -0,0 +1,19 @@ +get: + tags: + - User + summary: Get user by osu! ID + operationId: get-user-by-osu-id + parameters: + - name: osuId + in: path + required: true + schema: + type: string + description: osu! ID + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../models/user/user-response.yaml#/UserResponseDTO' \ No newline at end of file diff --git a/src/main/resources/openapi/paths/user/user.yaml b/src/main/resources/openapi/paths/user/user.yaml index 6e5e077..49b5d64 100644 --- a/src/main/resources/openapi/paths/user/user.yaml +++ b/src/main/resources/openapi/paths/user/user.yaml @@ -4,13 +4,6 @@ get: summary: Get user by JWT token operationId: me description: Get user by JWT token - security: - - bearerAuth: [ ] - parameters: - - in: header - name: Authorization - schema: - type: string responses: '200': description: OK diff --git a/src/test/java/xyz/aimcup/auth/AuthApplicationIT.java b/src/test/java/xyz/aimcup/auth/AuthApplicationIT.java deleted file mode 100644 index 7bc30aa..0000000 --- a/src/test/java/xyz/aimcup/auth/AuthApplicationIT.java +++ /dev/null @@ -1,15 +0,0 @@ -package xyz.aimcup.auth; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; - -@SpringBootTest -class AuthApplicationIT extends DatabaseContainerIT { - - @Test - @SuppressWarnings("squid:S2699") - void contextLoads() { - } - -} diff --git a/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java b/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java deleted file mode 100644 index 6c793f8..0000000 --- a/src/test/java/xyz/aimcup/auth/controller/ExampleControllerIT.java +++ /dev/null @@ -1,48 +0,0 @@ -//package xyz.aimcup.auth.controller; -// -// -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.junit.jupiter.MockitoExtension; -//import org.springframework.beans.factory.annotation.Autowired; -//import xyz.aimcup.generated.model.ExampleDataRequest; -//import xyz.aimcup.generated.model.ExampleDataResponse; -//import xyz.aimcup.auth.reusablecontainers.DatabaseContainerIT; -// -//import java.util.List; -// -//import static org.assertj.core.api.Assertions.assertThat; -// -//@ExtendWith(MockitoExtension.class) -//class ExampleControllerIT extends DatabaseContainerIT { -// @Autowired -// private ExampleController exampleController; -// -// @Test -// void shouldAddExampleToDatabase() { -// // given -// ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); -// exampleDataRequest.setData("example data"); -// -// // when -// String response = exampleController.addNewExamples(exampleDataRequest).getBody(); -// -// // then -// assertThat(response).isEqualTo("Example added"); -// } -// -// @Test -// void shouldFindOnlyOneExampleInDatabase() { -// // given -// ExampleDataRequest exampleDataRequest = new ExampleDataRequest(); -// exampleDataRequest.setData("example data 2"); -// -// // when -// exampleController.addNewExamples(exampleDataRequest).getBody(); -// exampleController.addNewExamples(exampleDataRequest).getBody(); -// List exampleList = exampleController.getExamples().getBody(); -// -// // then -// assertThat(exampleList).hasSizeGreaterThan(1); -// } -//} \ No newline at end of file diff --git a/src/test/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImplTest.java b/src/test/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImplTest.java new file mode 100644 index 0000000..a67b408 --- /dev/null +++ b/src/test/java/xyz/aimcup/auth/feign/osu/impl/OsuClientImplTest.java @@ -0,0 +1,99 @@ +package xyz.aimcup.auth.feign.osu.impl; + +import feign.FeignException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; +import xyz.aimcup.auth.feign.osu.FeignOsuClient; +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ExtendWith(MockitoExtension.class) +class OsuClientImplTest { + + @Mock + private FeignOsuClient feignOsuClient; + + @InjectMocks + private OsuClientImpl osuClient; + + @Test + @DisplayName("Should return user by id from osu! API") + void shouldReturnUserByIdFromOsuApi() { + // given + final String id = "3660794"; + + OsuUserExtended osuUserExtended = new OsuUserExtended(); + osuUserExtended.setId(Long.valueOf(id)); + + // when + Mockito.when(feignOsuClient.getUserById(id)).thenReturn(osuUserExtended); + OsuUserExtended expectedUser = osuClient.getUserById(id); + + // then + assertThat(expectedUser.getId()).isEqualTo(osuUserExtended.getId()); + } + + @Test + @DisplayName("Should throw exception when user not found") + void shouldThrowExceptionWhenUserNotFound() { + // given + final String id = "3660794"; + + final FeignException.NotFound feignExceptionNotFound = Mockito.mock(FeignException.NotFound.class); + + // when + Mockito.when(feignOsuClient.getUserById(id)).thenThrow(feignExceptionNotFound); + + // then + assertThatThrownBy(() -> osuClient.getUserById(id)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("User with id " + id + " not found") + .hasFieldOrPropertyWithValue("status", HttpStatus.NOT_FOUND); + } + + @Test + @DisplayName("Should throw exception when bad request") + void shouldThrowExceptionWhenBadRequest() { + // given + final String id = "3660794"; + + final FeignException.BadRequest feignExceptionBadRequest = Mockito.mock(FeignException.BadRequest.class); + + // when + Mockito.when(feignOsuClient.getUserById(id)).thenThrow(feignExceptionBadRequest); + + // then + assertThatThrownBy(() -> osuClient.getUserById(id)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("Bad request") + .hasFieldOrPropertyWithValue("status", HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("Should throw exception when external API call failed") + void shouldThrowExceptionWhenExternalApiCallFailed() { + // given + final String id = "3660794"; + + final FeignException feignException = Mockito.mock(FeignException.class); + + // when + Mockito.when(feignOsuClient.getUserById(id)).thenThrow(feignException); + + // then + assertThatThrownBy(() -> osuClient.getUserById(id)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("External API call failed") + .hasFieldOrPropertyWithValue("status", HttpStatus.INTERNAL_SERVER_ERROR); + } + +} \ No newline at end of file diff --git a/src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java b/src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java deleted file mode 100644 index 09e22f6..0000000 --- a/src/test/java/xyz/aimcup/auth/reusablecontainers/DatabaseContainerIT.java +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.aimcup.auth.reusablecontainers; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.DockerImageName; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public abstract class DatabaseContainerIT { - - private static final String POSTGRES_VERSION = "postgres:latest"; - private static final PostgreSQLContainer postgreSQLContainer; - - static { - postgreSQLContainer = - new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION)) - .withDatabaseName("test-db") - .withUsername("test-username") - .withPassword("test-password"); - - postgreSQLContainer.start(); - } - - @DynamicPropertySource - static void datasourceConfig(DynamicPropertyRegistry registry) { - registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl); - registry.add("spring.datasource.password", postgreSQLContainer::getPassword); - registry.add("spring.datasource.username", postgreSQLContainer::getUsername); - } -} diff --git a/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java b/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java new file mode 100644 index 0000000..919da11 --- /dev/null +++ b/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java @@ -0,0 +1,113 @@ +package xyz.aimcup.auth.service.impl; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.UserRepresentation; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import xyz.aimcup.auth.data.entity.User; +import xyz.aimcup.auth.feign.osu.OsuClient; +import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; +import xyz.aimcup.auth.mapper.user.UserMapper; +import xyz.aimcup.auth.service.KeycloakService; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@SpringBootTest(properties = { + "aimcup.keycloak.realm=aimcup_realm" +}, + classes = UserServiceImpl.class) +class UserServiceImplTest { + + @MockBean + private KeycloakService keycloakService; + + @MockBean + private OsuClient osuClient; + + @MockBean + private UserMapper userMapper; + + @Autowired + private UserServiceImpl userService; + + @Test + @DisplayName("Should return User when user exists in Keycloak") + void shouldReturnUserWhenUserExistsInKeycloak() { + // given + final String osuId = "3660794"; + UserRepresentation userRepresentation = new UserRepresentation(); + userRepresentation.setId(osuId); + userRepresentation.setUsername(osuId); + + final Keycloak keycloak = mock(Keycloak.class); + RealmResource resource = mock(RealmResource.class); + UsersResource usersResource = mock(UsersResource.class); + + final User user = User.builder() + .id(UUID.randomUUID()) + .osuId(osuId) + .build(); + + // when + when(keycloakService.getKeycloak()).thenReturn(keycloak); + when(keycloak.realm("aimcup_realm")).thenReturn(resource); + when(resource.users()).thenReturn(usersResource); + when(usersResource.searchByUsername(osuId, true)).thenReturn(List.of(userRepresentation)); + when(userMapper.userRepresentationToUser(userRepresentation)).thenReturn(user); + + User expectedUser = userService.getUserByOsuId(osuId); + + // then + assertThat(expectedUser.getOsuId()).isEqualTo(user.getOsuId()); + } + + @Test + @DisplayName("Should create User when user does not exist in Keycloak") + void shouldCreateUserWhenUserDoesNotExistInKeycloak() { + // given + final String osuId = "3660794"; + UserRepresentation userRepresentation = new UserRepresentation(); + userRepresentation.setId(osuId); + userRepresentation.setUsername(osuId); + + final Keycloak keycloak = mock(Keycloak.class); + RealmResource resource = mock(RealmResource.class); + UsersResource usersResource = mock(UsersResource.class); + OsuUserExtended osuUserExtended = mock(OsuUserExtended.class); + + final User user = User.builder() + .id(UUID.randomUUID()) + .osuId(osuId) + .build(); + + // when + when(keycloakService.getKeycloak()).thenReturn(keycloak); + when(keycloak.realm("aimcup_realm")).thenReturn(resource); + when(resource.users()).thenReturn(usersResource); + when(usersResource.searchByUsername(osuId, true)).thenReturn(List.of()); + + when(osuClient.getUserById(osuId)).thenReturn(osuUserExtended); + when(keycloakService.createUser(osuUserExtended)).thenReturn(userRepresentation); + + when(userMapper.userRepresentationToUser(userRepresentation)).thenReturn(user); + + User expectedUser = userService.getUserByOsuId(osuId); + + // then + assertThat(expectedUser.getOsuId()).isEqualTo(user.getOsuId()); + } + +} \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml deleted file mode 100644 index 916a23b..0000000 --- a/src/test/resources/application-test.yml +++ /dev/null @@ -1,45 +0,0 @@ -eureka: - client: - enabled: false -server: - servlet: - context-path: /user - port: 0 -spring: - application: - name: user-microservice - datasource: - driver-class-name: org.postgresql.Driver - hikari: - connection-timeout: 10000 - maximum-pool-size: 10 - idle-timeout: 5000 - max-lifetime: 1000 - auto-commit: true - flyway: - locations: classpath:db/migration - enabled: true - clean-disabled: false - jpa: - show-sql: true - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - security: - oauth2: - client: - registration: - osu: - clientId: 24331 - clientSecret: lwMOhyx28qc7EayzzkTZ7tKwaUe66SxXTqtE9VkT - redirectUri: "https://api-stg.aimcup.xyz/user/login/oauth2/code/osu" - authorizationGrantType: authorization_code - scope: - - identify - - public - provider: - osu: - authorizationUri: https://osu.ppy.sh/oauth/authorize - tokenUri: https://osu.ppy.sh/oauth/token - userInfoUri: https://osu.ppy.sh/api/v2/me - userNameAttribute: id \ No newline at end of file From 2eb634ee22be8bdd8a73a4dbb393286989531b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:11:03 +0100 Subject: [PATCH 22/39] [AC-57] Renamed Osu! Feign model class --- .../osu/model/{OsuAccessTokenPojo.java => OsuAccessToken.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/xyz/aimcup/auth/feign/osu/model/{OsuAccessTokenPojo.java => OsuAccessToken.java} (87%) diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessToken.java similarity index 87% rename from src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java rename to src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessToken.java index 2291d6f..64707c7 100644 --- a/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessTokenPojo.java +++ b/src/main/java/xyz/aimcup/auth/feign/osu/model/OsuAccessToken.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Data -public class OsuAccessTokenPojo { +public class OsuAccessToken { private String clientId; From c778791b723752010c1547424ce4e7ac7e55219d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:11:31 +0100 Subject: [PATCH 23/39] [AC-57] Removed unnecessary exception implementations --- .../auth/exception/BadRequestException.java | 28 ------------------- .../exception/ExternalApiCallException.java | 14 ---------- 2 files changed, 42 deletions(-) delete mode 100644 src/main/java/xyz/aimcup/auth/exception/BadRequestException.java delete mode 100644 src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java diff --git a/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java b/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java deleted file mode 100644 index eb994e7..0000000 --- a/src/main/java/xyz/aimcup/auth/exception/BadRequestException.java +++ /dev/null @@ -1,28 +0,0 @@ -package xyz.aimcup.auth.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@Getter -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class BadRequestException extends RuntimeException { - private String resourceName; - private String fieldName; - private Object fieldValue; - - public BadRequestException(String resourceName, String fieldName, Object fieldValue) { - super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); - this.resourceName = resourceName; - this.fieldName = fieldName; - this.fieldValue = fieldValue; - } - - public BadRequestException(String message) { - super(message); - } - - public BadRequestException() { - super("Bad request."); - } -} diff --git a/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java b/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java deleted file mode 100644 index 3e26139..0000000 --- a/src/main/java/xyz/aimcup/auth/exception/ExternalApiCallException.java +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.aimcup.auth.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@Getter -@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) -public class ExternalApiCallException extends RuntimeException { - - public ExternalApiCallException() { - super("External API call failed."); - } -} From b22208576c657a6002194d2bc3cc89c2de347cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:12:37 +0100 Subject: [PATCH 24/39] [AC-57] Removed unnecessary comment block --- .../java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java index 223eb36..ea48337 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java @@ -46,8 +46,6 @@ public UserRepresentation createUser(OsuUser osuUser) { UserRepresentation userRepresentation = new UserRepresentation(); userRepresentation.setUsername(osuUser.getId().toString()); userRepresentation.setEnabled(true); - - // attributes Map> attributes = new HashMap<>(); attributes.put("username", List.of(osuUser.getUsername())); userRepresentation.setAttributes(attributes); From 43c463695a2e35c2d12c6835fa6d60ad50d09262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:18:13 +0100 Subject: [PATCH 25/39] [AC-57] Updated Jacoco plugin --- pom.xml | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 1ec7923..f385ffb 100644 --- a/pom.xml +++ b/pom.xml @@ -209,31 +209,46 @@ - org.springframework.boot - spring-boot-maven-plugin + org.jacoco + jacoco-maven-plugin + 0.8.10 - - org.projectlombok - lombok - + xyz/aimcup/generated/**/* + xyz/aimcup/**/configuration/**/* + xyz/aimcup/**/data/**/* + xyz/aimcup/**/mapper/**/* - - - org.apache.maven.plugins - maven-failsafe-plugin - - -Dspring.profiles.active=test ${failsafe.jacoco.args} - - **/*IT.java - - + prepare-jacoco-agent-test + + prepare-agent + + + + generate-code-coverage-report-mvn-test + test - integration-test - verify + report + + + + prepare-jacoco-agent-verify + pre-integration-test + + prepare-agent + + + failsafe.jacoco.args + + + + generate-code-coverage-report-mvn-verify + post-integration-test + + report From d11975ca5134e9933efc3421882bec1681a39c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:18:55 +0100 Subject: [PATCH 26/39] [AC-57] Added OAuth2 Resource Server to security configuration --- .../xyz/aimcup/auth/configuration/SecurityConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java index f50b2ee..3676df7 100644 --- a/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/configuration/SecurityConfiguration.java @@ -22,6 +22,7 @@ public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception httpSecurity .sessionManagement(Customizer.withDefaults()); httpSecurity.csrf(AbstractHttpConfigurer::disable); + httpSecurity.oauth2ResourceServer(auth -> auth.jwt(Customizer.withDefaults())); return httpSecurity.build(); } } From e3a3057436a04a5aa99e4757a6ddc6032690cf5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:32:07 +0100 Subject: [PATCH 27/39] [AC-57] Use @ConfigurationProperties instead of @Value --- .../java/xyz/aimcup/auth/AuthApplication.java | 1 - .../osu/FeignOsuClientConfiguration.java | 15 +++++------ .../auth/properties/AimCupProperties.java | 12 +++++++++ .../auth/properties/KeycloakProperties.java | 15 +++++++++++ .../auth/properties/OsuApiProperties.java | 13 +++++++++ .../aimcup/auth/properties/OsuProperties.java | 12 +++++++++ .../service/impl/KeycloakServiceImpl.java | 27 +++++++------------ .../auth/service/impl/UserServiceImpl.java | 8 +++--- src/main/resources/application-dev.yml | 12 +++++---- src/main/resources/application.yml | 5 ---- 10 files changed, 78 insertions(+), 42 deletions(-) create mode 100644 src/main/java/xyz/aimcup/auth/properties/AimCupProperties.java create mode 100644 src/main/java/xyz/aimcup/auth/properties/KeycloakProperties.java create mode 100644 src/main/java/xyz/aimcup/auth/properties/OsuApiProperties.java create mode 100644 src/main/java/xyz/aimcup/auth/properties/OsuProperties.java diff --git a/src/main/java/xyz/aimcup/auth/AuthApplication.java b/src/main/java/xyz/aimcup/auth/AuthApplication.java index cea6af9..4eeba6e 100644 --- a/src/main/java/xyz/aimcup/auth/AuthApplication.java +++ b/src/main/java/xyz/aimcup/auth/AuthApplication.java @@ -2,7 +2,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java index bdd4953..626d571 100644 --- a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java @@ -3,7 +3,6 @@ import feign.RequestInterceptor; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.http.*; @@ -11,17 +10,14 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import xyz.aimcup.auth.feign.osu.model.OsuToken; +import xyz.aimcup.auth.properties.OsuApiProperties; +import xyz.aimcup.auth.properties.OsuProperties; @Log4j2 @RequiredArgsConstructor class FeignOsuClientConfiguration { private final RestTemplate restTemplate; - - @Value("${osu.api.clientId}") - private String clientId; - - @Value("${osu.api.clientSecret}") - private String clientSecret; + private final OsuProperties osuProperties; @Bean @Scope("prototype") @@ -30,9 +26,10 @@ public RequestInterceptor osuAccessTokenInterceptor() { headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.set("Accept", MediaType.APPLICATION_FORM_URLENCODED_VALUE); + OsuApiProperties osuApiProperties = osuProperties.getApi(); MultiValueMap map = new LinkedMultiValueMap<>(); - map.add("client_id", clientId); - map.add("client_secret", clientSecret); + map.add("client_id", osuApiProperties.getClientId()); + map.add("client_secret", osuApiProperties.getClientSecret()); map.add("grant_type", "client_credentials"); map.add("scope", "public"); HttpEntity> entity = new HttpEntity<>(map, headers); diff --git a/src/main/java/xyz/aimcup/auth/properties/AimCupProperties.java b/src/main/java/xyz/aimcup/auth/properties/AimCupProperties.java new file mode 100644 index 0000000..6dd35a1 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/properties/AimCupProperties.java @@ -0,0 +1,12 @@ +package xyz.aimcup.auth.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "aimcup") +@Data +public class AimCupProperties { + private KeycloakProperties keycloak; +} diff --git a/src/main/java/xyz/aimcup/auth/properties/KeycloakProperties.java b/src/main/java/xyz/aimcup/auth/properties/KeycloakProperties.java new file mode 100644 index 0000000..b6afd06 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/properties/KeycloakProperties.java @@ -0,0 +1,15 @@ +package xyz.aimcup.auth.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "keycloak") +@Data +public class KeycloakProperties { + private String serverUrl; + private String realm; + private String clientId; + private String clientSecret; +} diff --git a/src/main/java/xyz/aimcup/auth/properties/OsuApiProperties.java b/src/main/java/xyz/aimcup/auth/properties/OsuApiProperties.java new file mode 100644 index 0000000..c57aa63 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/properties/OsuApiProperties.java @@ -0,0 +1,13 @@ +package xyz.aimcup.auth.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "api") +@Data +public class OsuApiProperties { + private String clientId; + private String clientSecret; +} diff --git a/src/main/java/xyz/aimcup/auth/properties/OsuProperties.java b/src/main/java/xyz/aimcup/auth/properties/OsuProperties.java new file mode 100644 index 0000000..c62f4c1 --- /dev/null +++ b/src/main/java/xyz/aimcup/auth/properties/OsuProperties.java @@ -0,0 +1,12 @@ +package xyz.aimcup.auth.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "osu") +@Data +public class OsuProperties { + private OsuApiProperties api; +} diff --git a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java index ea48337..8fb16f4 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java @@ -6,9 +6,10 @@ import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; import org.keycloak.representations.idm.UserRepresentation; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import xyz.aimcup.auth.feign.osu.model.OsuUser; +import xyz.aimcup.auth.properties.AimCupProperties; +import xyz.aimcup.auth.properties.KeycloakProperties; import xyz.aimcup.auth.service.KeycloakService; import java.util.HashMap; @@ -19,25 +20,16 @@ @Service @RequiredArgsConstructor public class KeycloakServiceImpl implements KeycloakService { - @Value("${aimcup.keycloak.serverUrl}") - private String serverUrl; - - @Value("${aimcup.keycloak.realm}") - private String realm; - - @Value("${aimcup.keycloak.clientId}") - private String clientId; - - @Value("${aimcup.keycloak.clientSecret}") - private String clientSecret; + private final AimCupProperties aimCupProperties; public Keycloak getKeycloak() { + KeycloakProperties keycloakProperties = aimCupProperties.getKeycloak(); return KeycloakBuilder.builder() - .serverUrl(serverUrl) + .serverUrl(keycloakProperties.getServerUrl()) .grantType(OAuth2Constants.CLIENT_CREDENTIALS) - .realm(realm) - .clientId(clientId) - .clientSecret(clientSecret) + .realm(keycloakProperties.getRealm()) + .clientId(keycloakProperties.getClientId()) + .clientSecret(keycloakProperties.getClientSecret()) .build(); } @@ -56,7 +48,8 @@ public UserRepresentation createUser(OsuUser osuUser) { @Override public UUID createUser(UserRepresentation userRepresentation) { - try (Response response = this.getKeycloak().realm(realm).users().create(userRepresentation)) { + KeycloakProperties keycloakProperties = aimCupProperties.getKeycloak(); + try (Response response = this.getKeycloak().realm(keycloakProperties.getRealm()).users().create(userRepresentation)) { return UUID.fromString(response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1")); } } diff --git a/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java index fa29fe2..13ab6a8 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/UserServiceImpl.java @@ -3,12 +3,12 @@ import lombok.RequiredArgsConstructor; import org.keycloak.admin.client.Keycloak; import org.keycloak.representations.idm.UserRepresentation; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import xyz.aimcup.auth.data.entity.User; import xyz.aimcup.auth.feign.osu.OsuClient; import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; import xyz.aimcup.auth.mapper.user.UserMapper; +import xyz.aimcup.auth.properties.AimCupProperties; import xyz.aimcup.auth.service.KeycloakService; import xyz.aimcup.auth.service.UserService; @@ -19,14 +19,12 @@ public class UserServiceImpl implements UserService { private final KeycloakService keycloakService; private final OsuClient osuClient; private final UserMapper userMapper; - - @Value("${aimcup.keycloak.realm}") - private String realm; + private final AimCupProperties aimCupProperties; @Override public User getUserByOsuId(String osuId) { Keycloak keycloak = keycloakService.getKeycloak(); - return keycloak.realm(realm) + return keycloak.realm(aimCupProperties.getKeycloak().getRealm()) .users() .searchByUsername(osuId, true) .stream() diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 28943bc..3a144e9 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -31,6 +31,9 @@ spring: auto: validate security: oauth2: + resourceserver: + jwt: + jwk-set-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm/protocol/openid-connect/certs client: provider: aimcup: @@ -53,8 +56,7 @@ aimcup: clientId: ac-management clientSecret: msZLKuDHgERVggrmZA33xcm2yoqkcYqI -logging: - level: - org: - springframework: - security: DEBUG \ No newline at end of file +osu: + api: + clientId: 9552 + clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0309ce6..c477f03 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1 @@ spring.profiles.active: @spring.profiles.active@ - -osu: - api: - clientId: ${OSU_API_CLIENT_ID} - clientSecret: ${OSU_API_CLIENT_SECRET} From 55bbdcd7b8300ac0fbc08ca0d9485bca77a4525e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:33:44 +0100 Subject: [PATCH 28/39] [AC-57] Updated application properties --- src/main/resources/application-stg.yml | 41 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index b263742..5533c64 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -32,19 +32,32 @@ spring: auto: validate security: oauth2: + resourceserver: + jwt: + jwk-set-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm/protocol/openid-connect/certs client: - registration: - osu: - clientId: 24331 - clientSecret: lwMOhyx28qc7EayzzkTZ7tKwaUe66SxXTqtE9VkT - redirectUri: "https://api-stg.aimcup.xyz/user/login/oauth2/code/osu" - authorizationGrantType: authorization_code - scope: - - identify - - public provider: - osu: - authorizationUri: https://osu.ppy.sh/oauth/authorize - tokenUri: https://osu.ppy.sh/oauth/token - userInfoUri: https://osu.ppy.sh/api/v2/me - userNameAttribute: id + aimcup: + issuer-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm + registration: + aimcup: + provider: aimcup + client-name: aimcup + client-id: aimcup + client-secret: MUSDaD5wr0p06E8rwlVXkteKagnr29lU + scope: profile,openid,offline_access + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + main: + allow-bean-definition-overriding: true + +aimcup: + keycloak: + serverUrl: https://keycloak-stg.aimcup.xyz + realm: aimcup-realm + clientId: ac-management + clientSecret: msZLKuDHgERVggrmZA33xcm2yoqkcYqI + +osu: + api: + clientId: 9552 + clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt \ No newline at end of file From 081d10f6c146fbee33688a7021e84ca6686f79c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:38:15 +0100 Subject: [PATCH 29/39] [AC-57] Fixed tests --- .../aimcup/auth/service/impl/UserServiceImplTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java b/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java index 919da11..f564d93 100644 --- a/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java +++ b/src/test/java/xyz/aimcup/auth/service/impl/UserServiceImplTest.java @@ -15,6 +15,8 @@ import xyz.aimcup.auth.feign.osu.OsuClient; import xyz.aimcup.auth.feign.osu.model.OsuUserExtended; import xyz.aimcup.auth.mapper.user.UserMapper; +import xyz.aimcup.auth.properties.AimCupProperties; +import xyz.aimcup.auth.properties.KeycloakProperties; import xyz.aimcup.auth.service.KeycloakService; import java.util.List; @@ -40,6 +42,9 @@ class UserServiceImplTest { @MockBean private UserMapper userMapper; + @MockBean + private AimCupProperties aimCupProperties; + @Autowired private UserServiceImpl userService; @@ -55,6 +60,7 @@ void shouldReturnUserWhenUserExistsInKeycloak() { final Keycloak keycloak = mock(Keycloak.class); RealmResource resource = mock(RealmResource.class); UsersResource usersResource = mock(UsersResource.class); + KeycloakProperties keycloakProperties = mock(KeycloakProperties.class); final User user = User.builder() .id(UUID.randomUUID()) @@ -62,6 +68,8 @@ void shouldReturnUserWhenUserExistsInKeycloak() { .build(); // when + when(aimCupProperties.getKeycloak()).thenReturn(keycloakProperties); + when(keycloakProperties.getRealm()).thenReturn("aimcup_realm"); when(keycloakService.getKeycloak()).thenReturn(keycloak); when(keycloak.realm("aimcup_realm")).thenReturn(resource); when(resource.users()).thenReturn(usersResource); @@ -87,6 +95,7 @@ void shouldCreateUserWhenUserDoesNotExistInKeycloak() { RealmResource resource = mock(RealmResource.class); UsersResource usersResource = mock(UsersResource.class); OsuUserExtended osuUserExtended = mock(OsuUserExtended.class); + KeycloakProperties keycloakProperties = mock(KeycloakProperties.class); final User user = User.builder() .id(UUID.randomUUID()) @@ -94,6 +103,8 @@ void shouldCreateUserWhenUserDoesNotExistInKeycloak() { .build(); // when + when(aimCupProperties.getKeycloak()).thenReturn(keycloakProperties); + when(keycloakProperties.getRealm()).thenReturn("aimcup_realm"); when(keycloakService.getKeycloak()).thenReturn(keycloak); when(keycloak.realm("aimcup_realm")).thenReturn(resource); when(resource.users()).thenReturn(usersResource); From 22482ea4484fc741676796f2123bf057a754e188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 17:44:44 +0100 Subject: [PATCH 30/39] [AC-57] Fixed codacy issues --- src/main/java/xyz/aimcup/auth/data/entity/User.java | 6 +++++- .../aimcup/auth/feign/osu/FeignOsuClientConfiguration.java | 6 +++++- .../xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/aimcup/auth/data/entity/User.java b/src/main/java/xyz/aimcup/auth/data/entity/User.java index 01a77e6..390850c 100644 --- a/src/main/java/xyz/aimcup/auth/data/entity/User.java +++ b/src/main/java/xyz/aimcup/auth/data/entity/User.java @@ -1,6 +1,10 @@ package xyz.aimcup.auth.data.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java index 626d571..5036210 100644 --- a/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java +++ b/src/main/java/xyz/aimcup/auth/feign/osu/FeignOsuClientConfiguration.java @@ -5,7 +5,11 @@ import lombok.extern.log4j.Log4j2; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; diff --git a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java index 8fb16f4..4a7f982 100644 --- a/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java +++ b/src/main/java/xyz/aimcup/auth/service/impl/KeycloakServiceImpl.java @@ -49,7 +49,11 @@ public UserRepresentation createUser(OsuUser osuUser) { @Override public UUID createUser(UserRepresentation userRepresentation) { KeycloakProperties keycloakProperties = aimCupProperties.getKeycloak(); - try (Response response = this.getKeycloak().realm(keycloakProperties.getRealm()).users().create(userRepresentation)) { + try (Response response = + this.getKeycloak() + .realm(keycloakProperties.getRealm()) + .users() + .create(userRepresentation)) { return UUID.fromString(response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1")); } } From 853b97058a82fa7f0db6870adb2580e74a6aab65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:20:02 +0100 Subject: [PATCH 31/39] [AC-57] Fixed docker deployment for stg --- docker/stg/stg-build-deploy-job.yml | 2 +- docker/stg/stg-build-deploy.Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/stg/stg-build-deploy-job.yml b/docker/stg/stg-build-deploy-job.yml index e570743..aab09de 100644 --- a/docker/stg/stg-build-deploy-job.yml +++ b/docker/stg/stg-build-deploy-job.yml @@ -1,7 +1,7 @@ version: '3.8' services: - auth-microservice: + tournament-microservice: build: context: ../.. dockerfile: docker/stg/stg-build-deploy.Dockerfile diff --git a/docker/stg/stg-build-deploy.Dockerfile b/docker/stg/stg-build-deploy.Dockerfile index 981f680..7a206df 100644 --- a/docker/stg/stg-build-deploy.Dockerfile +++ b/docker/stg/stg-build-deploy.Dockerfile @@ -1,9 +1,9 @@ -FROM arm32v7/maven:3.9.3-eclipse-temurin-17 AS build +FROM maven:3.9.3-eclipse-temurin-17-focal AS build COPY src /acservice/src COPY pom.xml /acservice RUN mvn -f /acservice/pom.xml clean package -P stg -FROM arm32v7/eclipse-temurin:17 +FROM eclipse-temurin:17.0.8_7-jre-focal COPY --from=build /acservice/target/*.jar app.jar -EXPOSE 8502 +EXPOSE 8201 ENTRYPOINT ["java","-Dspring.profiles.active=stg","-jar","/app.jar"] From b704dec7f2782663e20e73eae30034214c1b016f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:31:04 +0100 Subject: [PATCH 32/39] [AC-57] Changed service name --- docker/stg/stg-build-deploy-job.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/stg/stg-build-deploy-job.yml b/docker/stg/stg-build-deploy-job.yml index aab09de..856d0c4 100644 --- a/docker/stg/stg-build-deploy-job.yml +++ b/docker/stg/stg-build-deploy-job.yml @@ -1,7 +1,7 @@ version: '3.8' services: - tournament-microservice: + user-microservice: build: context: ../.. dockerfile: docker/stg/stg-build-deploy.Dockerfile @@ -10,7 +10,6 @@ services: environment: - USER_DB_STG_PASSWORD=${POSTGRES_PASSWORD} - networks: default: external: true From 271a8b91cf0a90d67d8a5257f6a7f1f5ccef7490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:32:52 +0100 Subject: [PATCH 33/39] [AC-57] Changed service name, exposed port, db service name --- docker/stg/stg-build-deploy.Dockerfile | 2 +- docker/stg/stg-run-database-job.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/stg/stg-build-deploy.Dockerfile b/docker/stg/stg-build-deploy.Dockerfile index 7a206df..d94c606 100644 --- a/docker/stg/stg-build-deploy.Dockerfile +++ b/docker/stg/stg-build-deploy.Dockerfile @@ -5,5 +5,5 @@ RUN mvn -f /acservice/pom.xml clean package -P stg FROM eclipse-temurin:17.0.8_7-jre-focal COPY --from=build /acservice/target/*.jar app.jar -EXPOSE 8201 +EXPOSE 8502 ENTRYPOINT ["java","-Dspring.profiles.active=stg","-jar","/app.jar"] diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index 19612a7..874145f 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -1,7 +1,7 @@ version: '3.8' services: - db-auth-microservice: + stg-db-user-microservice: image: postgres:15.1 restart: always environment: From 3947475261c9d932d1822544374a57e82ae467c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:36:52 +0100 Subject: [PATCH 34/39] [AC-57] Changed db service name --- docker/stg/stg-run-database-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index 874145f..58e23b1 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -1,7 +1,7 @@ version: '3.8' services: - stg-db-user-microservice: + db-user-microservice: image: postgres:15.1 restart: always environment: From 4db56a2377d767f3fcd0adf93aff2d0494ebdc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:44:52 +0100 Subject: [PATCH 35/39] [AC-57] pom fixes --- pom.xml | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index f385ffb..6885f20 100644 --- a/pom.xml +++ b/pom.xml @@ -178,35 +178,16 @@ - org.jacoco - jacoco-maven-plugin - 0.8.10 + org.springframework.boot + spring-boot-maven-plugin - xyz/aimcup/generated/**/* - xyz/aimcup/*/data/**/* - xyz/aimcup/*/mapper/**/* + + org.projectlombok + lombok + - - - before-integration-test-execution - pre-integration-test - - prepare-agent - - - failsafe.jacoco.args - - - - after-integration-test-execution - post-integration-test - - report - - - org.jacoco @@ -280,9 +261,6 @@ false true - - UserPrincipal=xyz.aimcup.auth.security.UserPrincipal - From a8de48c9ad2f61cdff2ad9e098fe4649b6cef0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 19 Dec 2023 20:52:16 +0100 Subject: [PATCH 36/39] [AC-57] Forward headers strategy --- src/main/resources/application-stg.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index 5533c64..7288d11 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -6,6 +6,7 @@ server: servlet: context-path: /user port: 8502 + forward-headers-strategy: native spring: application: name: user-microservice From f773ba70c5dbef068806853bf498fd5a33cff6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20S=C5=82owiak?= Date: Fri, 22 Dec 2023 09:39:21 +0100 Subject: [PATCH 37/39] [AC-62] Migrate to env variables --- .github/workflows/prd-build-deploy.yml | 19 ++++++++++++-- .github/workflows/stg-build-deploy.yml | 18 ++++++++++++-- docker/dev/dev-run-database-job.yml | 17 +++++++++++++ docker/prd/prd-build-deploy-job.yml | 18 ++++++++++++-- docker/prd/prd-run-database-job.yml | 6 ++--- docker/stg/stg-build-deploy-job.yml | 16 +++++++++++- docker/stg/stg-run-database-job.yml | 4 +-- src/main/resources/application-dev.yml | 2 +- src/main/resources/application-prd.yml | 25 ++++++++++++++++--- src/main/resources/application-stg.yml | 34 +++++++++++++------------- 10 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 docker/dev/dev-run-database-job.yml diff --git a/.github/workflows/prd-build-deploy.yml b/.github/workflows/prd-build-deploy.yml index 99edbee..129e023 100644 --- a/.github/workflows/prd-build-deploy.yml +++ b/.github/workflows/prd-build-deploy.yml @@ -14,9 +14,24 @@ jobs: run: | docker-compose -f docker/prd/prd-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }}" + DB_PRD_USER: ${{ secrets.DB_PRD_USER }} + DB_PRD_PASSWORD: ${{ secrets.DB_PRD_PASSWORD }} - name: Build and deploy the Docker image run: | docker-compose -f docker/prd/prd-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }}" \ No newline at end of file + DB_PRD_ADDRESS: ${{ secrets.DB_PRD_ADDRESS }} + DB_PRD_USER: ${{ secrets.DB_PRD_USER }} + DB_PRD_PASSWORD: ${{ secrets.DB_PRD_PASSWORD }} + EUREKA_PRD: ${{ secrets.EUREKA_PRD }} + PRD_KEYCLOAK_ISSUER_ID: ${{ secrets.PRD_KEYCLOAK_ISSUER_ID }} + PRD_KEYCLOAK_CLIENT_NAME: ${{ secrets.PRD_KEYCLOAK_CLIENT_NAME }} + PRD_KEYCLOAK_CLIENT_ID: ${{ secrets.PRD_KEYCLOAK_CLIENT_ID }} + PRD_KEYCLOAK_CLIENT_SECRET: ${{ secrets.PRD_KEYCLOAK_CLIENT_SECRET }} + PRD_KEYCLOAK_JWK_SET_URI: ${{ secrets.PRD_KEYCLOAK_JWK_SET_URI }} + PRD_KEYCLOAK_URI: ${{ secrets.PRD_KEYCLOAK_URI }} + PRD_KEYCLOAK_REALM_NAME: ${{ secrets.PRD_KEYCLOAK_REALM_NAME }} + PRD_MANAGEMENT_KEYCLOAK_CLIENT_ID: ${{ secrets.PRD_MANAGEMENT_KEYCLOAK_CLIENT_ID }} + PRD_MANAGEMENT_KEYCLOAK_CLIENT_SECRET: ${{ secrets.PRD_MANAGEMENT_KEYCLOAK_CLIENT_SECRET }} + PRD_OSU_API_CLIENT_ID: ${{ secrets.PRD_OSU_API_CLIENT_ID }} + PRD_OSU_API_CLIENT_SECRET: ${{ secrets.PRD_OSU_API_CLIENT_SECRET }} \ No newline at end of file diff --git a/.github/workflows/stg-build-deploy.yml b/.github/workflows/stg-build-deploy.yml index 59832ce..05d859d 100644 --- a/.github/workflows/stg-build-deploy.yml +++ b/.github/workflows/stg-build-deploy.yml @@ -17,9 +17,23 @@ jobs: run: | docker-compose -f docker/stg/stg-run-database-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }} + DB_STG_USER: ${{ secrets.DB_STG_USER }} + DB_STG_PASSWORD: ${{ secrets.DB_STG_PASSWORD }} - name: Build and deploy the Docker image run: | docker-compose -f docker/stg/stg-build-deploy-job.yml up -d --build env: - POSTGRES_PASSWORD: ${{ secrets.USER_DB_STG_PASSWORD }} \ No newline at end of file + DB_STG_ADDRESS: ${{ secrets.DB_STG_ADDRESS }} + DB_STG_USER: ${{ secrets.DB_STG_USER }} + DB_STG_PASSWORD: ${{ secrets.DB_STG_PASSWORD }} + EUREKA_STG: ${{ secrets.EUREKA_STG }} + STG_KEYCLOAK_NAME_ISSUER: ${{ secrets.STG_KEYCLOAK_NAME_ISSUER }} + STG_KEYCLOAK_CLIENT_NAME: ${{ secrets.STG_KEYCLOAK_CLIENT_NAME }} + STG_KEYCLOAK_CLIENT_ID: ${{ secrets.STG_KEYCLOAK_CLIENT_ID }} + STG_KEYCLOAK_CLIENT_SECRET: ${{ secrets.STG_KEYCLOAK_CLIENT_SECRET }} + STG_KEYCLOAK_URI: ${{ secrets.STG_KEYCLOAK_URI }} + STG_KEYCLOAK_REALM_NAME: ${{ secrets.STG_KEYCLOAK_REALM_NAME }} + STG_MANAGEMENT_KEYCLOAK_CLIENT_ID: ${{ secrets.STG_MANAGEMENT_KEYCLOAK_CLIENT_ID }} + STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET: ${{ secrets.STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET }} + STG_OSU_API_CLIENT_ID: ${{ secrets.STG_OSU_API_CLIENT_ID }} + STG_OSU_API_CLIENT_SECRET: ${{ secrets.STG_OSU_API_CLIENT_SECRET }} \ No newline at end of file diff --git a/docker/dev/dev-run-database-job.yml b/docker/dev/dev-run-database-job.yml new file mode 100644 index 0000000..4643244 --- /dev/null +++ b/docker/dev/dev-run-database-job.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + db-user-microservice: + image: postgres:15.1 + restart: always + environment: + POSTGRES_USER: testUser + POSTGRES_PASSWORD: testPassword + POSTGRES_DB: user + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 5s + timeout: 5s + retries: 5 + ports: + - "5432:5432" \ No newline at end of file diff --git a/docker/prd/prd-build-deploy-job.yml b/docker/prd/prd-build-deploy-job.yml index acee466..bd8e4bb 100644 --- a/docker/prd/prd-build-deploy-job.yml +++ b/docker/prd/prd-build-deploy-job.yml @@ -1,14 +1,28 @@ version: '3.8' services: - auth-microservice: + user-microservice: build: context: ../.. dockerfile: docker/prd/prd-build-deploy.Dockerfile ports: - "8501:8501" environment: - - USER_DB_PRD_PASSWORD=${POSTGRES_PASSWORD} + - DB_PRD_ADDRESS=${DB_PRD_ADDRESS} + - DB_PRD_USER=${DB_PRD_USER} + - DB_PRD_PASSWORD=${DB_PRD_PASSWORD} + - EUREKA_PRD=${EUREKA_PRD} + - PRD_KEYCLOAK_ISSUER_ID=${PRD_KEYCLOAK_ISSUER_ID} + - PRD_KEYCLOAK_CLIENT_NAME=${PRD_KEYCLOAK_CLIENT_NAME} + - PRD_KEYCLOAK_CLIENT_ID=${PRD_KEYCLOAK_CLIENT_ID} + - PRD_KEYCLOAK_CLIENT_SECRET=${PRD_KEYCLOAK_CLIENT_SECRET} + - PRD_KEYCLOAK_JWK_SET_URI=${PRD_KEYCLOAK_JWK_SET_URI} + - PRD_KEYCLOAK_URI = ${PRD_KEYCLOAK_URI} + - PRD_KEYCLOAK_REALM_NAME = ${PRD_KEYCLOAK_REALM_NAME} + - PRD_MANAGEMENT_KEYCLOAK_CLIENT_ID = ${PRD_MANAGEMENT_KEYCLOAK_CLIENT_ID} + - PRD_MANAGEMENT_KEYCLOAK_CLIENT_SECRET = ${PRD_MANAGEMENT_KEYCLOAK_CLIENT_SECRET} + - PRD_OSU_API_CLIENT_SECRET = ${PRD_OSU_API_CLIENT_SECRET} + - PRD_OSU_API_CLIENT_ID = ${PRD_OSU_API_CLIENT_ID} networks: default: diff --git a/docker/prd/prd-run-database-job.yml b/docker/prd/prd-run-database-job.yml index 9df5ad1..74b2a3c 100644 --- a/docker/prd/prd-run-database-job.yml +++ b/docker/prd/prd-run-database-job.yml @@ -1,12 +1,12 @@ version: '3.8' services: - db-auth-microservice: + db-user-microservice: image: postgres:15.1 restart: always environment: - POSTGRES_USER: user - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_USER: ${DB_PRD_USER} + POSTGRES_PASSWORD: ${DB_PRD_PASSWORD} POSTGRES_DB: user healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] diff --git a/docker/stg/stg-build-deploy-job.yml b/docker/stg/stg-build-deploy-job.yml index 856d0c4..f6f32d8 100644 --- a/docker/stg/stg-build-deploy-job.yml +++ b/docker/stg/stg-build-deploy-job.yml @@ -8,7 +8,21 @@ services: ports: - "8502:8502" environment: - - USER_DB_STG_PASSWORD=${POSTGRES_PASSWORD} + - DB_STG_ADDRESS=${DB_STG_ADDRESS} + - DB_STG_USER=${DB_STG_USER} + - DB_STG_PASSWORD=${DB_STG_PASSWORD} + - EUREKA_STG=${EUREKA_STG} + - STG_KEYCLOAK_NAME_ISSUER=${STG_KEYCLOAK_NAME_ISSUER} + - STG_KEYCLOAK_CLIENT_NAME=${STG_KEYCLOAK_CLIENT_NAME} + - STG_KEYCLOAK_CLIENT_ID=${STG_KEYCLOAK_CLIENT_ID} + - STG_KEYCLOAK_CLIENT_SECRET=${STG_KEYCLOAK_CLIENT_SECRET} + - STG_KEYCLOAK_JWK_SET_URI=${STG_KEYCLOAK_JWK_SET_URI} + - STG_KEYCLOAK_URI = ${STG_KEYCLOAK_URI} + - STG_KEYCLOAK_REALM_NAME = ${STG_KEYCLOAK_REALM_NAME} + - STG_MANAGEMENT_KEYCLOAK_CLIENT_ID = ${STG_MANAGEMENT_KEYCLOAK_CLIENT_ID} + - STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET = ${STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET} + - STG_OSU_API_CLIENT_SECRET = ${STG_OSU_API_CLIENT_SECRET} + - STG_OSU_API_CLIENT_ID = ${STG_OSU_API_CLIENT_ID} networks: default: diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index 58e23b1..fd73906 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -5,8 +5,8 @@ services: image: postgres:15.1 restart: always environment: - POSTGRES_USER: user - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_USER: ${DB_STG_USER} + POSTGRES_PASSWORD: ${DB_STG_PASSWORD} POSTGRES_DB: user healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 3a144e9..221cc29 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -9,7 +9,7 @@ spring: application: name: user-microservice datasource: - url: jdbc:postgresql://localhost:5432/auth + url: jdbc:postgresql://localhost:5432/user username: testUser password: testPassword driver-class-name: org.postgresql.Driver diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml index a2ad138..97aed0d 100644 --- a/src/main/resources/application-prd.yml +++ b/src/main/resources/application-prd.yml @@ -1,7 +1,7 @@ eureka: client: service-url: - defaultZone: http://172.18.0.1:8761/eureka + defaultZone: http://${EUREKA_PRD}/eureka server: servlet: context-path: /user @@ -10,9 +10,9 @@ spring: application: name: user-microservice datasource: - url: jdbc:postgresql://172.18.0.1:5701/auth - username: user - password: ${USER_DB_PRD_PASSWORD} + url: jdbc:postgresql://${DB_PRD_ADDRESS}/user + username: ${DB_PRD_USER} + password: ${DB_PRD_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 @@ -28,6 +28,23 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + security: + oauth2: + client: + provider: + aimcup: + issuer-uri: ${PRD_KEYCLOAK_ISSUER_ID} + registration: + aimcup: + provider: aimcup + client-name: ${PRD_KEYCLOAK_CLIENT_NAME} + client-id: ${PRD_KEYCLOAK_CLIENT_ID} + client-secret: ${PRD_KEYCLOAK_CLIENT_SECRET} + scope: profile,openid,offline_access + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + resourceserver: + jwt: + jwk-set-uri: ${PRD_KEYCLOAK_JWK_SET_URI} springdoc: api-docs: enabled: false diff --git a/src/main/resources/application-stg.yml b/src/main/resources/application-stg.yml index 7288d11..8e84693 100644 --- a/src/main/resources/application-stg.yml +++ b/src/main/resources/application-stg.yml @@ -1,7 +1,7 @@ eureka: client: service-url: - defaultZone: http://172.18.0.1:8762/eureka + defaultZone: http://${EUREKA_STG}/eureka server: servlet: context-path: /user @@ -11,9 +11,9 @@ spring: application: name: user-microservice datasource: - url: jdbc:postgresql://172.18.0.1:5702/user - username: user - password: ${USER_DB_STG_PASSWORD} + url: jdbc:postgresql://${DB_STG_ADDRESS}/user + username: ${DB_STG_USER} + password: ${DB_STG_PASSWORD} driver-class-name: org.postgresql.Driver hikari: connection-timeout: 10000 @@ -33,32 +33,32 @@ spring: auto: validate security: oauth2: - resourceserver: - jwt: - jwk-set-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm/protocol/openid-connect/certs client: provider: aimcup: - issuer-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm + issuer-uri: ${STG_KEYCLOAK_NAME_ISSUER} registration: aimcup: provider: aimcup - client-name: aimcup - client-id: aimcup - client-secret: MUSDaD5wr0p06E8rwlVXkteKagnr29lU + client-name: ${STG_KEYCLOAK_CLIENT_NAME} + client-id: ${STG_KEYCLOAK_CLIENT_ID} + client-secret: ${STG_KEYCLOAK_CLIENT_SECRET} scope: profile,openid,offline_access redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + resourceserver: + jwt: + jwk-set-uri: ${STG_KEYCLOAK_JWK_SET_URI} main: allow-bean-definition-overriding: true aimcup: keycloak: - serverUrl: https://keycloak-stg.aimcup.xyz - realm: aimcup-realm - clientId: ac-management - clientSecret: msZLKuDHgERVggrmZA33xcm2yoqkcYqI + serverUrl: ${STG_KEYCLOAK_URI} + realm: ${STG_KEYCLOAK_REALM_NAME} + clientId: ${STG_MANAGEMENT_KEYCLOAK_CLIENT_ID} + clientSecret: ${STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET} osu: api: - clientId: 9552 - clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt \ No newline at end of file + clientId: ${STG_OSU_API_CLIENT_ID} + clientSecret: ${STG_OSU_API_CLIENT_SECRET} \ No newline at end of file From fe8dfd8f829f51e1990f8af4c336a3e10b95ba8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20S=C5=82owiak?= Date: Fri, 22 Dec 2023 10:01:43 +0100 Subject: [PATCH 38/39] [AC-62] Migrate to env variables - dev env --- src/main/resources/application-dev.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 221cc29..371bc7b 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -31,32 +31,32 @@ spring: auto: validate security: oauth2: - resourceserver: - jwt: - jwk-set-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm/protocol/openid-connect/certs client: provider: aimcup: - issuer-uri: https://keycloak-stg.aimcup.xyz/realms/aimcup-realm + issuer-uri: ${OSU_KEYCLOAK_ISSUER_ID} registration: aimcup: provider: aimcup - client-name: aimcup - client-id: aimcup - client-secret: MUSDaD5wr0p06E8rwlVXkteKagnr29lU + client-name: ${OSU_KEYCLOAK_CLIENT_NAME} + client-id: ${OSU_KEYCLOAK_CLIENT_ID} + client-secret: ${OSU_KEYCLOAK_CLIENT_SECRET} scope: profile,openid,offline_access redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + resourceserver: + jwt: + jwk-set-uri: ${OSU_KEYCLOAK_JWK_SET_URI} main: allow-bean-definition-overriding: true aimcup: keycloak: - serverUrl: https://keycloak-stg.aimcup.xyz - realm: aimcup-realm - clientId: ac-management - clientSecret: msZLKuDHgERVggrmZA33xcm2yoqkcYqI + serverUrl: ${STG_KEYCLOAK_URI} + realm: ${STG_KEYCLOAK_REALM_NAME} + clientId: ${STG_MANAGEMENT_KEYCLOAK_CLIENT_ID} + clientSecret: ${STG_MANAGEMENT_KEYCLOAK_CLIENT_SECRET} osu: api: - clientId: 9552 - clientSecret: md6Y4UwjAwTJ3VQAbfwUnzxLlZ6JMRY3ivGJaRkt \ No newline at end of file + clientId: ${STG_OSU_API_CLIENT_ID} + clientSecret: ${STG_OSU_API_CLIENT_SECRET} \ No newline at end of file From 7ada0751922ecfb1c477629256e59d91d6a13e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20S=C5=82owiak?= Date: Sat, 20 Jan 2024 13:58:56 +0100 Subject: [PATCH 39/39] [AC-64] Fix error in database healthcheck --- docker/dev/dev-run-database-job.yml | 2 +- docker/prd/prd-run-database-job.yml | 2 +- docker/stg/stg-run-database-job.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/dev/dev-run-database-job.yml b/docker/dev/dev-run-database-job.yml index 4643244..c3e7ed8 100644 --- a/docker/dev/dev-run-database-job.yml +++ b/docker/dev/dev-run-database-job.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: testPassword POSTGRES_DB: user healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres" ] + test: [ "CMD-SHELL", "pg_isready -U testUser user" ] interval: 5s timeout: 5s retries: 5 diff --git a/docker/prd/prd-run-database-job.yml b/docker/prd/prd-run-database-job.yml index 74b2a3c..6c096ad 100644 --- a/docker/prd/prd-run-database-job.yml +++ b/docker/prd/prd-run-database-job.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: ${DB_PRD_PASSWORD} POSTGRES_DB: user healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres" ] + test: [ "CMD-SHELL", "pg_isready -U $$DB_PRD_USER user" ] interval: 5s timeout: 5s retries: 5 diff --git a/docker/stg/stg-run-database-job.yml b/docker/stg/stg-run-database-job.yml index fd73906..ac9e413 100644 --- a/docker/stg/stg-run-database-job.yml +++ b/docker/stg/stg-run-database-job.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: ${DB_STG_PASSWORD} POSTGRES_DB: user healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres" ] + test: [ "CMD-SHELL", "pg_isready -U $$DB_STG_USER user" ] interval: 5s timeout: 5s retries: 5