diff --git a/Dockerfile b/Dockerfile index e93c528f8..74204e3a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ -FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim as builder +FROM eclipse-temurin:17.0.6_10-jdk-focal as builder WORKDIR /usr/src/app ADD . . RUN ./mvnw package -Dmaven.test.skip=true ##################################################### -FROM adoptopenjdk/openjdk11:jre-11.0.6_10-alpine +FROM eclipse-temurin:17.0.6_10-jre-focal COPY --from=builder /usr/src/app/target/ego-*-exec.jar /usr/bin/ego.jar ENV EGO_USER ego ENV EGO_USER_ID 9999 ENV EGO_GROUP_ID 9999 ENV EGO_DIR /target -RUN addgroup -S -g $EGO_GROUP_ID $EGO_USER \ - && adduser -S -u $EGO_USER_ID -G $EGO_USER $EGO_USER \ +RUN addgroup --system --gid $EGO_GROUP_ID $EGO_USER \ + && adduser --system --uid $EGO_USER_ID --ingroup $EGO_USER $EGO_USER \ && mkdir -p $EGO_DIR \ && chown -R $EGO_USER $EGO_DIR USER $EGO_USER_ID diff --git a/Jenkinsfile b/Jenkinsfile index 8d59c0e17..cdfc58f00 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,10 +12,10 @@ spec: - name: maven command: ['cat'] tty: true - image: maven:3.6.3-openjdk-11 + image: maven:3.8.5-openjdk-17 - name: jdk tty: true - image: adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim + image: eclipse-temurin:17.0.6_10-jdk-focal env: - name: DOCKER_HOST value: tcp://localhost:2375 diff --git a/pom.xml b/pom.xml index 37bcaa7d7..a3c387522 100644 --- a/pom.xml +++ b/pom.xml @@ -13,16 +13,18 @@ org.springframework.boot spring-boot-starter-parent - 2.5.12 + 3.0.5 UTF-8 UTF-8 - 11 + 17 1.2.0.Final - 1.34.1 + 1.54.0 + + 6.0.3 @@ -34,6 +36,14 @@ org.springframework.boot spring-boot-starter-actuator + + + + org.springframework.security + spring-security-oauth2-authorization-server + 1.0.1 + + org.springframework.boot spring-boot-starter-security @@ -52,8 +62,8 @@ org.springframework.security - spring-security-oauth2-authorization-server - 0.2.1 + spring-security-oauth2-client + 6.0.3 @@ -68,34 +78,31 @@ org.projectlombok lombok - 1.18.16 + + 1.18.22 true - - - io.springfox - springfox-swagger2 - 2.9.2 - compile - - - org.mapstruct - mapstruct - - - + - io.springfox - springfox-swagger-ui - 2.9.2 - compile + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 org.springframework.boot spring-boot-starter-test test - + + com.auth0 + java-jwt + 4.4.0 + + + com.auth0 + jwks-rsa + 0.22.0 + org.springframework.security spring-security-test @@ -135,25 +142,25 @@ com.vladmihalcea - hibernate-types-52 - 2.2.2 + hibernate-types-60 + 2.21.1 org.testcontainers testcontainers - 1.15.1 + 1.18.0 org.testcontainers jdbc - 1.15.1 + 1.18.0 org.testcontainers postgresql - 1.15.1 + 1.18.0 commons-io @@ -166,11 +173,11 @@ 2.6 - + org.flywaydb flyway-core - 5.2.4 + 9.16.3 @@ -241,8 +248,18 @@ org.springframework.cloud spring-cloud-starter-vault-config - 2.1.2.RELEASE + 4.0.1 + + + org.springframework.cloud + spring-cloud-starter-bootstrap + 4.0.1 + + org.springframework.cloud + spring-cloud-vault-config-databases + 4.0.1 + com.amazonaws aws-java-sdk-core @@ -304,6 +321,13 @@ test + + + javax.annotation + javax.annotation-api + 1.3.2 + + org.junit.vintage junit-vintage-engine @@ -356,7 +380,7 @@ spring-boot-maven-plugin exec - true + @@ -389,12 +413,16 @@ org.xolstice.maven.plugins protobuf-maven-plugin - 0.5.1 + 0.6.1 com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} + + ${basedir}/src/main/proto grpc-java - io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.54.0:exe:${os.detected.classifier} + + @@ -434,7 +462,7 @@ org.springframework.cloud spring-cloud-dependencies - 2020.0.4 + 2022.0.2 pom import diff --git a/src/main/bin/wrapper.log b/src/main/bin/wrapper.log deleted file mode 100644 index f45ee9531..000000000 --- a/src/main/bin/wrapper.log +++ /dev/null @@ -1,221 +0,0 @@ -FATAL | wrapper | 2017/11/15 11:19:39 | Unable to open configuration file: /Users/jeubank/workspace/overture-stack/ego/src/main/bin/../conf/wrapper.conf (No such file or directory) -FATAL | wrapper | 2017/11/15 11:19:39 | Current working directory: /Users/jeubank/workspace/overture-stack/ego/src/main/bin -WARN | wrapper | 2017/11/15 11:20:22 | Unable to write to the configured log directory: ../logs (No such file or directory) -WARN | wrapper | 2017/11/15 11:20:22 | The directory does not exist. -WARN | wrapper | 2017/11/15 11:20:22 | Unable to write to the configured log file: ../logs/wrapper.20171115.log (No such file or directory) -WARN | wrapper | 2017/11/15 11:20:22 | Falling back to the default file in the current working directory: wrapper.log -WARN | wrapper | 2017/11/15 11:20:22 | The version of the script (3.5.19) doesn't match the version of this Wrapper (3.5.21). This might cause some problems -STATUS | wrapper | 2017/11/15 11:20:22 | --> Wrapper Started as Daemon -STATUS | wrapper | 2017/11/15 11:20:22 | Java Service Wrapper Community Edition 64-bit 3.5.21 -STATUS | wrapper | 2017/11/15 11:20:22 | Copyright (C) 1999-2013 Tanuki Software, Ltd. All Rights Reserved. -STATUS | wrapper | 2017/11/15 11:20:22 | http://wrapper.tanukisoftware.com -STATUS | wrapper | 2017/11/15 11:20:22 | -STATUS | wrapper | 2017/11/15 11:20:22 | Launching a JVM... -INFO | jvm 1 | 2017/11/15 11:20:23 | WrapperManager: Initializing... -INFO | jvm 1 | 2017/11/15 11:20:23 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 1 | 2017/11/15 11:20:23 | -INFO | jvm 1 | 2017/11/15 11:20:23 | WrapperSimpleApp Usage: -INFO | jvm 1 | 2017/11/15 11:20:23 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 1 | 2017/11/15 11:20:23 | -INFO | jvm 1 | 2017/11/15 11:20:23 | Where: -INFO | jvm 1 | 2017/11/15 11:20:23 | app_class: The fully qualified class name of the application to run. -INFO | jvm 1 | 2017/11/15 11:20:23 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 1 | 2017/11/15 11:20:23 | application. -ERROR | wrapper | 2017/11/15 11:20:25 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:20:29 | Launching a JVM... -INFO | jvm 2 | 2017/11/15 11:20:30 | WrapperManager: Initializing... -INFO | jvm 2 | 2017/11/15 11:20:30 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 2 | 2017/11/15 11:20:30 | -INFO | jvm 2 | 2017/11/15 11:20:30 | WrapperSimpleApp Usage: -INFO | jvm 2 | 2017/11/15 11:20:30 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 2 | 2017/11/15 11:20:30 | -INFO | jvm 2 | 2017/11/15 11:20:30 | Where: -INFO | jvm 2 | 2017/11/15 11:20:30 | app_class: The fully qualified class name of the application to run. -INFO | jvm 2 | 2017/11/15 11:20:30 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 2 | 2017/11/15 11:20:30 | application. -ERROR | wrapper | 2017/11/15 11:20:32 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:20:36 | Launching a JVM... -INFO | jvm 3 | 2017/11/15 11:20:37 | WrapperManager: Initializing... -INFO | jvm 3 | 2017/11/15 11:20:37 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 3 | 2017/11/15 11:20:37 | -INFO | jvm 3 | 2017/11/15 11:20:37 | WrapperSimpleApp Usage: -INFO | jvm 3 | 2017/11/15 11:20:37 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 3 | 2017/11/15 11:20:37 | -INFO | jvm 3 | 2017/11/15 11:20:37 | Where: -INFO | jvm 3 | 2017/11/15 11:20:37 | app_class: The fully qualified class name of the application to run. -INFO | jvm 3 | 2017/11/15 11:20:37 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 3 | 2017/11/15 11:20:37 | application. -ERROR | wrapper | 2017/11/15 11:20:39 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:20:44 | Launching a JVM... -INFO | jvm 4 | 2017/11/15 11:20:44 | WrapperManager: Initializing... -INFO | jvm 4 | 2017/11/15 11:20:44 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 4 | 2017/11/15 11:20:44 | -INFO | jvm 4 | 2017/11/15 11:20:44 | WrapperSimpleApp Usage: -INFO | jvm 4 | 2017/11/15 11:20:44 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 4 | 2017/11/15 11:20:44 | -INFO | jvm 4 | 2017/11/15 11:20:44 | Where: -INFO | jvm 4 | 2017/11/15 11:20:44 | app_class: The fully qualified class name of the application to run. -INFO | jvm 4 | 2017/11/15 11:20:44 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 4 | 2017/11/15 11:20:44 | application. -ERROR | wrapper | 2017/11/15 11:20:46 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:20:51 | Launching a JVM... -INFO | jvm 5 | 2017/11/15 11:20:51 | WrapperManager: Initializing... -INFO | jvm 5 | 2017/11/15 11:20:51 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 5 | 2017/11/15 11:20:51 | -INFO | jvm 5 | 2017/11/15 11:20:51 | WrapperSimpleApp Usage: -INFO | jvm 5 | 2017/11/15 11:20:51 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 5 | 2017/11/15 11:20:51 | -INFO | jvm 5 | 2017/11/15 11:20:51 | Where: -INFO | jvm 5 | 2017/11/15 11:20:51 | app_class: The fully qualified class name of the application to run. -INFO | jvm 5 | 2017/11/15 11:20:51 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 5 | 2017/11/15 11:20:51 | application. -ERROR | wrapper | 2017/11/15 11:20:53 | JVM exited while loading the application. -FATAL | wrapper | 2017/11/15 11:20:53 | There were 5 failed launches in a row, each lasting less than 300 seconds. Giving up. -FATAL | wrapper | 2017/11/15 11:20:53 | There may be a configuration problem: please check the logs. -STATUS | wrapper | 2017/11/15 11:20:53 | <-- Wrapper Stopped -WARN | wrapper | 2017/11/15 11:21:01 | Unable to write to the configured log directory: ../logs (No such file or directory) -WARN | wrapper | 2017/11/15 11:21:01 | The directory does not exist. -WARN | wrapper | 2017/11/15 11:21:01 | Unable to write to the configured log file: ../logs/wrapper.20171115.log (No such file or directory) -WARN | wrapper | 2017/11/15 11:21:01 | Falling back to the default file in the current working directory: wrapper.log -WARN | wrapper | 2017/11/15 11:21:01 | The version of the script (3.5.19) doesn't match the version of this Wrapper (3.5.21). This might cause some problems -STATUS | wrapper | 2017/11/15 11:21:01 | --> Wrapper Started as Daemon -STATUS | wrapper | 2017/11/15 11:21:01 | Java Service Wrapper Community Edition 64-bit 3.5.21 -STATUS | wrapper | 2017/11/15 11:21:01 | Copyright (C) 1999-2013 Tanuki Software, Ltd. All Rights Reserved. -STATUS | wrapper | 2017/11/15 11:21:01 | http://wrapper.tanukisoftware.com -STATUS | wrapper | 2017/11/15 11:21:01 | -STATUS | wrapper | 2017/11/15 11:21:01 | Launching a JVM... -INFO | jvm 1 | 2017/11/15 11:21:02 | WrapperManager: Initializing... -INFO | jvm 1 | 2017/11/15 11:21:02 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 1 | 2017/11/15 11:21:02 | -INFO | jvm 1 | 2017/11/15 11:21:02 | WrapperSimpleApp Usage: -INFO | jvm 1 | 2017/11/15 11:21:02 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 1 | 2017/11/15 11:21:02 | -INFO | jvm 1 | 2017/11/15 11:21:02 | Where: -INFO | jvm 1 | 2017/11/15 11:21:02 | app_class: The fully qualified class name of the application to run. -INFO | jvm 1 | 2017/11/15 11:21:02 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 1 | 2017/11/15 11:21:02 | application. -ERROR | wrapper | 2017/11/15 11:21:04 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:08 | Launching a JVM... -INFO | jvm 2 | 2017/11/15 11:21:09 | WrapperManager: Initializing... -INFO | jvm 2 | 2017/11/15 11:21:09 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 2 | 2017/11/15 11:21:09 | -INFO | jvm 2 | 2017/11/15 11:21:09 | WrapperSimpleApp Usage: -INFO | jvm 2 | 2017/11/15 11:21:09 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 2 | 2017/11/15 11:21:09 | -INFO | jvm 2 | 2017/11/15 11:21:09 | Where: -INFO | jvm 2 | 2017/11/15 11:21:09 | app_class: The fully qualified class name of the application to run. -INFO | jvm 2 | 2017/11/15 11:21:09 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 2 | 2017/11/15 11:21:09 | application. -ERROR | wrapper | 2017/11/15 11:21:11 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:15 | Launching a JVM... -INFO | jvm 3 | 2017/11/15 11:21:16 | WrapperManager: Initializing... -INFO | jvm 3 | 2017/11/15 11:21:16 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 3 | 2017/11/15 11:21:16 | -INFO | jvm 3 | 2017/11/15 11:21:16 | WrapperSimpleApp Usage: -INFO | jvm 3 | 2017/11/15 11:21:16 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 3 | 2017/11/15 11:21:16 | -INFO | jvm 3 | 2017/11/15 11:21:16 | Where: -INFO | jvm 3 | 2017/11/15 11:21:16 | app_class: The fully qualified class name of the application to run. -INFO | jvm 3 | 2017/11/15 11:21:16 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 3 | 2017/11/15 11:21:16 | application. -ERROR | wrapper | 2017/11/15 11:21:18 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:22 | Launching a JVM... -INFO | jvm 4 | 2017/11/15 11:21:23 | WrapperManager: Initializing... -INFO | jvm 4 | 2017/11/15 11:21:23 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 4 | 2017/11/15 11:21:23 | -INFO | jvm 4 | 2017/11/15 11:21:23 | WrapperSimpleApp Usage: -INFO | jvm 4 | 2017/11/15 11:21:23 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 4 | 2017/11/15 11:21:23 | -INFO | jvm 4 | 2017/11/15 11:21:23 | Where: -INFO | jvm 4 | 2017/11/15 11:21:23 | app_class: The fully qualified class name of the application to run. -INFO | jvm 4 | 2017/11/15 11:21:23 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 4 | 2017/11/15 11:21:23 | application. -ERROR | wrapper | 2017/11/15 11:21:25 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:30 | Launching a JVM... -INFO | jvm 5 | 2017/11/15 11:21:30 | WrapperManager: Initializing... -INFO | jvm 5 | 2017/11/15 11:21:30 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 5 | 2017/11/15 11:21:30 | -INFO | jvm 5 | 2017/11/15 11:21:30 | WrapperSimpleApp Usage: -INFO | jvm 5 | 2017/11/15 11:21:30 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 5 | 2017/11/15 11:21:30 | -INFO | jvm 5 | 2017/11/15 11:21:30 | Where: -INFO | jvm 5 | 2017/11/15 11:21:30 | app_class: The fully qualified class name of the application to run. -INFO | jvm 5 | 2017/11/15 11:21:30 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 5 | 2017/11/15 11:21:30 | application. -ERROR | wrapper | 2017/11/15 11:21:32 | JVM exited while loading the application. -FATAL | wrapper | 2017/11/15 11:21:32 | There were 5 failed launches in a row, each lasting less than 300 seconds. Giving up. -FATAL | wrapper | 2017/11/15 11:21:32 | There may be a configuration problem: please check the logs. -STATUS | wrapper | 2017/11/15 11:21:32 | <-- Wrapper Stopped -WARN | wrapper | 2017/11/15 11:21:42 | Unable to write to the configured log directory: ../logs (No such file or directory) -WARN | wrapper | 2017/11/15 11:21:42 | The directory does not exist. -WARN | wrapper | 2017/11/15 11:21:42 | Unable to write to the configured log file: ../logs/wrapper.20171115.log (No such file or directory) -WARN | wrapper | 2017/11/15 11:21:42 | Falling back to the default file in the current working directory: wrapper.log -WARN | wrapper | 2017/11/15 11:21:42 | The version of the script (3.5.19) doesn't match the version of this Wrapper (3.5.21). This might cause some problems -STATUS | wrapper | 2017/11/15 11:21:42 | --> Wrapper Started as Daemon -STATUS | wrapper | 2017/11/15 11:21:42 | Java Service Wrapper Community Edition 64-bit 3.5.21 -STATUS | wrapper | 2017/11/15 11:21:42 | Copyright (C) 1999-2013 Tanuki Software, Ltd. All Rights Reserved. -STATUS | wrapper | 2017/11/15 11:21:42 | http://wrapper.tanukisoftware.com -STATUS | wrapper | 2017/11/15 11:21:42 | -STATUS | wrapper | 2017/11/15 11:21:42 | Launching a JVM... -INFO | jvm 1 | 2017/11/15 11:21:42 | WrapperManager: Initializing... -INFO | jvm 1 | 2017/11/15 11:21:42 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 1 | 2017/11/15 11:21:42 | -INFO | jvm 1 | 2017/11/15 11:21:42 | WrapperSimpleApp Usage: -INFO | jvm 1 | 2017/11/15 11:21:42 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 1 | 2017/11/15 11:21:42 | -INFO | jvm 1 | 2017/11/15 11:21:42 | Where: -INFO | jvm 1 | 2017/11/15 11:21:42 | app_class: The fully qualified class name of the application to run. -INFO | jvm 1 | 2017/11/15 11:21:42 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 1 | 2017/11/15 11:21:42 | application. -ERROR | wrapper | 2017/11/15 11:21:44 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:49 | Launching a JVM... -INFO | jvm 2 | 2017/11/15 11:21:49 | WrapperManager: Initializing... -INFO | jvm 2 | 2017/11/15 11:21:49 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 2 | 2017/11/15 11:21:49 | -INFO | jvm 2 | 2017/11/15 11:21:49 | WrapperSimpleApp Usage: -INFO | jvm 2 | 2017/11/15 11:21:49 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 2 | 2017/11/15 11:21:49 | -INFO | jvm 2 | 2017/11/15 11:21:49 | Where: -INFO | jvm 2 | 2017/11/15 11:21:49 | app_class: The fully qualified class name of the application to run. -INFO | jvm 2 | 2017/11/15 11:21:49 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 2 | 2017/11/15 11:21:49 | application. -ERROR | wrapper | 2017/11/15 11:21:52 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:21:56 | Launching a JVM... -INFO | jvm 3 | 2017/11/15 11:21:56 | WrapperManager: Initializing... -INFO | jvm 3 | 2017/11/15 11:21:56 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 3 | 2017/11/15 11:21:56 | -INFO | jvm 3 | 2017/11/15 11:21:56 | WrapperSimpleApp Usage: -INFO | jvm 3 | 2017/11/15 11:21:56 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 3 | 2017/11/15 11:21:56 | -INFO | jvm 3 | 2017/11/15 11:21:56 | Where: -INFO | jvm 3 | 2017/11/15 11:21:56 | app_class: The fully qualified class name of the application to run. -INFO | jvm 3 | 2017/11/15 11:21:56 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 3 | 2017/11/15 11:21:56 | application. -ERROR | wrapper | 2017/11/15 11:21:59 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:22:03 | Launching a JVM... -INFO | jvm 4 | 2017/11/15 11:22:03 | WrapperManager: Initializing... -INFO | jvm 4 | 2017/11/15 11:22:04 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 4 | 2017/11/15 11:22:04 | -INFO | jvm 4 | 2017/11/15 11:22:04 | WrapperSimpleApp Usage: -INFO | jvm 4 | 2017/11/15 11:22:04 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 4 | 2017/11/15 11:22:04 | -INFO | jvm 4 | 2017/11/15 11:22:04 | Where: -INFO | jvm 4 | 2017/11/15 11:22:04 | app_class: The fully qualified class name of the application to run. -INFO | jvm 4 | 2017/11/15 11:22:04 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 4 | 2017/11/15 11:22:04 | application. -ERROR | wrapper | 2017/11/15 11:22:06 | JVM exited while loading the application. -STATUS | wrapper | 2017/11/15 11:22:10 | Launching a JVM... -INFO | jvm 5 | 2017/11/15 11:22:11 | WrapperManager: Initializing... -INFO | jvm 5 | 2017/11/15 11:22:11 | WrapperSimpleApp Error: Unable to locate the class org.springframework.boot.loader.JarLauncher : java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher -INFO | jvm 5 | 2017/11/15 11:22:11 | -INFO | jvm 5 | 2017/11/15 11:22:11 | WrapperSimpleApp Usage: -INFO | jvm 5 | 2017/11/15 11:22:11 | java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class{/app_method}} [app_arguments] -INFO | jvm 5 | 2017/11/15 11:22:11 | -INFO | jvm 5 | 2017/11/15 11:22:11 | Where: -INFO | jvm 5 | 2017/11/15 11:22:11 | app_class: The fully qualified class name of the application to run. -INFO | jvm 5 | 2017/11/15 11:22:11 | app_arguments: The arguments that would normally be passed to the -INFO | jvm 5 | 2017/11/15 11:22:11 | application. -ERROR | wrapper | 2017/11/15 11:22:13 | JVM exited while loading the application. -FATAL | wrapper | 2017/11/15 11:22:13 | There were 5 failed launches in a row, each lasting less than 300 seconds. Giving up. -FATAL | wrapper | 2017/11/15 11:22:13 | There may be a configuration problem: please check the logs. -STATUS | wrapper | 2017/11/15 11:22:13 | <-- Wrapper Stopped diff --git a/src/main/java/bio/overture/ego/AuthorizationServiceMain.java b/src/main/java/bio/overture/ego/AuthorizationServiceMain.java index 1fbc9b94a..9ef972a81 100644 --- a/src/main/java/bio/overture/ego/AuthorizationServiceMain.java +++ b/src/main/java/bio/overture/ego/AuthorizationServiceMain.java @@ -18,8 +18,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication +@EnableCaching public class AuthorizationServiceMain { public static void main(String[] args) { diff --git a/src/main/java/bio/overture/ego/config/AppSecureServerConfig.java b/src/main/java/bio/overture/ego/config/AppSecureServerConfig.java new file mode 100644 index 000000000..426615aca --- /dev/null +++ b/src/main/java/bio/overture/ego/config/AppSecureServerConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017. The Ontario Institute for Cancer Research. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bio.overture.ego.config; + +import bio.overture.ego.security.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@Import(OAuth2ClientConfig.class) +@Profile("auth") +public class AppSecureServerConfig { + + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + @Autowired JWTAuthorizationFilter authorizationFilter; + + @Bean + @Order(SecurityProperties.BASIC_AUTH_ORDER - 6) + public SecurityFilterChain appFilterChain(HttpSecurity http) throws Exception { + return http.csrf() + .disable() + .apply(authorizationServerConfigurer) + .and() + .securityMatcher( + "/", + "/favicon.ico", + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/configuration/ui", + "/configuration/**", + "/v2/api**", + "/webjars/**", + "/actuator/**", + "/o/**", + "/oauth/token", + "/oauth/token/verify", + "/oauth/token/public_key") + .authorizeRequests() + .requestMatchers( + "/", + "/favicon.ico", + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/configuration/ui", + "/configuration/**", + "/v2/api**", + "/webjars/**", + "/actuator/**", + "/oauth/token/verify", + "/oauth/token/public_key") + .permitAll() + .requestMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .addFilterBefore(authorizationFilter, BasicAuthenticationFilter.class) + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .build(); + } +} diff --git a/src/main/java/bio/overture/ego/config/AuthorizationServerConfig.java b/src/main/java/bio/overture/ego/config/AuthorizationServerConfig.java index a3f33602b..d46580d20 100644 --- a/src/main/java/bio/overture/ego/config/AuthorizationServerConfig.java +++ b/src/main/java/bio/overture/ego/config/AuthorizationServerConfig.java @@ -16,7 +16,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; @Configuration public class AuthorizationServerConfig { @@ -48,7 +48,11 @@ public JwtDecoder jwtDecoder(@Autowired TokenSigner tokenSigner) { } @Bean - public ProviderSettings providerSettings(@Value("${token.issuer}") String issuer) { - return ProviderSettings.builder().tokenEndpoint("/oauth/token").issuer(issuer).build(); + public AuthorizationServerSettings providerSettings(@Value("${token.issuer}") String issuer) { + + return AuthorizationServerSettings.builder() + .tokenEndpoint("/oauth/token") + .issuer(issuer) + .build(); } } diff --git a/src/main/java/bio/overture/ego/config/InitializationConfig.java b/src/main/java/bio/overture/ego/config/InitializationConfig.java index 76b28054b..83c9d1511 100644 --- a/src/main/java/bio/overture/ego/config/InitializationConfig.java +++ b/src/main/java/bio/overture/ego/config/InitializationConfig.java @@ -1,11 +1,11 @@ package bio.overture.ego.config; import bio.overture.ego.model.enums.ApplicationType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/bio/overture/ego/config/OAuth2AccessTokenResponseConverterWithDefaults.java b/src/main/java/bio/overture/ego/config/OAuth2AccessTokenResponseConverterWithDefaults.java index ff4693a2c..f6d2bd7d8 100644 --- a/src/main/java/bio/overture/ego/config/OAuth2AccessTokenResponseConverterWithDefaults.java +++ b/src/main/java/bio/overture/ego/config/OAuth2AccessTokenResponseConverterWithDefaults.java @@ -14,40 +14,43 @@ // oauth2 spec. // https://github.com/spring-projects/spring-security/issues/5983 public class OAuth2AccessTokenResponseConverterWithDefaults - implements Converter, OAuth2AccessTokenResponse> { + implements Converter, OAuth2AccessTokenResponse> { private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of( OAuth2ParameterNames.ACCESS_TOKEN, OAuth2ParameterNames.TOKEN_TYPE, OAuth2ParameterNames.EXPIRES_IN, - OAuth2ParameterNames.REFRESH_TOKEN, OAuth2ParameterNames.SCOPE) .collect(Collectors.toSet()); private OAuth2AccessToken.TokenType defaultAccessTokenType = OAuth2AccessToken.TokenType.BEARER; @Override - public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { - String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); + public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { + String accessToken = + getParameterValue(tokenResponseParameters, OAuth2ParameterNames.ACCESS_TOKEN); OAuth2AccessToken.TokenType accessTokenType = this.defaultAccessTokenType; if (OAuth2AccessToken.TokenType.BEARER .getValue() - .equalsIgnoreCase(tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) { + .equalsIgnoreCase( + getParameterValue(tokenResponseParameters, OAuth2ParameterNames.TOKEN_TYPE))) { accessTokenType = OAuth2AccessToken.TokenType.BEARER; } long expiresIn = 0; if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { try { - expiresIn = Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN)); + expiresIn = + Long.parseLong( + getParameterValue(tokenResponseParameters, OAuth2ParameterNames.EXPIRES_IN)); } catch (NumberFormatException ignored) { } } Set scopes = Collections.emptySet(); if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { - String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); + String scope = getParameterValue(tokenResponseParameters, OAuth2ParameterNames.SCOPE); scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")) .collect(Collectors.toSet()); @@ -70,4 +73,10 @@ public final void setDefaultAccessTokenType(OAuth2AccessToken.TokenType defaultA Assert.notNull(defaultAccessTokenType, "defaultAccessTokenType cannot be null"); this.defaultAccessTokenType = defaultAccessTokenType; } + + private static String getParameterValue( + Map tokenResponseParameters, String parameterName) { + Object obj = tokenResponseParameters.get(parameterName); + return obj != null ? obj.toString() : null; + } } diff --git a/src/main/java/bio/overture/ego/config/OAuth2ClientConfig.java b/src/main/java/bio/overture/ego/config/OAuth2ClientConfig.java index a5165f0e6..683eb3ba0 100644 --- a/src/main/java/bio/overture/ego/config/OAuth2ClientConfig.java +++ b/src/main/java/bio/overture/ego/config/OAuth2ClientConfig.java @@ -30,7 +30,7 @@ public class OAuth2ClientConfig { @Bean public OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver( ClientRegistrationRepository clientRegistrationRepository) { - return new OAuth2RequestResolver(clientRegistrationRepository, "/oauth/login/"); + return new OAuth2RequestResolver(clientRegistrationRepository, "/oauth/login"); } @Bean diff --git a/src/main/java/bio/overture/ego/config/OauthSecureServerConfig.java b/src/main/java/bio/overture/ego/config/OauthSecureServerConfig.java new file mode 100644 index 000000000..fb4367dfc --- /dev/null +++ b/src/main/java/bio/overture/ego/config/OauthSecureServerConfig.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2017. The Ontario Institute for Cancer Research. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bio.overture.ego.config; + +import bio.overture.ego.model.exceptions.SSOAuthenticationFailureHandler; +import bio.overture.ego.security.*; +import bio.overture.ego.service.ApplicationService; +import bio.overture.ego.utils.Redirects; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import lombok.val; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.web.client.RestTemplate; + +@Configuration +@EnableWebSecurity +@Import(OAuth2ClientConfig.class) +@Profile("auth") +public class OauthSecureServerConfig { + + @Bean + public AuthorizationManager authorizationManager() { + return new SecureAuthorizationManager(); + } + + final OAuth2AuthorizationRequestResolver oAuth2RequestResolver; + final CustomOAuth2UserInfoService customOAuth2UserInfoService; + final CustomOidc2UserInfoService customOidc2UserInfoService; + final ApplicationService applicationService; + final SSOAuthenticationFailureHandler failureHandler; + + public OauthSecureServerConfig( + OAuth2AuthorizationRequestResolver requestResolver, + CustomOAuth2UserInfoService customOAuth2UserInfoService, + CustomOidc2UserInfoService customOidc2UserInfoService, + ApplicationService applicationService, + SSOAuthenticationFailureHandler failureHandler) { + this.oAuth2RequestResolver = requestResolver; + this.customOAuth2UserInfoService = customOAuth2UserInfoService; + this.customOidc2UserInfoService = customOidc2UserInfoService; + this.applicationService = applicationService; + this.failureHandler = failureHandler; + } + + @Bean + public SimpleUrlAuthenticationSuccessHandler successHandler() { + return new SimpleUrlAuthenticationSuccessHandler() { + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + val application = + applicationService.getByClientId( + (String) request.getSession().getAttribute("ego_client_id")); + + String redirectUri = (String) request.getSession().getAttribute("ego_redirect_uri"); + + val redirect = Redirects.getRedirectUri(application, redirectUri); + if (!redirect.isEmpty()) { + this.setDefaultTargetUrl(redirect); + super.onAuthenticationSuccess(request, response, authentication); + } else { + throw new OAuth2AuthenticationException( + new OAuth2Error(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT), + "Incorrect redirect uri for ego client."); + } + } + }; + } + + @Bean + @Order(SecurityProperties.BASIC_AUTH_ORDER + 3000) + public SecurityFilterChain oathFilterChain(HttpSecurity http) throws Exception { + return http.csrf() + .disable() + .securityMatcher( + "/oauth/code/*", + "/oauth/login/*", + "/oauth/ego-token", + "/oauth/update-ego-token", + "/oauth/refresh") + .authorizeHttpRequests() + .requestMatchers( + "/oauth/code/*", + "/oauth/login/*", + "/oauth/ego-token", + "/oauth/update-ego-token", + "/oauth/refresh") + .permitAll() + .and() + .oauth2Login( + x -> { + x.redirectionEndpoint().baseUri("/oauth/code/{registrationId}"); + x.authorizationEndpoint(y -> y.authorizationRequestResolver(oAuth2RequestResolver)); + x.tokenEndpoint() + .accessTokenResponseClient(this.authorizationCodeTokenResponseClient()); + x.userInfoEndpoint().oidcUserService(this.customOidc2UserInfoService); + x.userInfoEndpoint().userService(customOAuth2UserInfoService); + x.successHandler(this.successHandler()); + x.failureHandler(this.failureHandler); + }) + .build(); + } + + private OAuth2AccessTokenResponseClient + authorizationCodeTokenResponseClient() { + val tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); + tokenResponseHttpMessageConverter.setAccessTokenResponseConverter( + new OAuth2AccessTokenResponseConverterWithDefaults()); + + val restTemplate = + new RestTemplate( + Arrays.asList(new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); + restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); + + val tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); + tokenResponseClient.setRestOperations(restTemplate); + + return tokenResponseClient; + } +} diff --git a/src/main/java/bio/overture/ego/config/SecureServerConfig.java b/src/main/java/bio/overture/ego/config/SecureServerConfig.java index 7ddeb2447..533b0f02b 100644 --- a/src/main/java/bio/overture/ego/config/SecureServerConfig.java +++ b/src/main/java/bio/overture/ego/config/SecureServerConfig.java @@ -16,43 +16,13 @@ package bio.overture.ego.config; -import bio.overture.ego.model.exceptions.SSOAuthenticationFailureHandler; import bio.overture.ego.security.*; import bio.overture.ego.service.ApplicationService; import bio.overture.ego.service.TokenService; -import bio.overture.ego.utils.Redirects; -import java.io.IOException; -import java.util.Arrays; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.SneakyThrows; -import lombok.val; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.*; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpMethod; -import org.springframework.http.converter.FormHttpMessageConverter; -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.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.web.client.RestTemplate; @Configuration @EnableWebSecurity @@ -86,144 +56,10 @@ public AuthorizationManager authorizationManager() { return new SecureAuthorizationManager(); } - @Configuration - @Order(SecurityProperties.BASIC_AUTH_ORDER - 3) - public class OAuthConfigurerAdapter extends WebSecurityConfigurerAdapter { - - final OAuth2AuthorizationRequestResolver oAuth2RequestResolver; - final CustomOAuth2UserInfoService customOAuth2UserInfoService; - final CustomOidc2UserInfoService customOidc2UserInfoService; - final ApplicationService applicationService; - final SSOAuthenticationFailureHandler failureHandler; - - public OAuthConfigurerAdapter( - OAuth2AuthorizationRequestResolver requestResolver, - CustomOAuth2UserInfoService customOAuth2UserInfoService, - CustomOidc2UserInfoService customOidc2UserInfoService, - ApplicationService applicationService, - SSOAuthenticationFailureHandler failureHandler) { - this.oAuth2RequestResolver = requestResolver; - this.customOAuth2UserInfoService = customOAuth2UserInfoService; - this.customOidc2UserInfoService = customOidc2UserInfoService; - this.applicationService = applicationService; - this.failureHandler = failureHandler; - } - - @Bean - public SimpleUrlAuthenticationSuccessHandler successHandler() { - return new SimpleUrlAuthenticationSuccessHandler() { - public void onAuthenticationSuccess( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - val application = - applicationService.getByClientId( - (String) request.getSession().getAttribute("ego_client_id")); - - String redirectUri = (String) request.getSession().getAttribute("ego_redirect_uri"); - - val redirect = Redirects.getRedirectUri(application, redirectUri); - if (!redirect.isEmpty()) { - this.setDefaultTargetUrl(redirect); - super.onAuthenticationSuccess(request, response, authentication); - } else { - throw new OAuth2AuthenticationException( - new OAuth2Error(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT), - "Incorrect redirect uri for ego client."); - } - } - }; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.requestMatchers() - .antMatchers( - "/oauth/code/*", - "/oauth/login/*", - "/oauth/ego-token", - "/oauth/update-ego-token", - "/oauth/refresh") - .and() - .csrf() - .disable() - .authorizeRequests() - .anyRequest() - .permitAll() - .and() - .oauth2Login( - x -> { - x.redirectionEndpoint().baseUri("/oauth/code/{registrationId}"); - x.authorizationEndpoint(y -> y.authorizationRequestResolver(oAuth2RequestResolver)); - x.tokenEndpoint() - .accessTokenResponseClient(this.authorizationCodeTokenResponseClient()); - x.userInfoEndpoint().oidcUserService(this.customOidc2UserInfoService); - x.userInfoEndpoint().userService(customOAuth2UserInfoService); - x.successHandler(this.successHandler()); - x.failureHandler(this.failureHandler); - }); - } - - private OAuth2AccessTokenResponseClient - authorizationCodeTokenResponseClient() { - val tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - tokenResponseHttpMessageConverter.setTokenResponseConverter( - new OAuth2AccessTokenResponseConverterWithDefaults()); - - val restTemplate = - new RestTemplate( - Arrays.asList(new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - - val tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); - tokenResponseClient.setRestOperations(restTemplate); - - return tokenResponseClient; - } - } - @Bean @SneakyThrows - public JWTAuthorizationFilter authorizationFilter( + public JWTAuthorizationFilter authorizationFilterBean( TokenService tokenService, ApplicationService applicationService) { return new JWTAuthorizationFilter(PUBLIC_ENDPOINTS, tokenService, applicationService); } - - @Configuration - @Order(SecurityProperties.BASIC_AUTH_ORDER + 3) - public class AppConfigurerAdapter extends WebSecurityConfigurerAdapter { - - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - new OAuth2AuthorizationServerConfigurer<>(); - @Autowired JWTAuthorizationFilter authorizationFilter; - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.csrf() - .disable() - .apply(authorizationServerConfigurer) - .and() - .authorizeRequests() - .antMatchers( - "/", - "/favicon.ico", - "/swagger**", - "/swagger-resources/**", - "/configuration/ui", - "/configuration/**", - "/v2/api**", - "/webjars/**", - "/actuator/**", - "/oauth/token/verify", - "/oauth/token/public_key") - .permitAll() - .antMatchers(HttpMethod.OPTIONS, "/**") - .permitAll() - .anyRequest() - .authenticated() - .and() - .addFilterBefore(authorizationFilter, BasicAuthenticationFilter.class) - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } - } } diff --git a/src/main/java/bio/overture/ego/config/ServerConfig.java b/src/main/java/bio/overture/ego/config/ServerConfig.java index b34939818..bc1fd8a6c 100644 --- a/src/main/java/bio/overture/ego/config/ServerConfig.java +++ b/src/main/java/bio/overture/ego/config/ServerConfig.java @@ -23,32 +23,34 @@ import org.springframework.context.annotation.Profile; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity @Profile("!auth") -public class ServerConfig extends WebSecurityConfigurerAdapter { +public class ServerConfig { @Bean public AuthorizationManager authorizationManager() { return new DefaultAuthorizationManager(); } - @Override - protected void configure(HttpSecurity http) throws Exception { - http.csrf() + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.csrf() .disable() - .authorizeRequests() - .antMatchers("/**") + .authorizeHttpRequests() + .requestMatchers("/**") .permitAll() .anyRequest() .authenticated() .and() - .authorizeRequests() + .authorizeHttpRequests() .and() .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .build(); } } diff --git a/src/main/java/bio/overture/ego/config/SwaggerConfig.java b/src/main/java/bio/overture/ego/config/SwaggerConfig.java index e42d60779..808b60cdf 100644 --- a/src/main/java/bio/overture/ego/config/SwaggerConfig.java +++ b/src/main/java/bio/overture/ego/config/SwaggerConfig.java @@ -16,58 +16,40 @@ package bio.overture.ego.config; -import static bio.overture.ego.utils.SwaggerConstants.AUTH_CONTROLLER; -import static bio.overture.ego.utils.SwaggerConstants.POST_ACCESS_TOKEN; -import static java.util.stream.Collectors.toUnmodifiableList; -import static springfox.documentation.builders.RequestHandlerSelectors.basePackage; -import static springfox.documentation.spi.DocumentationType.SWAGGER_2; - -import com.fasterxml.classmate.TypeResolver; -import com.google.common.base.Predicates; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import static bio.overture.ego.utils.SwaggerConstants.*; + +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import java.net.URISyntaxException; +import java.util.*; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import lombok.val; +import org.apache.http.client.utils.URIBuilder; +import org.springdoc.core.customizers.OpenApiCustomizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; -import springfox.documentation.builders.ParameterBuilder; -import springfox.documentation.schema.ModelRef; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.ApiKey; -import springfox.documentation.service.AuthorizationScope; -import springfox.documentation.service.Contact; -import springfox.documentation.service.Parameter; -import springfox.documentation.service.SecurityReference; -import springfox.documentation.service.VendorExtension; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spi.service.ParameterBuilderPlugin; -import springfox.documentation.spi.service.contexts.ParameterContext; -import springfox.documentation.spi.service.contexts.SecurityContext; -import springfox.documentation.spring.web.paths.RelativePathProvider; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger.common.SwaggerPluginSupport; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -@EnableSwagger2 + +/** Open API Configuration Bean */ @Configuration public class SwaggerConfig { - private static final Set POST_ACCESS_TOKEN_PARAMS = - Set.of("client_secret", "client_id", "grant_type"); - private static final Set APPLICATION_SCOPED_PATHS = - Set.of( - "/o/check_api_key", - "/o/check_token", - "/transaction/group_permissions", - "/transaction/mass_delete"); - private final BuildProperties buildProperties; @Autowired @@ -76,103 +58,54 @@ public SwaggerConfig(@NonNull BuildProperties buildProperties) { } @Bean - public ParameterBuilderPlugin parameterBuilderPlugin() { - return new ParameterBuilderPlugin() { - @Override - public void apply(ParameterContext context) { - if (context.getGroupName().equals(AUTH_CONTROLLER) - && context.getOperationContext().getName().equals(POST_ACCESS_TOKEN)) { - context - .getOperationContext() - .operationBuilder() - .parameters(generatePostAccessTokenParameters()); - - // hide default "parameters" arg - val defaultName = context.resolvedMethodParameter().defaultName(); - if (defaultName.isPresent() && defaultName.get().equals("parameters")) { - context.parameterBuilder().required(false).hidden(true).build(); - } - } - } - - @Override - public boolean supports(DocumentationType delimiter) { - return SwaggerPluginSupport.pluginDoesApply(delimiter); - } - }; + public OpenAPI productApi(SwaggerProperties swaggerProperties) { + + URIBuilder uriBuilder = null; + try { + uriBuilder = new URIBuilder(swaggerProperties.host); + uriBuilder.setPath(swaggerProperties.baseUrl).build().normalize(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + return new OpenAPI() + .info(metaInfo()) + .servers(List.of(new Server().url(uriBuilder.toString()))) + .components(new Components().addSecuritySchemes(SECURITY_SCHEME_NAME, securityScheme())); } - @Bean - public Docket productApi(SwaggerProperties swaggerProperties) { - return new Docket(SWAGGER_2) - .select() - .apis(Predicates.or(basePackage("bio.overture.ego.controller"))) - .build() - .host(swaggerProperties.host) - .pathProvider( - new RelativePathProvider(null) { - @Override - public String getApplicationBasePath() { - return swaggerProperties.getBaseUrl(); - } - }) - .securitySchemes(List.of(apiKey())) - .securityContexts(List.of(securityContext())) - .apiInfo(metaInfo()) - .produces(Set.of("application/json")) - .consumes(Set.of("application/json")); - } - - private ApiInfo metaInfo() { - - return new ApiInfo( - "Ego Service API", - "Ego API Documentation", - buildProperties.getVersion(), - "", - new Contact("", "", ""), - "GNU Affero General Public License v3.0", - "", - new ArrayList()); - } + private Info metaInfo() { - private static ApiKey apiKey() { - return new ApiKey("Bearer", "Authorization", "header"); + return new Info() + .title("Ego Service API") + .description("Ego API Documentation") + .version(buildProperties.getVersion()) + .contact(new Contact()) + .license(new License().name("GNU Affero General Public License v3.0")); } - private static SecurityContext securityContext() { - return SecurityContext.builder() - .securityReferences(List.of(securityReference())) - // We want the default Bearer auth applied only for non-ApplicationScoped endpoints. - // For ApplicationScoped endpoints, an explicit RequestHeader - // fields will be present in the ui - .forPaths(x -> !isApplicationScopedPath(x)) - .build(); + private static SecurityScheme securityScheme() { + return new SecurityScheme() + .name(SECURITY_SCHEME_NAME) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); } private static boolean isApplicationScopedPath(@NonNull String path) { return APPLICATION_SCOPED_PATHS.contains(path); } - private static SecurityReference securityReference() { - return SecurityReference.builder() - .reference("Bearer") - .scopes(new AuthorizationScope[0]) - .build(); - } - private static List generatePostAccessTokenParameters() { return POST_ACCESS_TOKEN_PARAMS.stream() .map( name -> - new ParameterBuilder() - .type(new TypeResolver().resolve(String.class)) + new Parameter() + .schema(new Schema().type("string")) .name(name) - .parameterType("query") - .required(true) - .modelRef(new ModelRef("String")) - .build()) - .collect(toUnmodifiableList()); + .in(ParameterIn.QUERY.toString()) + .required(true)) + .collect(Collectors.toList()); } @Component @@ -189,4 +122,36 @@ class SwaggerProperties { */ private String baseUrl = ""; } + + @Bean + public OpenApiCustomizer openApiCustomiser() { + return openApi -> { + openApi + .getPaths() + .forEach( + (path, pathItem) -> { + + // We want the default Bearer auth applied only for non-ApplicationScoped endpoints. + // For ApplicationScoped endpoints, an explicit RequestHeader + if (!isApplicationScopedPath(path)) { + pathItem + .readOperations() + .forEach( + operation -> { + operation.addSecurityItem( + new SecurityRequirement().addList(SECURITY_SCHEME_NAME)); + }); + } + }); + + // generate access token parameters + PathItem accessTokenPath = + new PathItem() + .post( + new Operation() + .addTagsItem("Auth") + .parameters(generatePostAccessTokenParameters())); + openApi.getPaths().addPathItem("/oauth/token", accessTokenPath); + }; + } } diff --git a/src/main/java/bio/overture/ego/config/WebRequestConfig.java b/src/main/java/bio/overture/ego/config/WebRequestConfig.java index e99bcd589..3e9a709d4 100644 --- a/src/main/java/bio/overture/ego/config/WebRequestConfig.java +++ b/src/main/java/bio/overture/ego/config/WebRequestConfig.java @@ -24,10 +24,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class WebRequestConfig extends WebMvcConfigurerAdapter { +public class WebRequestConfig implements WebMvcConfigurer { @Bean public List fieldValues() { diff --git a/src/main/java/bio/overture/ego/controller/ApiKeyController.java b/src/main/java/bio/overture/ego/controller/ApiKeyController.java index 9a23b7c94..0f6c84567 100644 --- a/src/main/java/bio/overture/ego/controller/ApiKeyController.java +++ b/src/main/java/bio/overture/ego/controller/ApiKeyController.java @@ -46,12 +46,18 @@ import bio.overture.ego.security.ApplicationScoped; import bio.overture.ego.security.AuthorizationManager; import bio.overture.ego.service.TokenService; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import lombok.NonNull; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -64,12 +70,11 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; @Slf4j @RestController @RequestMapping("/o") -@Api(tags = "Api Keys") +@Tag(name = "Api Keys") public class ApiKeyController { /** Dependencies */ @@ -118,7 +123,7 @@ public ApiKeyController( @ResponseStatus(value = OK) @SneakyThrows public @ResponseBody UserScopesResponse getUserScope( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "userId") final UUID userId) { return tokenService.userScopes(userId); @@ -127,7 +132,7 @@ public ApiKeyController( @RequestMapping(method = POST, value = "/api_key") @ResponseStatus(value = OK) public @ResponseBody ApiKeyResponse issueApiKey( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "user_id") UUID userId, @RequestParam(value = "scopes") ArrayList scopes, @@ -153,7 +158,7 @@ public ApiKeyController( @RequestMapping(method = POST, value = "/token") @ResponseStatus(value = OK) public @ResponseBody TokenResponse issueToken( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "user_id") UUID userId, @RequestParam(value = "scopes") ArrayList scopes, @@ -175,7 +180,7 @@ public ApiKeyController( @RequestMapping(method = DELETE, value = "/api_key") @ResponseStatus(value = OK) public @ResponseBody GenericResponse revokeApiKey( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "apiKey") final String apiKey) { tokenService.revokeApiKey(apiKey); @@ -187,7 +192,7 @@ public ApiKeyController( @RequestMapping(method = DELETE, value = "/token") @ResponseStatus(value = OK) public @ResponseBody String revokeToken( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "token") final String token) { tokenService.revokeApiKey(token); @@ -195,42 +200,43 @@ public ApiKeyController( } @RequestMapping(method = GET, value = "/api_key") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page ApiKeys for a User")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page ApiKeys for a User")}) public @ResponseBody PageDTO listApiKeys( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "user_id") UUID userId, - @ApiParam(value = "Query string compares to ApiKey's Name fields.", required = false) + @Parameter(description = "Query string compares to ApiKey's Name fields.", required = false) @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { checkAdminOrOwner(userId); if (isEmpty(query)) { return new PageDTO<>(tokenService.listApiKeysForUser(userId, filters, pageable)); @@ -244,7 +250,7 @@ public ApiKeyController( @RequestMapping(method = GET, value = "/token") @ResponseStatus(value = OK) public @ResponseBody List listTokens( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "user_id") UUID userId) { checkAdminOrOwner(userId); diff --git a/src/main/java/bio/overture/ego/controller/ApplicationController.java b/src/main/java/bio/overture/ego/controller/ApplicationController.java index cefecf2be..cafe4407f 100644 --- a/src/main/java/bio/overture/ego/controller/ApplicationController.java +++ b/src/main/java/bio/overture/ego/controller/ApplicationController.java @@ -38,11 +38,13 @@ import bio.overture.ego.service.UserService; import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -52,12 +54,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; @Slf4j @RestController @RequestMapping("/applications") -@Api(tags = "Applications") +@Tag(name = "Applications") public class ApplicationController { /** Dependencies */ @@ -81,40 +82,40 @@ public ApplicationController( @AdminScoped @RequestMapping(method = GET, value = "") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Applications")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Page Applications")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO listApplications( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(applicationService.listApps(filters, pageable)); } else { @@ -124,10 +125,9 @@ public ApplicationController( @AdminScoped @RequestMapping(method = POST, value = "") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "New Application", response = Application.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "New Application")}) public @ResponseBody Application createApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestBody(required = true) CreateApplicationRequest request) { return applicationService.create(request); @@ -135,13 +135,10 @@ public ApplicationController( @AdminScoped @RequestMapping(method = GET, value = "/{id}") - @ApiResponses( - value = { - @ApiResponse(code = 200, message = "Application Details", response = Application.class) - }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Application Details")}) @JsonView(Views.REST.class) public @ResponseBody Application getApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { return applicationService.getById(id); @@ -150,11 +147,9 @@ public ApplicationController( @AdminScoped @RequestMapping(method = PUT, value = "/{id}") @ApiResponses( - value = { - @ApiResponse(code = 200, message = "Updated application info", response = Application.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Updated application info")}) public @ResponseBody Application updateApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(name = "id", required = true) UUID id, @RequestBody(required = true) UpdateApplicationRequest updateRequest) { @@ -165,7 +160,7 @@ public ApplicationController( @RequestMapping(method = DELETE, value = "/{id}") @ResponseStatus(value = HttpStatus.OK) public void deleteApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { applicationService.delete(id); @@ -173,41 +168,42 @@ public void deleteApplication( @AdminScoped @RequestMapping(method = GET, value = "/{id}/users") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Users for an Application")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Users for an Application")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getUsersForApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(userService.findUsersForApplication(id, filters, pageable)); } else { @@ -217,41 +213,42 @@ public void deleteApplication( @AdminScoped @RequestMapping(method = GET, value = "/{id}/groups") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Groups for an Application")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Groups for an Application")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getGroupsForApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(groupService.findGroupsForApplication(id, filters, pageable)); } else { @@ -264,56 +261,51 @@ public void deleteApplication( */ @AdminScoped @RequestMapping(method = GET, value = "/{id}/permissions") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) @ApiResponses( value = { - @ApiResponse(code = 200, message = "Page Permissions for an Application"), + @ApiResponse(responseCode = "200", description = "Page Permissions for an Application"), }) @JsonView(Views.REST.class) public @ResponseBody PageDTO getPermissionsForApplication( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) Pageable pageable) { return new PageDTO<>(applicationPermissionService.getPermissions(id, pageable)); } @AdminScoped @RequestMapping(method = POST, value = "/{id}/permissions") @ApiResponses( - value = { - @ApiResponse( - code = 200, - message = "Add application permissions", - response = Application.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Add application permissions")}) public @ResponseBody Application addPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List permissions) { @@ -322,10 +314,11 @@ public void deleteApplication( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/permissions/{permissionIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete application permissions")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete application permissions")}) @ResponseStatus(value = OK) public void deletePermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "permissionIds", required = true) List permissionIds) { @@ -337,13 +330,13 @@ public void deletePermissions( @ApiResponses( value = { @ApiResponse( - code = 200, - message = + responseCode = "200", + description = "Get effective permissions for an application with application and group permissions") }) @ResponseStatus(value = HttpStatus.OK) public @ResponseBody Collection getResolvedPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { return applicationPermissionService.getResolvedPermissions(id); diff --git a/src/main/java/bio/overture/ego/controller/AuthController.java b/src/main/java/bio/overture/ego/controller/AuthController.java index 927b189ce..ae538af37 100644 --- a/src/main/java/bio/overture/ego/controller/AuthController.java +++ b/src/main/java/bio/overture/ego/controller/AuthController.java @@ -18,6 +18,7 @@ import static bio.overture.ego.model.enums.JavaFields.REFRESH_ID; import static bio.overture.ego.utils.SwaggerConstants.AUTH_CONTROLLER; +import static bio.overture.ego.utils.TypeUtils.isValidUUID; import static org.springframework.http.HttpStatus.*; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.*; @@ -27,14 +28,15 @@ import bio.overture.ego.model.exceptions.InvalidTokenException; import bio.overture.ego.provider.google.GoogleTokenService; import bio.overture.ego.security.CustomOAuth2User; -import bio.overture.ego.service.RefreshContextService; -import bio.overture.ego.service.TokenService; +import bio.overture.ego.service.*; import bio.overture.ego.token.IDToken; import bio.overture.ego.token.signer.TokenSigner; import bio.overture.ego.utils.Tokens; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import java.util.Objects; -import javax.servlet.http.HttpServletResponse; +import java.util.Optional; import lombok.NonNull; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -47,32 +49,39 @@ import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; +import org.springframework.web.client.HttpClientErrorException; @Slf4j @RestController @RequestMapping("/oauth") -@Api(tags = "Auth", value = AUTH_CONTROLLER) +@Tag(name = "Auth", description = AUTH_CONTROLLER) public class AuthController { @Value("${auth.token.prefix}") private String TOKEN_PREFIX; private final TokenService tokenService; + private final PassportService passportService; private final GoogleTokenService googleTokenService; private final TokenSigner tokenSigner; private final RefreshContextService refreshContextService; + private final UserService userService; + private final String GA4GH_PASSPORT_SCOPE = "ga4gh_passport_v1"; @Autowired public AuthController( @NonNull TokenService tokenService, + @NonNull PassportService passportService, @NonNull GoogleTokenService googleTokenService, @NonNull TokenSigner tokenSigner, - @NonNull RefreshContextService refreshContextService) { + @NonNull RefreshContextService refreshContextService, + @NonNull UserService userService) { this.tokenService = tokenService; + this.passportService = passportService; this.googleTokenService = googleTokenService; this.tokenSigner = tokenSigner; this.refreshContextService = refreshContextService; + this.userService = userService; } @RequestMapping(method = GET, value = "/google/token") @@ -120,33 +129,61 @@ public ResponseEntity user( throw new RuntimeException("no user"); } - val user = (CustomOAuth2User) authentication.getPrincipal(); - String token = + val oAuth2User = (CustomOAuth2User) authentication.getPrincipal(); + + val passportJwtToken = + (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null) + ? passportService.getPassportToken( + authentication.getAuthorizedClientRegistrationId(), oAuth2User.getAccessToken()) + : null; + + Optional providerType = + ProviderType.findIfExist(authentication.getAuthorizedClientRegistrationId()); + + if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && providerType.isEmpty()) { + providerType = Optional.of(ProviderType.PASSPORT); + } + + val idToken = + IDToken.builder() + .providerSubjectId(oAuth2User.getSubjectId()) + .email(oAuth2User.getEmail()) + .familyName(oAuth2User.getFamilyName()) + .givenName(oAuth2User.getGivenName()) + .providerType(providerType.get()) + .providerIssuerUri(oAuth2User.getIssuer().toString()) + .build(); + + val egoToken = tokenService.generateUserToken( - IDToken.builder() - .providerSubjectId(user.getSubjectId()) - .email(user.getEmail()) - .familyName(user.getFamilyName()) - .givenName(user.getGivenName()) - .providerType( - ProviderType.resolveProviderType( - authentication.getAuthorizedClientRegistrationId())) - .build()); - - val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(token); - val cookie = - refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken()); - response.addCookie(cookie); + idToken, passportJwtToken, authentication.getAuthorizedClientRegistrationId()); + + if (oAuth2User.getClaim(GA4GH_PASSPORT_SCOPE) != null && oAuth2User.getRefreshToken() != null) { + // create a cookie with passport refresh token + val user = userService.getUserByToken(idToken); + val outgoingRefreshContext = + refreshContextService.createPassportRefreshToken(user, oAuth2User.getRefreshToken()); + val cookie = + refreshContextService.createPassportRefreshCookie( + outgoingRefreshContext, oAuth2User.getRefreshToken()); + response.addCookie(cookie); + } else { + // create a cookie with refreshId + val outgoingRefreshContext = refreshContextService.createInitialRefreshContext(egoToken); + val cookie = + refreshContextService.createRefreshCookie(outgoingRefreshContext.getRefreshToken()); + response.addCookie(cookie); + } SecurityContextHolder.getContext().setAuthentication(null); - return new ResponseEntity<>(token, OK); + return new ResponseEntity<>(egoToken, OK); } @RequestMapping( method = {GET, POST}, value = "/update-ego-token") public ResponseEntity updateEgoToken( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization) { val currentToken = Tokens.removeTokenPrefix(authorization, TOKEN_PREFIX); return new ResponseEntity<>(tokenService.updateUserToken(currentToken), OK); @@ -154,7 +191,7 @@ public ResponseEntity updateEgoToken( @RequestMapping(method = DELETE, value = "/refresh") public ResponseEntity deleteRefreshToken( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @CookieValue(value = REFRESH_ID, defaultValue = "missing") String refreshId, HttpServletResponse response) { @@ -170,7 +207,7 @@ public ResponseEntity deleteRefreshToken( @RequestMapping(method = POST, value = "/refresh") public ResponseEntity refreshEgoToken( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @CookieValue(value = REFRESH_ID, defaultValue = "missing") String refreshId, HttpServletResponse response) { @@ -178,15 +215,41 @@ public ResponseEntity refreshEgoToken( return new ResponseEntity<>("Please login", UNAUTHORIZED); } val currentToken = Tokens.removeTokenPrefix(authorization, TOKEN_PREFIX); - // TODO: [anncatton] validate jwt before proceeding to service call. - val outboundUserToken = - refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken); - val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken(); - val newCookie = refreshContextService.createRefreshCookie(newRefreshToken); - response.addCookie(newCookie); + try { + if (isValidUUID(refreshId)) { + val outboundUserToken = + refreshContextService.validateAndReturnNewUserToken(refreshId, currentToken); + val newRefreshToken = tokenService.getTokenUserInfo(outboundUserToken).getRefreshToken(); + val newCookie = refreshContextService.createRefreshCookie(newRefreshToken); + response.addCookie(newCookie); - return new ResponseEntity<>(outboundUserToken, OK); + return new ResponseEntity<>(outboundUserToken, OK); + } else { + + val user = tokenService.getTokenUserInfo(currentToken); + + val clientRegistration = + passportService.getPassportClientRegistrations().get(user.getProviderIssuerUri()); + + val passportResponse = + passportService.refreshToken(clientRegistration.getRegistrationId(), refreshId); + + val egoToken = tokenService.generatePassportEgoToken(user, passportResponse.getAccess_token(), clientRegistration.getRegistrationId()); + + val outgoingRefreshContext = + refreshContextService.createPassportRefreshToken( + user, passportResponse.getRefresh_token()); + val newCookie = + refreshContextService.createPassportRefreshCookie( + outgoingRefreshContext, passportResponse.getRefresh_token()); + response.addCookie(newCookie); + + return new ResponseEntity<>(egoToken, OK); + } + }catch (HttpClientErrorException e){ + return new ResponseEntity<>(e.getResponseBodyAsString(), e.getStatusCode()); + } } @ExceptionHandler({InvalidTokenException.class}) diff --git a/src/main/java/bio/overture/ego/controller/GroupController.java b/src/main/java/bio/overture/ego/controller/GroupController.java index 5edcfc24e..c0088bca9 100644 --- a/src/main/java/bio/overture/ego/controller/GroupController.java +++ b/src/main/java/bio/overture/ego/controller/GroupController.java @@ -43,11 +43,13 @@ import bio.overture.ego.service.UserService; import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.NonNull; @@ -64,12 +66,11 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import springfox.documentation.annotations.ApiIgnore; @Slf4j @RestController @RequestMapping("/groups") -@Api(tags = "Groups") +@Tag(name = "Groups") public class GroupController { /** Dependencies */ @@ -95,40 +96,40 @@ public GroupController( @AdminScoped @RequestMapping(method = GET, value = "") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Groups")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Page Groups")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO listGroups( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(groupService.listGroups(filters, pageable)); } else { @@ -140,10 +141,10 @@ public GroupController( @RequestMapping(method = POST, value = "") @ApiResponses( value = { - @ApiResponse(code = 200, message = "New Group", response = Group.class), + @ApiResponse(responseCode = "200", description = "New Group"), }) public @ResponseBody Group createGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestBody GroupRequest createRequest) { return groupService.create(createRequest); @@ -151,11 +152,10 @@ public GroupController( @AdminScoped @RequestMapping(method = GET, value = "/{id}") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Group Details", response = Group.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Group Details")}) @JsonView(Views.REST.class) public @ResponseBody Group getGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id") UUID id) { return groupService.getById(id); @@ -163,10 +163,9 @@ public GroupController( @AdminScoped @RequestMapping(method = RequestMethod.PUT, value = "/{id}") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Updated group info", response = Group.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Updated group info")}) public @ResponseBody Group updateGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id") UUID id, @RequestBody(required = true) GroupRequest updateRequest) { @@ -177,7 +176,7 @@ public GroupController( @RequestMapping(method = DELETE, value = "/{id}") @ResponseStatus(value = OK) public void deleteGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { groupService.delete(id); @@ -188,57 +187,56 @@ public void deleteGroup( */ @AdminScoped @RequestMapping(method = GET, value = "/{id}/permissions") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = true, - dataType = "string", - paramType = "path", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) @ApiResponses( value = { - @ApiResponse(code = 200, message = "Page GroupPermissions for a Group"), + @ApiResponse(responseCode = "200", description = "Page GroupPermissions for a Group"), }) @JsonView(Views.REST.class) public @ResponseBody PageDTO getGroupPermissionsForGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) Pageable pageable) { return new PageDTO<>(groupPermissionService.getPermissions(id, pageable)); } @AdminScoped @RequestMapping(method = POST, value = "/{id}/permissions") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add group permissions", response = Group.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Add group permissions")}) public @ResponseBody Group addPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List permissions) { @@ -247,10 +245,11 @@ public void deleteGroup( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/permissions/{permissionIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete group permissions")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete group permissions")}) @ResponseStatus(value = OK) public void deletePermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "permissionIds", required = true) List permissionIds) { @@ -262,47 +261,48 @@ public void deletePermissions( */ @AdminScoped @RequestMapping(method = GET, value = "/{id}/applications") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = true, - dataType = "string", - paramType = "path", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Applications for a Group")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Applications for a Group")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getApplicationsForGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (StringUtils.isEmpty(query)) { return new PageDTO<>(applicationService.findApplicationsForGroup(id, filters, pageable)); } else { @@ -313,10 +313,9 @@ public void deletePermissions( @AdminScoped @RequestMapping(method = POST, value = "/{id}/applications") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add Apps to Group", response = Group.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Add Apps to Group")}) public @ResponseBody Group addApplicationsToGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List appIds) { @@ -325,10 +324,11 @@ public void deletePermissions( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/applications/{appIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete Apps from Group")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete Apps from Group")}) @ResponseStatus(value = OK) public void deleteApplicationsFromGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "appIds", required = true) List appIds) { @@ -340,47 +340,48 @@ public void deleteApplicationsFromGroup( */ @AdminScoped @RequestMapping(method = GET, value = "/{id}/users") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = true, - dataType = "string", - paramType = "path", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Users for a Group")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Users for a Group")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getUsersForGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (StringUtils.isEmpty(query)) { return new PageDTO<>(userService.findUsersForGroup(id, filters, pageable)); } else { @@ -390,10 +391,9 @@ public void deleteApplicationsFromGroup( @AdminScoped @RequestMapping(method = POST, value = "/{id}/users") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add Users to Group", response = Group.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Add Users to Group")}) public @ResponseBody Group addUsersToGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List userIds) { @@ -402,10 +402,11 @@ public void deleteApplicationsFromGroup( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/users/{userIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete Users from Group")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete Users from Group")}) @ResponseStatus(value = OK) public void deleteUsersFromGroup( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "userIds", required = true) List userIds) { diff --git a/src/main/java/bio/overture/ego/controller/PolicyController.java b/src/main/java/bio/overture/ego/controller/PolicyController.java index 7493542e5..ca7590e47 100644 --- a/src/main/java/bio/overture/ego/controller/PolicyController.java +++ b/src/main/java/bio/overture/ego/controller/PolicyController.java @@ -28,7 +28,14 @@ import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; import com.google.common.collect.ImmutableList; -import io.swagger.annotations.*; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.NonNull; @@ -38,12 +45,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; @Slf4j @RestController @RequestMapping("/policies") -@Api(tags = "Policies") +@Tag(name = "Policies") public class PolicyController { /** Dependencies */ @@ -67,11 +73,10 @@ public PolicyController( @AdminScoped @RequestMapping(method = GET, value = "/{id}") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Get policy by id", response = Policy.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Get policy by id")}) @JsonView(Views.REST.class) public @ResponseBody Policy getPolicy( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { return policyService.getById(id); @@ -79,51 +84,51 @@ public PolicyController( @AdminScoped @RequestMapping(method = GET, value = "") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = false, - dataType = "string", - paramType = "query", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = Fields.NAME, required = false, - dataType = "string", - paramType = "query", - value = "Search for policies whose names contain this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for policies whose names contain this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Policies")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Page Policies")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO listPolicies( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { return new PageDTO<>(policyService.listPolicies(filters, pageable)); } @@ -131,10 +136,10 @@ public PolicyController( @RequestMapping(method = POST, value = "") @ApiResponses( value = { - @ApiResponse(code = 200, message = "New Policy", response = Policy.class), + @ApiResponse(responseCode = "200", description = "New Policy"), }) public @ResponseBody Policy createPolicy( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @RequestBody(required = true) PolicyRequest createRequest) { return policyService.create(createRequest); @@ -142,10 +147,9 @@ public PolicyController( @AdminScoped @RequestMapping(method = PUT, value = "/{id}") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Updated Policy", response = Policy.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Updated Policy")}) public @ResponseBody Policy updatePolicy( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id") UUID id, @RequestBody(required = true) PolicyRequest updatedRequst) { @@ -156,7 +160,7 @@ public PolicyController( @RequestMapping(method = DELETE, value = "/{id}") @ResponseStatus(value = HttpStatus.OK) public void deletePolicy( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { policyService.delete(id); @@ -165,10 +169,15 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = POST, value = "/{id}/permission/group/{group_id}") @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add group permission", response = String.class)}) + value = { + @ApiResponse( + responseCode = "200", + description = "Add group permission", + content = @Content(schema = @Schema(implementation = String.class))) + }) @JsonView(Views.REST.class) public @ResponseBody Group createGroupPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "group_id", required = true) UUID groupId, @@ -180,14 +189,9 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/permission/group/{group_id}") @ApiResponses( - value = { - @ApiResponse( - code = 200, - message = "Delete group permission", - response = GenericResponse.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Delete group permission")}) public @ResponseBody GenericResponse deleteGroupPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "group_id", required = true) UUID groupId) { @@ -198,9 +202,14 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = POST, value = "/{id}/permission/user/{user_id}") @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add user permission", response = String.class)}) + value = { + @ApiResponse( + responseCode = "200", + description = "Add user permission", + content = @Content(schema = @Schema(implementation = String.class))) + }) public @ResponseBody User createUserPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "user_id", required = true) UUID userId, @@ -212,14 +221,9 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/permission/user/{user_id}") @ApiResponses( - value = { - @ApiResponse( - code = 200, - message = "Delete group permission", - response = GenericResponse.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Delete group permission")}) public @ResponseBody GenericResponse deleteUserPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "user_id", required = true) UUID userId) { @@ -232,10 +236,13 @@ public void deletePolicy( @RequestMapping(method = POST, value = "/{id}/permission/application/{application_id}") @ApiResponses( value = { - @ApiResponse(code = 200, message = "Add application permission", response = String.class) + @ApiResponse( + responseCode = "200", + description = "Add application permission", + content = @Content(schema = @Schema(implementation = String.class))) }) public @ResponseBody Application createApplicationPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "application_id", required = true) UUID applicationId, @@ -247,14 +254,9 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = DELETE, value = "/{id}/permission/application/{application_id}") @ApiResponses( - value = { - @ApiResponse( - code = 200, - message = "Delete application permission", - response = GenericResponse.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Delete application permission")}) public @ResponseBody GenericResponse deleteApplicationPermission( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "application_id", required = true) UUID applicationId) { @@ -265,49 +267,51 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = GET, value = "/{id}/users") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC. Note: ascending sort order for the mask field is: READ,WRITE,DENY"), }) @ApiResponses( value = { @ApiResponse( - code = 200, - message = "Get list of user ids with given policy id", - response = String.class) + responseCode = "200", + description = "Get list of user ids with given policy id", + content = @Content(schema = @Schema(implementation = String.class))) }) public @ResponseBody PageDTO findUserIds( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiParam(value = "Query string compares to AccessLevel and user Id field.", required = false) + @Parameter( + description = "Query string compares to AccessLevel and user Id field.", + required = false) @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { val decoratedPageable = new IgnoreCaseSortDecorator(pageable); if (isEmpty(query)) { return new PageDTO<>( @@ -320,51 +324,51 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = GET, value = "/{id}/groups") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC. Note: ascending sort order for the mask field is: READ,WRITE,DENY"), }) @ApiResponses( value = { @ApiResponse( - code = 200, - message = "Get list of group ids with given policy id", - response = String.class) + responseCode = "200", + description = "Get list of group ids with given policy id", + content = @Content(schema = @Schema(implementation = String.class))) }) public @ResponseBody PageDTO findGroupIds( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiParam( - value = "Query string compares to AccessLevel and group Id and Name fields.", + @Parameter( + description = "Query string compares to AccessLevel and group Id and Name fields.", required = false) @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { val decoratedPageable = new IgnoreCaseSortDecorator(pageable); if (isEmpty(query)) { return new PageDTO( @@ -378,51 +382,52 @@ public void deletePolicy( @AdminScoped @RequestMapping(method = GET, value = "/{id}/applications") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC. Note: ascending sort order for the mask field is: READ,WRITE,DENY"), }) @ApiResponses( value = { @ApiResponse( - code = 200, - message = "Get list of application ids with given policy id", - response = String.class) + responseCode = "200", + description = "Get list of application ids with given policy id", + content = @Content(schema = @Schema(implementation = String.class))) }) public @ResponseBody PageDTO findApplicationIds( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiParam( - value = "Query string compares to AccessLevel and Application Id and Name fields.", + @Parameter( + description = + "Query string compares to AccessLevel and Application Id and Name fields.", required = false) @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { val decoratedPageable = new IgnoreCaseSortDecorator(pageable); if (isEmpty(query)) { return new PageDTO( diff --git a/src/main/java/bio/overture/ego/controller/TransactionController.java b/src/main/java/bio/overture/ego/controller/TransactionController.java index 435b15925..14e635151 100644 --- a/src/main/java/bio/overture/ego/controller/TransactionController.java +++ b/src/main/java/bio/overture/ego/controller/TransactionController.java @@ -33,11 +33,11 @@ import bio.overture.ego.service.GroupService; import bio.overture.ego.service.PolicyService; import com.google.common.collect.ImmutableList; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.util.List; import java.util.Optional; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -52,7 +52,7 @@ @RestController @Transactional @RequestMapping("/transaction") -@Api(tags = "Transactions") +@Tag(name = "Transactions") public class TransactionController { PolicyService policyService; GroupService groupService; diff --git a/src/main/java/bio/overture/ego/controller/UserController.java b/src/main/java/bio/overture/ego/controller/UserController.java index 7152047d0..22b8e55a5 100644 --- a/src/main/java/bio/overture/ego/controller/UserController.java +++ b/src/main/java/bio/overture/ego/controller/UserController.java @@ -31,12 +31,13 @@ import bio.overture.ego.service.*; import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -54,12 +55,11 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import springfox.documentation.annotations.ApiIgnore; @Slf4j @RestController @RequestMapping("/users") -@Api(tags = "Users") +@Tag(name = "Users") public class UserController { /** Dependencies */ @@ -83,51 +83,51 @@ public UserController( @AdminScoped @RequestMapping(method = RequestMethod.GET, value = "") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = false, - dataType = "string", - paramType = "query", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Users")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Page Users")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO listUsers( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, - @ApiParam( - value = + @Parameter( + description = "Query string compares to Users Email, First Name, Last Name, Status and ProviderType fields.", required = false) @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(userService.listUsers(filters, pageable)); } else { @@ -137,10 +137,10 @@ public UserController( @AdminScoped @RequestMapping(method = RequestMethod.GET, value = "/{id}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "User Details", response = User.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "User Details")}) @JsonView(Views.REST.class) public @ResponseBody User getUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { return userService.getById(id); @@ -151,12 +151,11 @@ public UserController( @ApiResponses( value = { @ApiResponse( - code = 200, - message = "Partially update using non-null user info", - response = User.class) + responseCode = "200", + description = "Partially update using non-null user info") }) public @ResponseBody User updateUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) UpdateUserRequest updateUserRequest) { @@ -167,7 +166,7 @@ public UserController( @RequestMapping(method = RequestMethod.DELETE, value = "/{id}") @ResponseStatus(value = HttpStatus.OK) public void deleteUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { userService.delete(id); @@ -178,48 +177,50 @@ public void deleteUser( */ @AdminScoped @RequestMapping(method = RequestMethod.GET, value = "/{id}/permissions") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page User Permissions for a User")}) + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "Page User Permissions for a User") + }) @JsonView(Views.REST.class) public @ResponseBody PageDTO getPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) Pageable pageable) { return new PageDTO<>(userPermissionService.getPermissions(id, pageable)); } @AdminScoped @RequestMapping(method = RequestMethod.POST, value = "/{id}/permissions") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add user permissions", response = User.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Add user permissions")}) public @ResponseBody User addPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List permissions) { @@ -228,10 +229,11 @@ public void deleteUser( @AdminScoped @RequestMapping(method = RequestMethod.DELETE, value = "/{id}/permissions/{permissionIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete User permissions")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete User permissions")}) @ResponseStatus(value = HttpStatus.OK) public void deletePermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "permissionIds", required = true) List permissionIds) { @@ -243,12 +245,12 @@ public void deletePermissions( @ApiResponses( value = { @ApiResponse( - code = 200, - message = "Get effective permissions for a user with user and group permissions") + responseCode = "200", + description = "Get effective permissions for a user with user and group permissions") }) @ResponseStatus(value = HttpStatus.OK) public @ResponseBody Collection getResolvedPermissions( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id) { return userPermissionService.getResolvedPermissions(id); @@ -259,47 +261,48 @@ public void deletePermissions( */ @AdminScoped @RequestMapping(method = RequestMethod.GET, value = "/{id}/groups") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = true, - dataType = "string", - paramType = "path", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Groups for a User")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Groups for a User")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getGroupsFromUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(groupService.findGroupsForUser(id, filters, pageable)); } else { @@ -309,10 +312,9 @@ public void deletePermissions( @AdminScoped @RequestMapping(method = RequestMethod.POST, value = "/{id}/groups") - @ApiResponses( - value = {@ApiResponse(code = 200, message = "Add Groups to user", response = User.class)}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Add Groups to user")}) public @ResponseBody User addGroupsToUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List groupIds) { @@ -321,10 +323,11 @@ public void deletePermissions( @AdminScoped @RequestMapping(method = RequestMethod.DELETE, value = "/{id}/groups/{groupIDs}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete Groups from User")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete Groups from User")}) @ResponseStatus(value = HttpStatus.OK) public void deleteGroupsFromUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "groupIDs", required = true) List groupIds) { @@ -336,47 +339,48 @@ public void deleteGroupsFromUser( */ @AdminScoped @RequestMapping(method = RequestMethod.GET, value = "/{id}/applications") - @ApiImplicitParams({ - @ApiImplicitParam( + @Parameters({ + @Parameter( name = Fields.ID, required = true, - dataType = "string", - paramType = "path", - value = "Search for ids containing this text"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Search for ids containing this text"), + @Parameter( name = LIMIT, required = false, - dataType = "string", - paramType = "query", - value = "Number of results to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Number of results to retrieve"), + @Parameter( name = OFFSET, required = false, - dataType = "string", - paramType = "query", - value = "Index of first result to retrieve"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Index of first result to retrieve"), + @Parameter( name = SORT, required = false, - dataType = "string", - paramType = "query", - value = "Field to sort on"), - @ApiImplicitParam( + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Field to sort on"), + @Parameter( name = SORTORDER, required = false, - dataType = "string", - paramType = "query", - value = "Sorting order: ASC|DESC. Default order: DESC"), + schema = @Schema(type = "string"), + in = ParameterIn.QUERY, + description = "Sorting order: ASC|DESC. Default order: DESC"), }) - @ApiResponses(value = {@ApiResponse(code = 200, message = "Page Applications for a User")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Page Applications for a User")}) @JsonView(Views.REST.class) public @ResponseBody PageDTO getApplicationsFromUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestParam(value = "query", required = false) String query, - @ApiIgnore @Filters List filters, - @ApiIgnore Pageable pageable) { + @Parameter(hidden = true) @Filters List filters, + @Parameter(hidden = true) Pageable pageable) { if (isEmpty(query)) { return new PageDTO<>(applicationService.findApplicationsForUser(id, filters, pageable)); } else { @@ -388,11 +392,9 @@ public void deleteGroupsFromUser( @AdminScoped @RequestMapping(method = RequestMethod.POST, value = "/{id}/applications") @ApiResponses( - value = { - @ApiResponse(code = 200, message = "Add Applications to User", response = User.class) - }) + value = {@ApiResponse(responseCode = "200", description = "Add Applications to User")}) public @ResponseBody User addApplicationsToUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @RequestBody(required = true) List applicationIds) { @@ -401,10 +403,11 @@ public void deleteGroupsFromUser( @AdminScoped @RequestMapping(method = RequestMethod.DELETE, value = "/{id}/applications/{applicationIds}") - @ApiResponses(value = {@ApiResponse(code = 200, message = "Delete Applications from User")}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Delete Applications from User")}) @ResponseStatus(value = HttpStatus.OK) public void deleteApplicationsFromUser( - @ApiIgnore @RequestHeader(value = "Authorization", required = true) + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) final String authorization, @PathVariable(value = "id", required = true) UUID id, @PathVariable(value = "applicationIds", required = true) List applicationIds) { diff --git a/src/main/java/bio/overture/ego/controller/VisaController.java b/src/main/java/bio/overture/ego/controller/VisaController.java new file mode 100644 index 000000000..0217dd9b0 --- /dev/null +++ b/src/main/java/bio/overture/ego/controller/VisaController.java @@ -0,0 +1,240 @@ +package bio.overture.ego.controller; + +import static java.lang.String.format; +import static org.springframework.web.bind.annotation.RequestMethod.*; + +import bio.overture.ego.model.dto.PageDTO; +import bio.overture.ego.model.dto.VisaPermissionRequest; +import bio.overture.ego.model.dto.VisaRequest; +import bio.overture.ego.model.entity.Visa; +import bio.overture.ego.model.entity.VisaPermission; +import bio.overture.ego.model.exceptions.NotFoundException; +import bio.overture.ego.security.AdminScoped; +import bio.overture.ego.service.VisaPermissionService; +import bio.overture.ego.service.VisaService; +import bio.overture.ego.view.Views; +import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/visas") +@Tag(name = "Visas") +public class VisaController { + + /** Dependencies */ + private final VisaService visaService; + + private final VisaPermissionService visaPermissionService; + + @Autowired + public VisaController( + @NonNull VisaService visaService, @NotNull VisaPermissionService visaPermissionService) { + this.visaService = visaService; + this.visaPermissionService = visaPermissionService; + } + + /* + * This method is used to fetch visa using type and value + * @param type String + * @param value String + * @return visas List + */ + @AdminScoped + @RequestMapping(method = GET, value = "/{type}/{value}") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Get Visa Using Type and Value")}) + @JsonView(Views.REST.class) + public @ResponseBody List getVisaByTypeAndValue( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "type", required = true) String type, + @PathVariable(value = "value", required = true) String value) { + List visas = visaService.getByTypeAndValue(type, value); + if (visas != null && !visas.isEmpty()) { + return visas; + } else { + throw new NotFoundException( + format("No Visa exists with type '%s' and value '%s'", type, value)); + } + } + + @AdminScoped + @RequestMapping(method = GET, value = "/{visaId}") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Get Visa Using visa Id")}) + @JsonView(Views.REST.class) + public @ResponseBody Visa getVisaByTypeAndValue( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "visaId", required = true) UUID visaId) { + val visa = visaService.getById(visaId); + if (visa != null) { + return visa; + } else { + throw new NotFoundException( + format("No Visa exists with id '%s'", visaId)); + } + } + + /* + * This method is used to list all visas + * @return visas List + */ + @AdminScoped + @RequestMapping(method = GET, value = "") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "All Visas")}) + @JsonView(Views.REST.class) + public @ResponseBody PageDTO listVisa( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @Parameter(hidden = true) Pageable pageable) { + return new PageDTO<>(visaService.listVisa(pageable)); + } + + /* + * This method is used to create visa using visa create request + * @param visaRequest VisaRequest + * @return Visa visa + */ + @AdminScoped + @RequestMapping(method = POST, value = "") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "New Visa"), + }) + public @ResponseBody Visa createVisa( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @RequestBody(required = true) VisaRequest visaRequest) { + return visaService.create(visaRequest); + } + + /* + * This method is used to update visa using visa id and update request + * @param visaId UUID + * @param visaRequest VisaRequest + * @return Visa visa + */ + @AdminScoped + @RequestMapping(method = PUT, value = "/{visaId}") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Update Visa")}) + public @ResponseBody Visa updateVisa( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "visaId", required = true) UUID visaId, + @RequestBody(required = true) VisaRequest visaRequest) { + return visaService.update(visaId, visaRequest); + } + + /* + * This method is used to delete visa using visa id + * @param visaId UUID + */ + @AdminScoped + @RequestMapping(method = DELETE, value = "/{visaId}") + @ResponseStatus(value = HttpStatus.OK) + public void deleteVisa( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "visaId", required = true) UUID visaId) { + visaService.delete(visaId); + } + + /* + * This method is used to fetch visa permissions using type and value + * @param type String + * @param value String + * @return visas List + */ + @AdminScoped + @RequestMapping(method = GET, value = "/permissions/{type}/{value}") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Get Visa Using Type and Value")}) + @JsonView(Views.REST.class) + public @ResponseBody List getVisaPermissionsByTypeAndValue( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "type", required = true) String type, + @PathVariable(value = "value", required = true) String value) { + return visaPermissionService.getPermissionsForVisa(visaService.getByTypeAndValue(type, value)); + } + + @AdminScoped + @RequestMapping(method = GET, value = "/permissions/{visaId}") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Get Visa using visa id")}) + @JsonView(Views.REST.class) + public @ResponseBody List getVisaPermissionsByVisaId( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "visaId", required = true) UUID visaId) { + val visa = visaService.getById(visaId); + return visaPermissionService.getPermissionsForVisa(Arrays.asList(visa)); + } + + /* + * This method is used to fetch visa permissions using policy id + * @param policyId UUID + * @return visaPermissions List + */ + @AdminScoped + @RequestMapping(method = GET, value = "/permissions/policyId/{id}") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Get VisaPermissions by policyId")}) + @JsonView(Views.REST.class) + public @ResponseBody List getPermissionsByPolicyId( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "id", required = true) UUID id) { + return visaPermissionService.getPermissionsByPolicyId(id); + } + + /* + * This method is used to create/update visa permissions + * @param visaPermissionRequest VisaPermissionRequest + * @return visaPermission VisaPermission + */ + @AdminScoped + @RequestMapping(method = POST, value = "/permissions") + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Create or Update VisaPermission")}) + @JsonView(Views.REST.class) + public @ResponseBody VisaPermission createOrUpdatePermissions( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @RequestBody(required = true) VisaPermissionRequest visaPermissionRequest) { + return visaPermissionService.createOrUpdatePermissions(visaPermissionRequest); + } + + /* + * This method is used to delete/remove visa permissions + * @param visaId UUID + * @param policyId UUID + */ + @AdminScoped + @RequestMapping(method = DELETE, value = "/{visaId}/permissions/{policyId}") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Remove Visa Permission")}) + @JsonView(Views.REST.class) + public @ResponseBody void removePermissions( + @Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true) + final String authorization, + @PathVariable(value = "visaId", required = true) UUID visaId, + @PathVariable(value = "policyId", required = true) UUID policyId) { + visaPermissionService.removePermission(policyId, visaId); + } +} diff --git a/src/main/java/bio/overture/ego/model/dto/CreateApplicationRequest.java b/src/main/java/bio/overture/ego/model/dto/CreateApplicationRequest.java index 62387df73..a03c92c6f 100644 --- a/src/main/java/bio/overture/ego/model/dto/CreateApplicationRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/CreateApplicationRequest.java @@ -18,7 +18,7 @@ import bio.overture.ego.model.enums.ApplicationType; import bio.overture.ego.model.enums.StatusType; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/CreateTokenRequest.java b/src/main/java/bio/overture/ego/model/dto/CreateTokenRequest.java index e9e19c360..1d8828065 100644 --- a/src/main/java/bio/overture/ego/model/dto/CreateTokenRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/CreateTokenRequest.java @@ -1,7 +1,7 @@ package bio.overture.ego.model.dto; +import jakarta.validation.constraints.NotNull; import java.util.Date; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/CreateUserRequest.java b/src/main/java/bio/overture/ego/model/dto/CreateUserRequest.java index fba667fab..1c88eb928 100644 --- a/src/main/java/bio/overture/ego/model/dto/CreateUserRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/CreateUserRequest.java @@ -20,7 +20,7 @@ import bio.overture.ego.model.enums.ProviderType; import bio.overture.ego.model.enums.StatusType; import bio.overture.ego.model.enums.UserType; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -47,4 +47,6 @@ public class CreateUserRequest { @NotNull ProviderType providerType; @NotNull String providerSubjectId; + + private String providerIssuerUri; } diff --git a/src/main/java/bio/overture/ego/model/dto/Ga4ghVisaV1.java b/src/main/java/bio/overture/ego/model/dto/Ga4ghVisaV1.java new file mode 100644 index 000000000..11c1bdc52 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/Ga4ghVisaV1.java @@ -0,0 +1,31 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Ga4ghVisaV1 { + + @JsonProperty("asserted") + private int asserted; + + @JsonProperty("by") + private String by; + + @JsonProperty("source") + private Object source; + + @JsonProperty("type") + private String type; + + @JsonProperty("value") + private String value; +} diff --git a/src/main/java/bio/overture/ego/model/dto/GroupRequest.java b/src/main/java/bio/overture/ego/model/dto/GroupRequest.java index 25124e102..37af37ad7 100644 --- a/src/main/java/bio/overture/ego/model/dto/GroupRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/GroupRequest.java @@ -17,7 +17,7 @@ package bio.overture.ego.model.dto; import bio.overture.ego.model.enums.StatusType; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/MaskDTO.java b/src/main/java/bio/overture/ego/model/dto/MaskDTO.java index 92c6dcdac..21f30ce50 100644 --- a/src/main/java/bio/overture/ego/model/dto/MaskDTO.java +++ b/src/main/java/bio/overture/ego/model/dto/MaskDTO.java @@ -1,7 +1,7 @@ package bio.overture.ego.model.dto; import bio.overture.ego.model.enums.AccessLevel; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/Passport.java b/src/main/java/bio/overture/ego/model/dto/Passport.java new file mode 100644 index 000000000..36ed9a561 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/Passport.java @@ -0,0 +1,35 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Passport { + + @JsonProperty("sub") + private String sub; + + @JsonProperty("iss") + private String iss; + + @JsonProperty("exp") + private long exp; + + @JsonProperty("iat") + private int iat; + + @JsonProperty("ga4gh_passport_v1") + private List ga4ghPassportV1; + + @JsonProperty("jti") + private String jti; +} diff --git a/src/main/java/bio/overture/ego/model/dto/PassportRefreshToken.java b/src/main/java/bio/overture/ego/model/dto/PassportRefreshToken.java new file mode 100644 index 000000000..09f625bfc --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/PassportRefreshToken.java @@ -0,0 +1,21 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.val; + +import java.util.Calendar; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PassportRefreshToken { + private String iss; + private String aud; + private Long exp; // in seconds + private String jti; + + public Long getSecondsUntilExpiry() { + val seconds = this.exp - Calendar.getInstance().getTime().getTime() / 1000L; + return seconds > 0 ? seconds : 0; + } +} diff --git a/src/main/java/bio/overture/ego/model/dto/PassportRefreshTokenResponse.java b/src/main/java/bio/overture/ego/model/dto/PassportRefreshTokenResponse.java new file mode 100644 index 000000000..5bf77f2ea --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/PassportRefreshTokenResponse.java @@ -0,0 +1,15 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PassportRefreshTokenResponse { + private String access_token; + private String token_type; + private String refresh_token; + private Long expires_in; + private String scope; + private String id_token; +} diff --git a/src/main/java/bio/overture/ego/model/dto/PassportToken.java b/src/main/java/bio/overture/ego/model/dto/PassportToken.java new file mode 100644 index 000000000..a437c8c16 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/PassportToken.java @@ -0,0 +1,14 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PassportToken { + + private String access_token; + private String refresh_token; + private Long expires_in; + private String issued_token_type; +} diff --git a/src/main/java/bio/overture/ego/model/dto/PassportVisa.java b/src/main/java/bio/overture/ego/model/dto/PassportVisa.java new file mode 100644 index 000000000..3afe19857 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/PassportVisa.java @@ -0,0 +1,34 @@ +package bio.overture.ego.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class PassportVisa { + + @JsonProperty("sub") + private String sub; + + @JsonProperty("ga4gh_visa_v1") + private Ga4ghVisaV1 ga4ghVisaV1; + + @JsonProperty("iss") + private String iss; + + @JsonProperty("exp") + private long exp; + + @JsonProperty("iat") + private int iat; + + @JsonProperty("jti") + private String jti; +} diff --git a/src/main/java/bio/overture/ego/model/dto/PermissionRequest.java b/src/main/java/bio/overture/ego/model/dto/PermissionRequest.java index 2baf41782..321671107 100644 --- a/src/main/java/bio/overture/ego/model/dto/PermissionRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/PermissionRequest.java @@ -1,8 +1,8 @@ package bio.overture.ego.model.dto; import bio.overture.ego.model.enums.AccessLevel; +import jakarta.validation.constraints.NotNull; import java.util.UUID; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/PolicyRequest.java b/src/main/java/bio/overture/ego/model/dto/PolicyRequest.java index e5ce8a4e9..96be27804 100644 --- a/src/main/java/bio/overture/ego/model/dto/PolicyRequest.java +++ b/src/main/java/bio/overture/ego/model/dto/PolicyRequest.java @@ -1,7 +1,7 @@ package bio.overture.ego.model.dto; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/dto/VisaPermissionRequest.java b/src/main/java/bio/overture/ego/model/dto/VisaPermissionRequest.java new file mode 100644 index 000000000..770c1e42d --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/VisaPermissionRequest.java @@ -0,0 +1,18 @@ +package bio.overture.ego.model.dto; + +import bio.overture.ego.model.enums.AccessLevel; +import java.util.UUID; +import lombok.*; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class VisaPermissionRequest { + + private UUID policyId; + + private UUID visaId; + + private AccessLevel accessLevel; +} diff --git a/src/main/java/bio/overture/ego/model/dto/VisaRequest.java b/src/main/java/bio/overture/ego/model/dto/VisaRequest.java new file mode 100644 index 000000000..d903df140 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/dto/VisaRequest.java @@ -0,0 +1,22 @@ +package bio.overture.ego.model.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class VisaRequest { + + @NotNull private String type; + + @NotNull private String source; + + @NotNull private String value; + + @NotNull private String by; +} diff --git a/src/main/java/bio/overture/ego/model/entity/AbstractPermission.java b/src/main/java/bio/overture/ego/model/entity/AbstractPermission.java index 971d99f6f..d0d30f87d 100644 --- a/src/main/java/bio/overture/ego/model/entity/AbstractPermission.java +++ b/src/main/java/bio/overture/ego/model/entity/AbstractPermission.java @@ -1,38 +1,34 @@ package bio.overture.ego.model.entity; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ACCESS_LEVEL_ENUM; - import bio.overture.ego.model.enums.AccessLevel; import bio.overture.ego.model.enums.JavaFields; import bio.overture.ego.model.enums.SqlFields; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.validation.constraints.NotNull; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.MappedSuperclass; -import javax.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.experimental.FieldNameConstants; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; @Data @MappedSuperclass @FieldNameConstants @EqualsAndHashCode(of = {"id"}) @ToString(exclude = {"policy"}) -@TypeDef(name = EGO_ACCESS_LEVEL_ENUM, typeClass = PostgreSQLEnumType.class) @JsonPropertyOrder({JavaFields.ID, JavaFields.POLICY, JavaFields.OWNER, JavaFields.ACCESS_LEVEL}) @JsonSubTypes({ @JsonSubTypes.Type(value = UserPermission.class, name = JavaFields.USERPERMISSIONS), @@ -54,7 +50,7 @@ public abstract class AbstractPermission> @NotNull @Column(name = SqlFields.ACCESS_LEVEL, nullable = false) @Enumerated(EnumType.STRING) - @Type(type = EGO_ACCESS_LEVEL_ENUM) + @Type(PostgreSQLEnumType.class) private AccessLevel accessLevel; public abstract O getOwner(); diff --git a/src/main/java/bio/overture/ego/model/entity/ApiKey.java b/src/main/java/bio/overture/ego/model/entity/ApiKey.java index bc048d670..2f7ad0a4b 100644 --- a/src/main/java/bio/overture/ego/model/entity/ApiKey.java +++ b/src/main/java/bio/overture/ego/model/entity/ApiKey.java @@ -8,18 +8,18 @@ import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; import java.util.*; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/entity/ApiKeyScope.java b/src/main/java/bio/overture/ego/model/entity/ApiKeyScope.java index 0e8edba40..4aa35225c 100644 --- a/src/main/java/bio/overture/ego/model/entity/ApiKeyScope.java +++ b/src/main/java/bio/overture/ego/model/entity/ApiKeyScope.java @@ -1,35 +1,31 @@ package bio.overture.ego.model.entity; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ACCESS_LEVEL_ENUM; - import bio.overture.ego.model.enums.AccessLevel; import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; import com.fasterxml.jackson.annotation.JsonIgnore; import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; // TODO: rename TOKENSCOPE to API_KEY_SCOPE [anncatton] @NoArgsConstructor @AllArgsConstructor @Data @Entity -@TypeDef(name = EGO_ACCESS_LEVEL_ENUM, typeClass = PostgreSQLEnumType.class) @Table(name = Tables.TOKENSCOPE) public class ApiKeyScope implements Serializable { @@ -51,7 +47,7 @@ public class ApiKeyScope implements Serializable { @NotNull @Enumerated(EnumType.STRING) - @Type(type = EGO_ACCESS_LEVEL_ENUM) + @Type(PostgreSQLEnumType.class) @Column(name = SqlFields.ACCESS_LEVEL, nullable = false) private AccessLevel accessLevel; diff --git a/src/main/java/bio/overture/ego/model/entity/Application.java b/src/main/java/bio/overture/ego/model/entity/Application.java index d1a1044f8..b5f685f15 100644 --- a/src/main/java/bio/overture/ego/model/entity/Application.java +++ b/src/main/java/bio/overture/ego/model/entity/Application.java @@ -16,7 +16,6 @@ package bio.overture.ego.model.entity; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ENUM; import static com.google.common.collect.Sets.newHashSet; import bio.overture.ego.model.enums.ApplicationType; @@ -32,19 +31,19 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; import java.util.Set; import java.util.UUID; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -55,7 +54,6 @@ import lombok.experimental.FieldNameConstants; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; @Entity @Table(name = Tables.APPLICATION) @@ -79,8 +77,6 @@ @NoArgsConstructor @JsonView(Views.REST.class) @FieldNameConstants -@TypeDef(name = "application_type_enum", typeClass = PostgreSQLEnumType.class) -@TypeDef(name = EGO_ENUM, typeClass = PostgreSQLEnumType.class) @JsonInclude(JsonInclude.Include.CUSTOM) public class Application implements PolicyOwner, NameableEntity { @@ -96,7 +92,7 @@ public class Application implements PolicyOwner, NameableEntity { private String name; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @Column(name = SqlFields.TYPE, nullable = false) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) @@ -124,7 +120,7 @@ public class Application implements PolicyOwner, NameableEntity { private String errorRedirectUri; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) @Column(name = SqlFields.STATUS, nullable = false) diff --git a/src/main/java/bio/overture/ego/model/entity/ApplicationPermission.java b/src/main/java/bio/overture/ego/model/entity/ApplicationPermission.java index 47d89ac6d..37f6d8e48 100644 --- a/src/main/java/bio/overture/ego/model/entity/ApplicationPermission.java +++ b/src/main/java/bio/overture/ego/model/entity/ApplicationPermission.java @@ -5,7 +5,7 @@ import bio.overture.ego.model.enums.Tables; import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; -import javax.persistence.*; +import jakarta.persistence.*; import lombok.*; import lombok.experimental.FieldNameConstants; diff --git a/src/main/java/bio/overture/ego/model/entity/DefaultProvider.java b/src/main/java/bio/overture/ego/model/entity/DefaultProvider.java index da1216217..f8db8487e 100644 --- a/src/main/java/bio/overture/ego/model/entity/DefaultProvider.java +++ b/src/main/java/bio/overture/ego/model/entity/DefaultProvider.java @@ -1,11 +1,10 @@ package bio.overture.ego.model.entity; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ENUM; - import bio.overture.ego.model.enums.ProviderType; import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; -import javax.persistence.*; +import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.Type; @@ -19,7 +18,7 @@ public class DefaultProvider implements Identifiable { @Id @Column(name = SqlFields.ID, nullable = false) - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) private ProviderType id; } diff --git a/src/main/java/bio/overture/ego/model/entity/Group.java b/src/main/java/bio/overture/ego/model/entity/Group.java index e81fbd6e0..0ad549d21 100644 --- a/src/main/java/bio/overture/ego/model/entity/Group.java +++ b/src/main/java/bio/overture/ego/model/entity/Group.java @@ -16,7 +16,6 @@ package bio.overture.ego.model.entity; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ENUM; import static com.google.common.collect.Sets.newHashSet; import bio.overture.ego.model.enums.JavaFields; @@ -30,19 +29,19 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; import java.util.Set; import java.util.UUID; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -51,7 +50,6 @@ import lombok.ToString; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; @Data @Entity @@ -61,7 +59,6 @@ @Table(name = Tables.GROUP) @JsonView(Views.REST.class) @EqualsAndHashCode(of = "id") -@TypeDef(name = EGO_ENUM, typeClass = PostgreSQLEnumType.class) @ToString(exclude = {"userGroups", "groupApplications", "permissions"}) @JsonPropertyOrder({ JavaFields.ID, @@ -87,7 +84,7 @@ public class Group implements PolicyOwner, NameableEntity { private String description; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @Column(name = SqlFields.STATUS, nullable = false) private StatusType status; diff --git a/src/main/java/bio/overture/ego/model/entity/GroupPermission.java b/src/main/java/bio/overture/ego/model/entity/GroupPermission.java index 83bf7d919..ec1551ce5 100644 --- a/src/main/java/bio/overture/ego/model/entity/GroupPermission.java +++ b/src/main/java/bio/overture/ego/model/entity/GroupPermission.java @@ -6,13 +6,13 @@ import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonView; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.NamedAttributeNode; -import javax.persistence.NamedEntityGraph; -import javax.persistence.Table; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/bio/overture/ego/model/entity/InitTripWire.java b/src/main/java/bio/overture/ego/model/entity/InitTripWire.java index 22ef4ad28..f736a6973 100644 --- a/src/main/java/bio/overture/ego/model/entity/InitTripWire.java +++ b/src/main/java/bio/overture/ego/model/entity/InitTripWire.java @@ -2,10 +2,10 @@ import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/entity/Policy.java b/src/main/java/bio/overture/ego/model/entity/Policy.java index dd11c17fd..32347191a 100644 --- a/src/main/java/bio/overture/ego/model/entity/Policy.java +++ b/src/main/java/bio/overture/ego/model/entity/Policy.java @@ -10,20 +10,20 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.util.Set; import java.util.UUID; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.NamedAttributeNode; -import javax.persistence.NamedEntityGraph; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/entity/RefreshToken.java b/src/main/java/bio/overture/ego/model/entity/RefreshToken.java index 1470d32e2..dc63d7d2b 100644 --- a/src/main/java/bio/overture/ego/model/entity/RefreshToken.java +++ b/src/main/java/bio/overture/ego/model/entity/RefreshToken.java @@ -3,9 +3,9 @@ import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; import java.util.*; -import javax.persistence.*; -import javax.validation.constraints.NotNull; import lombok.*; import lombok.experimental.FieldNameConstants; import org.hibernate.annotations.GenericGenerator; diff --git a/src/main/java/bio/overture/ego/model/entity/User.java b/src/main/java/bio/overture/ego/model/entity/User.java index cc1ea6148..9af3163a6 100644 --- a/src/main/java/bio/overture/ego/model/entity/User.java +++ b/src/main/java/bio/overture/ego/model/entity/User.java @@ -17,7 +17,6 @@ package bio.overture.ego.model.entity; import static bio.overture.ego.grpc.ProtoUtils.toProtoString; -import static bio.overture.ego.model.enums.AccessLevel.EGO_ENUM; import static bio.overture.ego.service.UserService.resolveUsersPermissions; import static bio.overture.ego.utils.CollectionUtils.mapToImmutableSet; import static bio.overture.ego.utils.PolicyPermissionUtils.extractPermissionStrings; @@ -34,11 +33,11 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; -import javax.persistence.*; -import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -51,7 +50,6 @@ import org.hibernate.LazyInitializationException; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; @Slf4j @Entity @@ -83,7 +81,6 @@ @AllArgsConstructor @NoArgsConstructor @JsonView(Views.REST.class) -@TypeDef(name = EGO_ENUM, typeClass = PostgreSQLEnumType.class) @FieldNameConstants public class User implements PolicyOwner, Identifiable { @@ -99,14 +96,14 @@ public class User implements PolicyOwner, Identifiable { private String email; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @Column(name = SqlFields.TYPE, nullable = false) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) private UserType type; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) @Column(name = SqlFields.STATUS, nullable = false) @@ -133,14 +130,14 @@ public class User implements PolicyOwner, Identifiable { @Temporal(value = TemporalType.TIMESTAMP) private Date lastLogin; - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @Column(name = SqlFields.PREFERREDLANGUAGE) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) private LanguageType preferredLanguage; @NotNull - @Type(type = EGO_ENUM) + @Type(PostgreSQLEnumType.class) @Enumerated(EnumType.STRING) @Column(name = SqlFields.PROVIDERTYPE, nullable = false) @JsonView({Views.JWTAccessToken.class, Views.REST.class}) @@ -151,6 +148,10 @@ public class User implements PolicyOwner, Identifiable { @Column(name = SqlFields.PROVIDERSUBJECTID, nullable = false) private String providerSubjectId; + @JsonView({Views.JWTAccessToken.class, Views.REST.class}) + @Column(name = SqlFields.PROVIDERISSUERURI) + private String providerIssuerUri; + @JsonIgnore @OneToMany( mappedBy = JavaFields.OWNER, diff --git a/src/main/java/bio/overture/ego/model/entity/UserPermission.java b/src/main/java/bio/overture/ego/model/entity/UserPermission.java index 7a3f37c61..0628faa31 100644 --- a/src/main/java/bio/overture/ego/model/entity/UserPermission.java +++ b/src/main/java/bio/overture/ego/model/entity/UserPermission.java @@ -5,13 +5,13 @@ import bio.overture.ego.model.enums.Tables; import bio.overture.ego.view.Views; import com.fasterxml.jackson.annotation.JsonView; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.NamedAttributeNode; -import javax.persistence.NamedEntityGraph; -import javax.persistence.Table; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/entity/Visa.java b/src/main/java/bio/overture/ego/model/entity/Visa.java new file mode 100644 index 000000000..ce879437c --- /dev/null +++ b/src/main/java/bio/overture/ego/model/entity/Visa.java @@ -0,0 +1,70 @@ +package bio.overture.ego.model.entity; + +import static com.google.common.collect.Sets.newHashSet; + +import bio.overture.ego.model.enums.JavaFields; +import bio.overture.ego.model.enums.SqlFields; +import bio.overture.ego.model.enums.Tables; +import bio.overture.ego.view.Views; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonView; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import java.util.Set; +import java.util.UUID; +import lombok.*; +import lombok.experimental.FieldNameConstants; +import org.hibernate.annotations.GenericGenerator; + +@Entity +@Table(name = Tables.GA4GHVISA) +@JsonInclude() +@JsonPropertyOrder({ + JavaFields.ID, + JavaFields.TYPE, + JavaFields.SOURCE, + JavaFields.VALUE, + JavaFields.BY +}) +@JsonView(Views.REST.class) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@EqualsAndHashCode(of = {"id"}) +public class Visa implements Identifiable { + + @Id + @Column(name = SqlFields.ID, updatable = false, nullable = false) + @GenericGenerator(name = "visa_uuid", strategy = "org.hibernate.id.UUIDGenerator") + @GeneratedValue(generator = "visa_uuid") + private UUID id; + + @NotNull + @Column(name = SqlFields.TYPE, nullable = false) + @JsonView({Views.JWTAccessToken.class, Views.REST.class}) + private String type; + + @NotNull + @JsonView({Views.JWTAccessToken.class, Views.REST.class}) + @Column(name = SqlFields.SOURCE) + private String source; + + @NotNull + @JsonView({Views.JWTAccessToken.class, Views.REST.class}) + @Column(name = SqlFields.VALUE) + private String value; + + @NotNull + @JsonView({Views.JWTAccessToken.class, Views.REST.class}) + @Column(name = SqlFields.BY) + private String by; + + @JsonIgnore + @OneToMany(mappedBy = JavaFields.VISA, cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private Set visaPermissions = newHashSet(); +} diff --git a/src/main/java/bio/overture/ego/model/entity/VisaPermission.java b/src/main/java/bio/overture/ego/model/entity/VisaPermission.java new file mode 100644 index 000000000..ebac950a0 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/entity/VisaPermission.java @@ -0,0 +1,43 @@ +package bio.overture.ego.model.entity; + +import bio.overture.ego.model.enums.JavaFields; +import bio.overture.ego.model.enums.SqlFields; +import bio.overture.ego.model.enums.Tables; +import bio.overture.ego.view.Views; +import com.fasterxml.jackson.annotation.JsonView; +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldNameConstants; + +@Entity +@Table(name = Tables.ACLVISAPERMISSION) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonView(Views.REST.class) +@ToString(callSuper = true) +@FieldNameConstants +@EqualsAndHashCode( + callSuper = true, + of = {"id"}) +@NamedEntityGraph( + name = "visa-permission-entity-with-relationships", + attributeNodes = { + @NamedAttributeNode(value = JavaFields.POLICY), + @NamedAttributeNode(value = JavaFields.VISA) + }) +public class VisaPermission extends AbstractPermission { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = SqlFields.VISA_ID, nullable = false) + private Visa visa; + + @Override + public Visa getOwner() { + return null; + } + + @Override + public void setOwner(Visa owner) {} +} diff --git a/src/main/java/bio/overture/ego/model/enums/JavaFields.java b/src/main/java/bio/overture/ego/model/enums/JavaFields.java index 7ec650a4e..5592b257b 100644 --- a/src/main/java/bio/overture/ego/model/enums/JavaFields.java +++ b/src/main/java/bio/overture/ego/model/enums/JavaFields.java @@ -66,4 +66,12 @@ public class JavaFields { public static final String PROVIDERTYPE = "providerType"; public static final String PROVIDER_SUBJECT_ID = "providerSubjectId"; public static final String ERROR_REDIRECT_URI = "errorRedirectUri"; + // Visas Added + public static final String SOURCE = "source"; + public static final String VALUE = "value"; + + public static final String BY = "by"; + public static final String VISAPERMISSION = "ACLVISAPERMISSION"; + public static final String GA4GHVISA = "ga4ghvisa"; + public static final String VISA = "visa"; } diff --git a/src/main/java/bio/overture/ego/model/enums/ProviderType.java b/src/main/java/bio/overture/ego/model/enums/ProviderType.java index 44e22aa9d..fc6f531a8 100644 --- a/src/main/java/bio/overture/ego/model/enums/ProviderType.java +++ b/src/main/java/bio/overture/ego/model/enums/ProviderType.java @@ -8,6 +8,8 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; +import java.util.Optional; + @RequiredArgsConstructor public enum ProviderType { GOOGLE, @@ -15,11 +17,15 @@ public enum ProviderType { LINKEDIN, GITHUB, ORCID, - KEYCLOAK; + KEYCLOAK, + PASSPORT; // TODO: verify these are the correct accessor keys for each provider public static String getIdAccessor(ProviderType provider) { - if (provider.equals(GOOGLE) || provider.equals(ORCID) || provider.equals(KEYCLOAK)) { + if (provider.equals(GOOGLE) + || provider.equals(ORCID) + || provider.equals(KEYCLOAK) + || provider.equals(PASSPORT)) { return "sub"; } @@ -49,6 +55,12 @@ public static ProviderType resolveProviderType(@NonNull String providerType) { providerType, COMMA.join(values())))); } + public static Optional findIfExist(@NonNull String providerType) { + return stream(values()) + .filter(x -> x.toString().equalsIgnoreCase(providerType)) + .findFirst(); + } + @Override public String toString() { return this.name(); diff --git a/src/main/java/bio/overture/ego/model/enums/SqlFields.java b/src/main/java/bio/overture/ego/model/enums/SqlFields.java index 5fde6918d..a00a6820f 100644 --- a/src/main/java/bio/overture/ego/model/enums/SqlFields.java +++ b/src/main/java/bio/overture/ego/model/enums/SqlFields.java @@ -36,6 +36,11 @@ public class SqlFields { public static final String USER_ID = "user_id"; public static final String PROVIDERTYPE = "providertype"; public static final String PROVIDERSUBJECTID = "providersubjectid"; + public static final String PROVIDERISSUERURI = "providerissueruri"; public static final String INITIALIZED = "initialized"; public static final String ERRORREDIRECTURI = "errorredirecturi"; + public static final String SOURCE = "source"; + public static final String VALUE = "value"; + public static final String BY = "by"; + public static final String VISA_ID = "visa_id"; } diff --git a/src/main/java/bio/overture/ego/model/enums/Tables.java b/src/main/java/bio/overture/ego/model/enums/Tables.java index 9eab02c90..e4198aaf9 100644 --- a/src/main/java/bio/overture/ego/model/enums/Tables.java +++ b/src/main/java/bio/overture/ego/model/enums/Tables.java @@ -22,4 +22,6 @@ public class Tables { public static final String APPLICATION_PERMISSION = "applicationpermission"; public static final String DEFAULTPROVIDERTRIPWIRE = "defaultprovidertripwire"; public static final String INITTRIPWIRE = "inittripwire"; + public static final String GA4GHVISA = "ga4ghvisa"; + public static final String ACLVISAPERMISSION = "ACLVISAPERMISSION"; } diff --git a/src/main/java/bio/overture/ego/model/enums/VisaType.java b/src/main/java/bio/overture/ego/model/enums/VisaType.java new file mode 100644 index 000000000..0eb87dab1 --- /dev/null +++ b/src/main/java/bio/overture/ego/model/enums/VisaType.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017. The Ontario Institute for Cancer Research. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bio.overture.ego.model.enums; + +import static bio.overture.ego.utils.Joiners.COMMA; +import static bio.overture.ego.utils.Streams.stream; +import static java.lang.String.format; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum VisaType { + STANDARD_VISA_TYPE, + CUSTOM_VISA_TYPE; + + public static VisaType resolveStatusType(@NonNull String statusType) { + return stream(values()) + .filter(x -> x.toString().equals(statusType)) + .findFirst() + .orElseThrow( + () -> + new IllegalArgumentException( + format( + "The status type '%s' cannot be resolved. Must be one of: [%s]", + statusType, COMMA.join(values())))); + } + + @Override + public String toString() { + return this.name(); + } +} diff --git a/src/main/java/bio/overture/ego/model/exceptions/ExceptionHandlers.java b/src/main/java/bio/overture/ego/model/exceptions/ExceptionHandlers.java index 20835a1c5..a2871f1a9 100644 --- a/src/main/java/bio/overture/ego/model/exceptions/ExceptionHandlers.java +++ b/src/main/java/bio/overture/ego/model/exceptions/ExceptionHandlers.java @@ -1,18 +1,18 @@ package bio.overture.ego.model.exceptions; import static java.lang.String.format; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.*; import bio.overture.ego.utils.Joiners; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; import java.util.Date; import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -35,6 +35,21 @@ public ResponseEntity handleConstraintViolationException( NOT_FOUND); } + @ExceptionHandler(MissingRequestHeaderException.class) + public ResponseEntity handleMissingRequestHeaderException( + HttpServletRequest req, MissingRequestHeaderException ex) { + val message = ex.getMessage(); + log.error(message); + return new ResponseEntity( + Map.of( + "message", ex.getMessage(), + "timestamp", new Date(), + "path", req.getServletPath(), + "error", UNAUTHORIZED.getReasonPhrase()), + new HttpHeaders(), + UNAUTHORIZED); + } + @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity handleConstraintViolationException( HttpServletRequest req, ConstraintViolationException ex) { diff --git a/src/main/java/bio/overture/ego/model/exceptions/RequestValidationException.java b/src/main/java/bio/overture/ego/model/exceptions/RequestValidationException.java index 0a86301f2..0519cbe2d 100644 --- a/src/main/java/bio/overture/ego/model/exceptions/RequestValidationException.java +++ b/src/main/java/bio/overture/ego/model/exceptions/RequestValidationException.java @@ -5,8 +5,8 @@ import static java.lang.String.format; import static org.springframework.http.HttpStatus.BAD_REQUEST; -import javax.validation.Validation; -import javax.validation.Validator; +import jakarta.validation.Validation; +import jakarta.validation.Validator; import lombok.NonNull; import lombok.val; import org.springframework.web.bind.annotation.ResponseStatus; diff --git a/src/main/java/bio/overture/ego/model/exceptions/RequestViolation.java b/src/main/java/bio/overture/ego/model/exceptions/RequestViolation.java index c9eb2f850..9f9182837 100644 --- a/src/main/java/bio/overture/ego/model/exceptions/RequestViolation.java +++ b/src/main/java/bio/overture/ego/model/exceptions/RequestViolation.java @@ -1,6 +1,6 @@ package bio.overture.ego.model.exceptions; -import javax.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolation; import lombok.Builder; import lombok.NonNull; import lombok.Value; diff --git a/src/main/java/bio/overture/ego/model/exceptions/SSOAuthenticationFailureHandler.java b/src/main/java/bio/overture/ego/model/exceptions/SSOAuthenticationFailureHandler.java index 132d46c45..7dab5dd89 100644 --- a/src/main/java/bio/overture/ego/model/exceptions/SSOAuthenticationFailureHandler.java +++ b/src/main/java/bio/overture/ego/model/exceptions/SSOAuthenticationFailureHandler.java @@ -6,11 +6,11 @@ import bio.overture.ego.model.enums.ProviderType; import bio.overture.ego.service.ApplicationService; import bio.overture.ego.utils.Redirects; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -56,7 +56,7 @@ public void onAuthenticationFailure( } else if (rootExceptionThrowable instanceof OAuth2Exception) { errorUri = buildOAuth2ExceptionResponse(errorUri, providerType); } else { - throw new InternalServerException("Invalid response from OAuth Service"); + throw new InternalServerException("Invalid response from OAuth Service:" + exception); } response.setStatus(403); response.sendRedirect(errorUri.toString()); diff --git a/src/main/java/bio/overture/ego/model/join/GroupApplication.java b/src/main/java/bio/overture/ego/model/join/GroupApplication.java index 0b49e4e3d..99fb1ca13 100644 --- a/src/main/java/bio/overture/ego/model/join/GroupApplication.java +++ b/src/main/java/bio/overture/ego/model/join/GroupApplication.java @@ -6,14 +6,14 @@ import bio.overture.ego.model.enums.JavaFields; import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; -import javax.persistence.CascadeType; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.MapsId; -import javax.persistence.Table; +import jakarta.persistence.CascadeType; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/join/GroupApplicationId.java b/src/main/java/bio/overture/ego/model/join/GroupApplicationId.java index c7e5ab2bc..aa5144d93 100644 --- a/src/main/java/bio/overture/ego/model/join/GroupApplicationId.java +++ b/src/main/java/bio/overture/ego/model/join/GroupApplicationId.java @@ -1,10 +1,10 @@ package bio.overture.ego.model.join; import bio.overture.ego.model.enums.SqlFields; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; import java.io.Serializable; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Embeddable; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/join/UserApplication.java b/src/main/java/bio/overture/ego/model/join/UserApplication.java index f6b5f6b6a..63ec91e3e 100644 --- a/src/main/java/bio/overture/ego/model/join/UserApplication.java +++ b/src/main/java/bio/overture/ego/model/join/UserApplication.java @@ -6,14 +6,14 @@ import bio.overture.ego.model.enums.JavaFields; import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; -import javax.persistence.CascadeType; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.MapsId; -import javax.persistence.Table; +import jakarta.persistence.CascadeType; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/join/UserApplicationId.java b/src/main/java/bio/overture/ego/model/join/UserApplicationId.java index 22b942273..cae1172f0 100644 --- a/src/main/java/bio/overture/ego/model/join/UserApplicationId.java +++ b/src/main/java/bio/overture/ego/model/join/UserApplicationId.java @@ -1,10 +1,10 @@ package bio.overture.ego.model.join; import bio.overture.ego.model.enums.SqlFields; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; import java.io.Serializable; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Embeddable; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/join/UserGroup.java b/src/main/java/bio/overture/ego/model/join/UserGroup.java index 21700719b..40a9f326b 100644 --- a/src/main/java/bio/overture/ego/model/join/UserGroup.java +++ b/src/main/java/bio/overture/ego/model/join/UserGroup.java @@ -6,14 +6,14 @@ import bio.overture.ego.model.enums.JavaFields; import bio.overture.ego.model.enums.SqlFields; import bio.overture.ego.model.enums.Tables; -import javax.persistence.CascadeType; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.MapsId; -import javax.persistence.Table; +import jakarta.persistence.CascadeType; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/model/join/UserGroupId.java b/src/main/java/bio/overture/ego/model/join/UserGroupId.java index 5d74805e4..9dcb16c4e 100644 --- a/src/main/java/bio/overture/ego/model/join/UserGroupId.java +++ b/src/main/java/bio/overture/ego/model/join/UserGroupId.java @@ -1,10 +1,10 @@ package bio.overture.ego.model.join; import bio.overture.ego.model.enums.SqlFields; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; import java.io.Serializable; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Embeddable; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/bio/overture/ego/repository/BaseRepository.java b/src/main/java/bio/overture/ego/repository/BaseRepository.java index 060365171..69f58e78a 100644 --- a/src/main/java/bio/overture/ego/repository/BaseRepository.java +++ b/src/main/java/bio/overture/ego/repository/BaseRepository.java @@ -3,12 +3,13 @@ import java.util.Collection; import java.util.Set; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; @NoRepositoryBean public interface BaseRepository - extends PagingAndSortingRepository, JpaSpecificationExecutor { + extends PagingAndSortingRepository, CrudRepository, JpaSpecificationExecutor { T findFirstBy(); Set findAllByIdIn(Collection ids); diff --git a/src/main/java/bio/overture/ego/repository/VisaPermissionRepository.java b/src/main/java/bio/overture/ego/repository/VisaPermissionRepository.java new file mode 100644 index 000000000..abee391d1 --- /dev/null +++ b/src/main/java/bio/overture/ego/repository/VisaPermissionRepository.java @@ -0,0 +1,28 @@ +package bio.overture.ego.repository; + +import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.FETCH; + +import bio.overture.ego.model.entity.VisaPermission; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.EntityGraph; + +public interface VisaPermissionRepository extends NamedRepository { + @Override + @Deprecated + default Optional findByName(String name) { + return null; + } + + List findAll(); + + @EntityGraph(value = "visa-permission-entity-with-relationships", type = FETCH) + List findByVisa_Id(UUID visa_id); + + @EntityGraph(value = "visa-permission-entity-with-relationships", type = FETCH) + List findByPolicy_Id(UUID policy_id); + + @EntityGraph(value = "visa-permission-entity-with-relationships", type = FETCH) + List findByPolicyIdAndVisaId(UUID policy_id, UUID visa_id); +} diff --git a/src/main/java/bio/overture/ego/repository/VisaRepository.java b/src/main/java/bio/overture/ego/repository/VisaRepository.java new file mode 100644 index 000000000..a9f3de0a7 --- /dev/null +++ b/src/main/java/bio/overture/ego/repository/VisaRepository.java @@ -0,0 +1,18 @@ +package bio.overture.ego.repository; + +import bio.overture.ego.model.entity.Visa; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface VisaRepository extends NamedRepository { + @Override + @Deprecated + default Optional findByName(String name) { + return null; + } + + List findAll(); + + List getByTypeAndValue(String type, String value); +} diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/AbstractPermissionSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/AbstractPermissionSpecification.java index a66bfb5d3..882911298 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/AbstractPermissionSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/AbstractPermissionSpecification.java @@ -10,10 +10,10 @@ import bio.overture.ego.model.search.SearchFilter; import bio.overture.ego.utils.QueryUtils; import com.google.common.collect.Lists; +import jakarta.persistence.criteria.Predicate; import java.util.List; import java.util.UUID; import java.util.stream.Stream; -import javax.persistence.criteria.Predicate; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/ApplicationSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/ApplicationSpecification.java index 04e1a7852..cfc9477df 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/ApplicationSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/ApplicationSpecification.java @@ -33,8 +33,8 @@ import bio.overture.ego.model.join.GroupApplication; import bio.overture.ego.model.join.UserApplication; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.Join; import java.util.UUID; -import javax.persistence.criteria.Join; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/GroupSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/GroupSpecification.java index 80cdfa11d..664b3004b 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/GroupSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/GroupSpecification.java @@ -31,8 +31,8 @@ import bio.overture.ego.model.join.GroupApplication; import bio.overture.ego.model.join.UserGroup; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.Join; import java.util.UUID; -import javax.persistence.criteria.Join; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/SimpleCriteriaBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/SimpleCriteriaBuilder.java index f4ff1fab5..475ae60cc 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/SimpleCriteriaBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/SimpleCriteriaBuilder.java @@ -6,10 +6,10 @@ import bio.overture.ego.model.search.SearchFilter; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.*; import java.util.Collection; import java.util.List; import java.util.UUID; -import javax.persistence.criteria.*; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.val; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/SpecificationBase.java b/src/main/java/bio/overture/ego/repository/queryspecification/SpecificationBase.java index f7e1a8004..ae5277bdb 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/SpecificationBase.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/SpecificationBase.java @@ -18,11 +18,11 @@ import bio.overture.ego.model.search.SearchFilter; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import java.util.Arrays; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/TokenStoreSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/TokenStoreSpecification.java index 386e15bad..446ad3126 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/TokenStoreSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/TokenStoreSpecification.java @@ -21,8 +21,8 @@ import bio.overture.ego.model.entity.ApiKey; import bio.overture.ego.model.entity.User; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.Join; import java.util.UUID; -import javax.persistence.criteria.Join; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/UserPermissionSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/UserPermissionSpecification.java index 5ef7bb928..42fd722b0 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/UserPermissionSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/UserPermissionSpecification.java @@ -29,9 +29,9 @@ import bio.overture.ego.model.search.SearchFilter; import bio.overture.ego.utils.QueryUtils; import com.google.common.collect.Lists; +import jakarta.persistence.criteria.Predicate; import java.util.List; import java.util.UUID; -import javax.persistence.criteria.Predicate; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/UserSpecification.java b/src/main/java/bio/overture/ego/repository/queryspecification/UserSpecification.java index 2f3826ea5..2bd063152 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/UserSpecification.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/UserSpecification.java @@ -25,9 +25,9 @@ import bio.overture.ego.model.join.UserApplication; import bio.overture.ego.model.join.UserGroup; import bio.overture.ego.utils.QueryUtils; +import jakarta.persistence.criteria.Join; import java.util.Collection; import java.util.UUID; -import javax.persistence.criteria.Join; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/AbstractSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/AbstractSpecificationBuilder.java index 4f9f10d3a..6f61161bc 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/AbstractSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/AbstractSpecificationBuilder.java @@ -3,10 +3,10 @@ import static bio.overture.ego.model.enums.JavaFields.NAME; import bio.overture.ego.model.enums.JavaFields; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import java.util.Collection; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import lombok.NonNull; import lombok.val; import org.springframework.data.jpa.domain.Specification; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/ApplicationSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/ApplicationSpecificationBuilder.java index 3052fda41..94646eb27 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/ApplicationSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/ApplicationSpecificationBuilder.java @@ -1,13 +1,13 @@ package bio.overture.ego.repository.queryspecification.builder; import static bio.overture.ego.model.enums.JavaFields.*; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.entity.Application; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import java.util.UUID; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import lombok.NonNull; import lombok.Setter; import lombok.experimental.Accessors; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/GroupSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/GroupSpecificationBuilder.java index 092a33593..c8a7884b9 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/GroupSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/GroupSpecificationBuilder.java @@ -5,11 +5,11 @@ import static bio.overture.ego.model.enums.JavaFields.PERMISSIONS; import static bio.overture.ego.model.enums.JavaFields.USER; import static bio.overture.ego.model.enums.JavaFields.USERGROUPS; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.entity.Group; +import jakarta.persistence.criteria.Root; import java.util.UUID; -import javax.persistence.criteria.Root; import lombok.Setter; import lombok.experimental.Accessors; import lombok.val; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/RefreshTokenSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/RefreshTokenSpecificationBuilder.java index 4aae74ebf..453e48126 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/RefreshTokenSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/RefreshTokenSpecificationBuilder.java @@ -1,10 +1,10 @@ package bio.overture.ego.repository.queryspecification.builder; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.entity.RefreshToken; +import jakarta.persistence.criteria.Root; import java.util.UUID; -import javax.persistence.criteria.Root; import lombok.Setter; import lombok.experimental.Accessors; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/TokenSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/TokenSpecificationBuilder.java index 44ed9aaeb..38b5d3ef2 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/TokenSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/TokenSpecificationBuilder.java @@ -2,11 +2,11 @@ import static bio.overture.ego.model.enums.JavaFields.OWNER; import static bio.overture.ego.model.enums.JavaFields.SCOPES; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.entity.ApiKey; +import jakarta.persistence.criteria.Root; import java.util.UUID; -import javax.persistence.criteria.Root; import lombok.Setter; import lombok.experimental.Accessors; diff --git a/src/main/java/bio/overture/ego/repository/queryspecification/builder/UserSpecificationBuilder.java b/src/main/java/bio/overture/ego/repository/queryspecification/builder/UserSpecificationBuilder.java index 9079b7ea6..badee2024 100644 --- a/src/main/java/bio/overture/ego/repository/queryspecification/builder/UserSpecificationBuilder.java +++ b/src/main/java/bio/overture/ego/repository/queryspecification/builder/UserSpecificationBuilder.java @@ -1,14 +1,14 @@ package bio.overture.ego.repository.queryspecification.builder; import static bio.overture.ego.model.enums.JavaFields.*; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.entity.User; import bio.overture.ego.model.enums.ProviderType; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import java.util.UUID; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import lombok.NonNull; import lombok.Setter; import lombok.experimental.Accessors; diff --git a/src/main/java/bio/overture/ego/security/CorsFilter.java b/src/main/java/bio/overture/ego/security/CorsFilter.java index b6e054b34..7e5d70cb4 100644 --- a/src/main/java/bio/overture/ego/security/CorsFilter.java +++ b/src/main/java/bio/overture/ego/security/CorsFilter.java @@ -18,10 +18,10 @@ import bio.overture.ego.service.ApplicationService; import bio.overture.ego.utils.Redirects; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.net.URI; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/security/CustomOAuth2User.java b/src/main/java/bio/overture/ego/security/CustomOAuth2User.java index 62f2fed67..1bb4fb7eb 100644 --- a/src/main/java/bio/overture/ego/security/CustomOAuth2User.java +++ b/src/main/java/bio/overture/ego/security/CustomOAuth2User.java @@ -23,6 +23,8 @@ public class CustomOAuth2User implements OidcUser { @NonNull private String subjectId; private String email; private OAuth2User oauth2User; + private String accessToken; + private String refreshToken; @Override public Map getAttributes() { @@ -47,6 +49,10 @@ public String getFamilyName() { return this.familyName; } + public String getAccessToken() { return this.accessToken; } + + public String getRefreshToken() {return this.refreshToken; } + public String getSubjectId() { return oauth2User.getAttributes().containsKey(IdTokenClaimNames.SUB) ? oauth2User.getAttributes().get(IdTokenClaimNames.SUB).toString() diff --git a/src/main/java/bio/overture/ego/security/CustomOidc2UserInfoService.java b/src/main/java/bio/overture/ego/security/CustomOidc2UserInfoService.java index 77dc4d9f4..ae8974a2b 100644 --- a/src/main/java/bio/overture/ego/security/CustomOidc2UserInfoService.java +++ b/src/main/java/bio/overture/ego/security/CustomOidc2UserInfoService.java @@ -15,6 +15,7 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -36,9 +37,9 @@ public OidcUser loadUser(OidcUserRequest oAuth2UserRequest) throws OAuth2Authent OidcUser oidcUser = super.loadUser(oAuth2UserRequest); try { String provider = oAuth2UserRequest.getClientRegistration().getRegistrationId(); - val idName = ProviderType.getIdAccessor(ProviderType.resolveProviderType(provider)); if (provider.equalsIgnoreCase(ProviderType.ORCID.toString())) { val info = getOrcidUserInfo(oidcUser, oAuth2UserRequest); + val idName = ProviderType.getIdAccessor(ProviderType.resolveProviderType(provider)); return CustomOAuth2User.builder() .oauth2User(new DefaultOAuth2User(oidcUser.getAuthorities(), info, idName)) .subjectId(info.get(idName).toString()) @@ -47,12 +48,16 @@ public OidcUser loadUser(OidcUserRequest oAuth2UserRequest) throws OAuth2Authent .givenName(info.getOrDefault(GIVEN_NAME, "").toString()) .build(); } + + val refreshToken = getRefreshToken(oAuth2UserRequest); return CustomOAuth2User.builder() .oauth2User(oidcUser) .subjectId(oidcUser.getSubject()) .email(oidcUser.getEmail()) .familyName(oidcUser.getFamilyName()) .givenName(oidcUser.getGivenName()) + .accessToken(oAuth2UserRequest.getAccessToken().getTokenValue()) + .refreshToken(refreshToken) .build(); } catch (AuthenticationException ex) { throw ex; @@ -86,4 +91,11 @@ private RestTemplate getTemplate(OAuth2UserRequest oAuth2UserRequest) { }); return restTemplate; } + + private String getRefreshToken(OAuth2UserRequest oAuth2UserRequest) { + val refreshToken = + (String) + oAuth2UserRequest.getAdditionalParameters().get(OAuth2ParameterNames.REFRESH_TOKEN); + return refreshToken; + } } diff --git a/src/main/java/bio/overture/ego/security/JWTAuthorizationFilter.java b/src/main/java/bio/overture/ego/security/JWTAuthorizationFilter.java index 995c1d341..49b57b72d 100644 --- a/src/main/java/bio/overture/ego/security/JWTAuthorizationFilter.java +++ b/src/main/java/bio/overture/ego/security/JWTAuthorizationFilter.java @@ -25,11 +25,11 @@ import bio.overture.ego.token.app.AppTokenClaims; import bio.overture.ego.token.user.UserTokenClaims; import bio.overture.ego.view.Views; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Arrays; -import javax.servlet.FilterChain; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/security/OAuth2RequestResolver.java b/src/main/java/bio/overture/ego/security/OAuth2RequestResolver.java index 1f0acc356..340af2a37 100644 --- a/src/main/java/bio/overture/ego/security/OAuth2RequestResolver.java +++ b/src/main/java/bio/overture/ego/security/OAuth2RequestResolver.java @@ -2,8 +2,8 @@ import static java.util.Objects.isNull; +import jakarta.servlet.http.HttpServletRequest; import java.net.URI; -import javax.servlet.http.HttpServletRequest; import lombok.SneakyThrows; import lombok.val; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; diff --git a/src/main/java/bio/overture/ego/service/AbstractPermissionService.java b/src/main/java/bio/overture/ego/service/AbstractPermissionService.java index 75d7683ed..e3a2761d3 100644 --- a/src/main/java/bio/overture/ego/service/AbstractPermissionService.java +++ b/src/main/java/bio/overture/ego/service/AbstractPermissionService.java @@ -13,13 +13,13 @@ import static bio.overture.ego.utils.PermissionRequestAnalyzer.analyze; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Maps.uniqueIndex; +import static jakarta.persistence.criteria.JoinType.LEFT; import static java.util.Arrays.stream; import static java.util.Collections.reverse; import static java.util.Comparator.comparing; import static java.util.Objects.isNull; import static java.util.function.Function.identity; import static java.util.stream.Collectors.*; -import static javax.persistence.criteria.JoinType.LEFT; import bio.overture.ego.model.dto.PermissionRequest; import bio.overture.ego.model.dto.ResolvedPermissionResponse; diff --git a/src/main/java/bio/overture/ego/service/GroupService.java b/src/main/java/bio/overture/ego/service/GroupService.java index d3041c476..7bfada9fd 100644 --- a/src/main/java/bio/overture/ego/service/GroupService.java +++ b/src/main/java/bio/overture/ego/service/GroupService.java @@ -51,11 +51,11 @@ import bio.overture.ego.repository.queryspecification.builder.GroupSpecificationBuilder; import bio.overture.ego.utils.EntityServices; import com.google.common.collect.ImmutableSet; +import jakarta.transaction.Transactional; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; -import javax.transaction.Transactional; import lombok.NonNull; import lombok.val; import org.mapstruct.Mapper; diff --git a/src/main/java/bio/overture/ego/service/InitializationService.java b/src/main/java/bio/overture/ego/service/InitializationService.java index 65d507a77..7650afdd3 100644 --- a/src/main/java/bio/overture/ego/service/InitializationService.java +++ b/src/main/java/bio/overture/ego/service/InitializationService.java @@ -8,7 +8,7 @@ import bio.overture.ego.model.dto.CreateApplicationRequest; import bio.overture.ego.model.entity.InitTripWire; import bio.overture.ego.repository.InitTripWireRepository; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/service/PassportService.java b/src/main/java/bio/overture/ego/service/PassportService.java new file mode 100644 index 000000000..43c521ad1 --- /dev/null +++ b/src/main/java/bio/overture/ego/service/PassportService.java @@ -0,0 +1,266 @@ +package bio.overture.ego.service; + +import static bio.overture.ego.utils.CollectionUtils.mapToSet; + +import bio.overture.ego.model.dto.*; +import bio.overture.ego.model.entity.User; +import bio.overture.ego.model.entity.Visa; +import bio.overture.ego.model.entity.VisaPermission; +import bio.overture.ego.utils.CacheUtil; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; +import java.util.*; +import java.util.stream.Collectors; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.commons.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +@Service +@Transactional +@Configuration +public class PassportService { + + /** Dependencies */ + @Autowired private VisaService visaService; + + @Autowired private VisaPermissionService visaPermissionService; + + @Autowired private CacheUtil cacheUtil; + + @Autowired private ClientRegistrationRepository clientRegistrationRepository; + + @Autowired private InMemoryClientRegistrationRepository inMemoryClientRegistrationRepository; + + private final String TOKEN_TYPE_PASSPORT = "urn:ga4gh:params:oauth:token-type:passport"; + private final String TOKEN_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"; + private final String GRANT_TYPE_TOKEN_EXCHANGE = + "urn:ietf:params:oauth:grant-type:token-exchange"; + private final String REFRESH_TOKEN = "refresh_token"; + private final String GA4GH_PASSPORT_SCOPE = "ga4gh_passport_v1"; + + @Autowired + public PassportService( + @NonNull VisaPermissionService visaPermissionService, @NonNull VisaService visaService) { + this.visaService = visaService; + this.visaPermissionService = visaPermissionService; + } + + public List getPermissions(String authToken, String providerType) + throws JsonProcessingException, ParseException, JwkException { + // Validates passport auth token + isValidPassport(authToken, providerType); + // Parses passport JWT token + Passport parsedPassport = parsePassport(authToken); + // Fetches visas for parsed passport + List visas = getVisas(parsedPassport, providerType); + // Fetches visa permissions for extracted visas + List visaPermissions = getVisaPermissions(visas); + // removes deduplicates from visaPermissions + visaPermissions = deDupeVisaPermissions(visaPermissions); + return visaPermissions; + } + + // Validates passport token based on public key + private void isValidPassport(@NonNull String authToken, @NonNull String providerType) + throws JwkException { + DecodedJWT jwt = JWT.decode(authToken); + Jwk jwk = cacheUtil.getPassportBrokerPublicKey(providerType).get(jwt.getKeyId()); + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); + algorithm.verify(jwt); + } + + // Extracts Visas from parsed passport object + private List getVisas(Passport passport, @NonNull String providerType) { + List visas = new ArrayList<>(); + passport.getGa4ghPassportV1().stream() + .forEach( + visaJwt -> { + try { + visaService.isValidVisa(visaJwt, providerType); + PassportVisa visa = visaService.parseVisa(visaJwt); + if (visa != null) { + visas.add(visa); + } + } catch (JsonProcessingException | JwkException e) { + e.printStackTrace(); + } + }); + return visas; + } + + // Fetches Visa Permissions for extracted Visa list + private List getVisaPermissions(List visas) { + List visaPermissions = new ArrayList<>(); + visas.stream() + .distinct() + .forEach( + visa -> { + List visaEntities = + visaService.getByTypeAndValue( + visa.getGa4ghVisaV1().getType(), visa.getGa4ghVisaV1().getValue()); + if (visaEntities != null && !visaEntities.isEmpty()) { + visaPermissions.addAll(visaPermissionService.getPermissionsForVisa(visaEntities)); + } + }); + return visaPermissions; + } + + public Set extractScopes(@NonNull String passportJwtToken, @NonNull String providerType) + throws ParseException, JwkException, JsonProcessingException { + val resolvedPermissions = getPermissions(passportJwtToken, providerType); + val output = mapToSet(resolvedPermissions, AbstractPermissionService::buildScope); + if (output.isEmpty()) { + output.add(Scope.defaultScope()); + } + return output; + } + + // Parse Passport token to extract the passport body + @SneakyThrows + private Passport parsePassport(@NonNull String passportJwtToken) { + val body = decodeJwtBody(passportJwtToken); + return new ObjectMapper().readValue(body, Passport.class); + } + + @SneakyThrows + private PassportRefreshToken parseRefreshToken(@NonNull String jwtRefreshToken) { + val body = decodeJwtBody(jwtRefreshToken); + return new ObjectMapper().readValue(body, PassportRefreshToken.class); + } + + private String decodeJwtBody(@NonNull String jwtToken){ + val split_string = jwtToken.split("\\."); + val base64EncodedBody = split_string[1]; + val base64Url = new Base64(true); + return new String(base64Url.decode(base64EncodedBody)); + } + + // Removes duplicates from the VisaPermissons List + private List deDupeVisaPermissions(List visaPermissions) { + Set permissionsSet = new HashSet(); + permissionsSet.addAll(visaPermissions); + return permissionsSet.stream().collect(Collectors.toList()); + } + + public String getPassportToken(String providerId, String accessToken) { + + if (accessToken == null || accessToken.isEmpty()) return null; + + val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerId); + + val uri = + UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()) + .queryParams(passportTokenParams(accessToken)) + .toUriString(); + + val passportToken = + getTemplate(clientRegistration) + .exchange(uri, HttpMethod.POST, null, PassportToken.class) + .getBody(); + + return (passportToken != null) ? passportToken.getAccess_token() : null; + } + + + public PassportRefreshTokenResponse refreshToken(String providerId, String refreshJwtToken) { + if (refreshJwtToken == null || refreshJwtToken.isEmpty()) return null; + + val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerId); + + val uri = + UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()) + .queryParams(refreshPassportTokenParams(clientRegistration, refreshJwtToken)) + .toUriString(); + + return getTemplate(clientRegistration) + .exchange(uri, HttpMethod.POST, null, PassportRefreshTokenResponse.class) + .getBody(); + } + + public Map getPassportClientRegistrations() { + Map passportProviderDetails = new HashMap<>(); + + inMemoryClientRegistrationRepository.forEach( + clientRegistration -> { + if (clientRegistration.getScopes().contains(GA4GH_PASSPORT_SCOPE)) { + passportProviderDetails.put( + clientRegistration.getProviderDetails().getIssuerUri(), clientRegistration); + } + }); + return passportProviderDetails; + } + + private RestTemplate getTemplate(ClientRegistration clientRegistration) { + RestTemplate restTemplate = new RestTemplate(); + restTemplate + .getInterceptors() + .add( + (x, y, z) -> { + x.getHeaders() + .set( + HttpHeaders.AUTHORIZATION, + "Basic " + + getBasicAuthHeader( + clientRegistration.getClientId(), + clientRegistration.getClientSecret())); + return z.execute(x, y); + }); + return restTemplate; + } + + private String getBasicAuthHeader(String clientId, String clientSecret) { + String credentials = clientId + ":" + clientSecret; + return new String(Base64.encodeBase64(credentials.getBytes())); + } + + private MultiValueMap passportTokenParams(String accessToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("requested_token_type", TOKEN_TYPE_PASSPORT); + params.add("subject_token", accessToken); + params.add("subject_token_type", TOKEN_TYPE_ACCESS_TOKEN); + params.add("grant_type", GRANT_TYPE_TOKEN_EXCHANGE); + return params; + } + + private MultiValueMap refreshPassportTokenParams( + ClientRegistration clientRegistration, String refreshJwtToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", clientRegistration.getClientId()); + params.add("client_secret", clientRegistration.getClientSecret()); + params.add("refresh_token", refreshJwtToken); + params.add("grant_type", REFRESH_TOKEN); + return params; + } + + @SneakyThrows + public PassportRefreshToken getRefreshTokenClaims(String jwtToken, User user) { + isValidPassport( + jwtToken, + this.getPassportClientRegistrations().get(user.getProviderIssuerUri()).getRegistrationId()); + + return parseRefreshToken(jwtToken); + } +} diff --git a/src/main/java/bio/overture/ego/service/PolicyService.java b/src/main/java/bio/overture/ego/service/PolicyService.java index 92fb6122e..f0d249947 100644 --- a/src/main/java/bio/overture/ego/service/PolicyService.java +++ b/src/main/java/bio/overture/ego/service/PolicyService.java @@ -7,7 +7,7 @@ import static bio.overture.ego.model.exceptions.RequestValidationException.checkRequestValid; import static bio.overture.ego.model.exceptions.UniqueViolationException.checkUnique; import static bio.overture.ego.utils.FieldUtils.onUpdateDetected; -import static javax.persistence.criteria.JoinType.LEFT; +import static jakarta.persistence.criteria.JoinType.LEFT; import static org.mapstruct.factory.Mappers.getMapper; import bio.overture.ego.event.token.ApiKeyEventsPublisher; diff --git a/src/main/java/bio/overture/ego/service/RefreshContextService.java b/src/main/java/bio/overture/ego/service/RefreshContextService.java index 0a71991a8..72fdbdd97 100644 --- a/src/main/java/bio/overture/ego/service/RefreshContextService.java +++ b/src/main/java/bio/overture/ego/service/RefreshContextService.java @@ -6,18 +6,21 @@ import static bio.overture.ego.model.exceptions.UniqueViolationException.checkUnique; import bio.overture.ego.model.domain.RefreshContext; +import bio.overture.ego.model.dto.PassportRefreshToken; import bio.overture.ego.model.entity.RefreshToken; import bio.overture.ego.model.entity.User; import bio.overture.ego.model.exceptions.ForbiddenException; +import bio.overture.ego.model.exceptions.UnauthorizedException; import bio.overture.ego.repository.RefreshTokenRepository; import bio.overture.ego.repository.queryspecification.builder.RefreshTokenSpecificationBuilder; +import jakarta.servlet.http.Cookie; import java.sql.Date; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Optional; import java.util.UUID; -import javax.servlet.http.Cookie; import lombok.NonNull; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; @@ -30,6 +33,7 @@ public class RefreshContextService extends AbstractBaseService { private final ApplicationService applicationService; private final ApiKeyStoreService apiKeyStoreService; private final PolicyService policyService; + private final PassportService passportService; private final UserRepository userRepository; /** Configuration */ @@ -115,6 +109,7 @@ public TokenService( @NonNull ApplicationService applicationService, @NonNull ApiKeyStoreService apiKeyStoreService, @NonNull PolicyService policyService, + @NonNull PassportService passportService, @NonNull TokenStoreRepository tokenStoreRepository, @NonNull UserRepository userRepository, @Value("${jwt.user.durationMs:10800000}") int userJwtDuration, @@ -126,6 +121,7 @@ public TokenService( this.applicationService = applicationService; this.apiKeyStoreService = apiKeyStoreService; this.policyService = policyService; + this.passportService = passportService; this.userRepository = userRepository; this.userJwtDuration = userJwtDuration; this.appJwtDuration = appJwtDuration; @@ -138,8 +134,35 @@ public ApiKey getWithRelationships(@NonNull UUID id) { } public String generateUserToken(IDToken idToken) { + return generateUserToken(idToken, null, null); + } + + public String generateUserToken(IDToken idToken, String passportJwtToken, String providerType) { val user = userService.getUserByToken(idToken); - return generateUserToken(user); + if (passportJwtToken == null || providerType == null) { + return generateUserToken(user); + } + + return generateUserToken(user, passportJwtToken, providerType); + } + + public String generatePassportEgoToken(User user, String passportAccessToken, String registrationId){ + val passportJwtToken = + passportService.getPassportToken( + registrationId, passportAccessToken); + + val idToken = + IDToken.builder() + .providerSubjectId(user.getProviderSubjectId()) + .email(user.getEmail()) + .familyName(user.getLastName()) + .givenName(user.getFirstName()) + .providerType(user.getProviderType()) + .providerIssuerUri(user.getProviderIssuerUri()) + .build(); + + return generateUserToken( + idToken, passportJwtToken, registrationId); } public String updateUserToken(String accessToken) { @@ -168,13 +191,16 @@ public String generateUserToken(User u) { } @SneakyThrows - public String generateUserToken(UUID userId) { - val user = userService.findById(userId); - if (user.isEmpty()) { - throw new NotFoundException("user not found"); + public String generateUserToken(User u, String passportJwtToken, String providerType) { + + Set scopes = extractExplicitScopes(u); + + if (passportJwtToken != null && providerType != null) { + Set scopesFromVisas = extractExplicitScopes(passportJwtToken, providerType); + scopes = mergeScopes(scopes, scopesFromVisas); } - val u = user.get(); - return generateUserToken(u, extractExplicitScopes(u)); + + return generateUserToken(u, scopes); } public Set getScopes(Set scopeNames) { @@ -266,7 +292,7 @@ public String generateApiKeyString() { return UUID.randomUUID().toString(); } - public String generateUserToken(@NonNull User u, @NonNull Set scope) { + private String generateUserToken(@NonNull User u, @NonNull Set scope) { val tokenClaims = generateUserTokenClaims(u, scope); return getSignedToken(tokenClaims); } @@ -580,6 +606,21 @@ private static Set extractExplicitScopes(Application a) { return mapToSet(explicitScopes(extractScopes(a)), Scope::toString); } + @SneakyThrows + private Set extractExplicitScopes(String passportJwtToken, String providerType) { + return mapToSet( + explicitScopes(passportService.extractScopes(passportJwtToken, providerType)), + Scope::toString); + } + + private Set mergeScopes(Set scopeSet, Set scopeSetAdditional) { + scopeSet.addAll(scopeSetAdditional); + if (scopeSet.size() > 1 && scopeSet.contains(Scope.defaultScope().toString())) { + scopeSet.remove(Scope.defaultScope().toString()); + } + return scopeSet; + } + /** DEPRECATED: To be removed in next major release */ @Deprecated private TokenResponse createTokenResponse(@NonNull ApiKey token) { diff --git a/src/main/java/bio/overture/ego/service/UserService.java b/src/main/java/bio/overture/ego/service/UserService.java index 04dcb9d0e..c6141bc9d 100644 --- a/src/main/java/bio/overture/ego/service/UserService.java +++ b/src/main/java/bio/overture/ego/service/UserService.java @@ -51,8 +51,8 @@ import bio.overture.ego.token.IDToken; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import jakarta.transaction.Transactional; import java.util.*; -import javax.transaction.Transactional; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -175,6 +175,7 @@ public User createFromIDToken(IDToken idToken) { .type(userDefaultsConfig.getDefaultUserType()) .providerType(idToken.getProviderType()) .providerSubjectId(idToken.getProviderSubjectId()) + .providerIssuerUri(idToken.getProviderIssuerUri()) .build()); } diff --git a/src/main/java/bio/overture/ego/service/VisaPermissionService.java b/src/main/java/bio/overture/ego/service/VisaPermissionService.java new file mode 100644 index 000000000..b75b48735 --- /dev/null +++ b/src/main/java/bio/overture/ego/service/VisaPermissionService.java @@ -0,0 +1,121 @@ +package bio.overture.ego.service; + +import static java.lang.String.format; +import static org.mapstruct.factory.Mappers.getMapper; + +import bio.overture.ego.model.dto.VisaPermissionRequest; +import bio.overture.ego.model.entity.Visa; +import bio.overture.ego.model.entity.VisaPermission; +import bio.overture.ego.model.exceptions.NotFoundException; +import bio.overture.ego.repository.VisaPermissionRepository; +import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ReportingPolicy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +public class VisaPermissionService extends AbstractNamedService { + /** Dependencies */ + @Autowired private VisaService visaService; + + @Autowired private PolicyService policyService; + @Autowired private VisaPermissionRepository visaPermissionRepository; + private static final VisaPermissionConverter VISA_PERMISSION_CONVERTER = + getMapper(VisaPermissionConverter.class); + + @Autowired + public VisaPermissionService( + @NonNull VisaPermissionRepository visaPermissionRepository, + @NonNull VisaService visaService) { + super(VisaPermission.class, visaPermissionRepository); + this.visaPermissionRepository = visaPermissionRepository; + this.visaService = visaService; + } + + public List getPermissionsByPolicyId(@NonNull UUID policyId) { + val result = (List) visaPermissionRepository.findByPolicy_Id(policyId); + if (result.isEmpty()) { + throw new NotFoundException(format("No VisaPermissions exists with policyId '%s'", policyId)); + } + return result; + } + + public VisaPermission createOrUpdatePermissions( + @NonNull VisaPermissionRequest visaPermissionRequest) { + VisaPermission visaPermission = null; + List visaPermissionEntities = + visaPermissionRepository.findByPolicyIdAndVisaId( + visaPermissionRequest.getPolicyId(), visaPermissionRequest.getVisaId()); + if (visaPermissionEntities.isEmpty()) { + visaPermission = new VisaPermission(); + visaPermission.setVisa(visaService.getById(visaPermissionRequest.getVisaId())); + visaPermission.setPolicy(policyService.getById(visaPermissionRequest.getPolicyId())); + visaPermission.setAccessLevel(visaPermissionRequest.getAccessLevel()); + return visaPermissionRepository.save(visaPermission); + } else { + VISA_PERMISSION_CONVERTER.updateVisaPermission( + visaPermissionRequest, visaPermissionEntities.get(0)); + return visaPermissionRepository.save(visaPermissionEntities.get(0)); + } + } + + public void removePermission(@NonNull UUID policyId, @NotNull UUID visaId) { + + val visa = visaService.getById(visaId); + + if(visa == null) { + throw new NotFoundException(format("No Visa exists with id '%s'", visaId)); + } + + val visaPermissionEntities = visaPermissionRepository.findByPolicyIdAndVisaId(policyId, visa.getId()); + + if(visaPermissionEntities.isEmpty()){ + throw new NotFoundException(format("No VisaPermissions exists with policyId '%s'", policyId)); + } + + visaPermissionRepository.deleteById(visaPermissionEntities.get(0).getId()); + + } + + // Fetches visa permissions for given visa request + public List getPermissionsForVisa(@NonNull List visas) { + List visaPermissions = new ArrayList<>(); + visas.stream() + .distinct() + .forEach( + visa -> { + visaPermissions.addAll(visaPermissionRepository.findByVisa_Id(visa.getId())); + }); + return visaPermissions; + } + + @Override + public VisaPermission getById(@NonNull UUID uuid) { + return super.getById(uuid); + } + + @Override + public VisaPermission getWithRelationships(UUID uuid) { + return null; + } + + @Mapper( + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + unmappedTargetPolicy = ReportingPolicy.WARN) + public abstract static class VisaPermissionConverter { + public abstract void updateVisaPermission( + VisaPermissionRequest visaPermissionRequest, @MappingTarget VisaPermission visaPermission); + } +} diff --git a/src/main/java/bio/overture/ego/service/VisaService.java b/src/main/java/bio/overture/ego/service/VisaService.java new file mode 100644 index 000000000..dab688d71 --- /dev/null +++ b/src/main/java/bio/overture/ego/service/VisaService.java @@ -0,0 +1,142 @@ +package bio.overture.ego.service; + +import static bio.overture.ego.model.exceptions.NotFoundException.checkNotFound; +import static bio.overture.ego.model.exceptions.RequestValidationException.checkRequestValid; +import static java.lang.String.format; +import static org.mapstruct.factory.Mappers.getMapper; + +import bio.overture.ego.model.dto.PassportVisa; +import bio.overture.ego.model.dto.VisaRequest; +import bio.overture.ego.model.entity.Visa; +import bio.overture.ego.model.exceptions.NotFoundException; +import bio.overture.ego.repository.VisaRepository; +import bio.overture.ego.utils.CacheUtil; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.constraints.NotNull; +import java.security.interfaces.RSAPublicKey; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.commons.codec.binary.Base64; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ReportingPolicy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +public class VisaService extends AbstractNamedService { + /** Constants */ + private static final VisaConverter VISA_CONVERTER = getMapper(VisaConverter.class); + + /** Dependencies */ + @Autowired private VisaRepository visaRepository; + + @Autowired private CacheUtil cacheUtil; + + + @Autowired + public VisaService( + @NonNull VisaRepository visaRepository) { + super(Visa.class, visaRepository); + this.visaRepository = visaRepository; + } + + public Visa create(@NonNull VisaRequest createRequest) { + checkRequestValid(createRequest); + val visa = VISA_CONVERTER.convertToVisa(createRequest); + return getRepository().save(visa); + } + + @Override + public Visa getById(@NonNull UUID uuid) { + val result = (Optional) getRepository().findById(uuid); + checkNotFound(result.isPresent(), "The visaId '%s' does not exist", uuid); + return result.get(); + } + + public List getByTypeAndValue(@NonNull String type, @NotNull String value) { + val result = visaRepository.getByTypeAndValue(type, value); + if (!result.isEmpty()) { + return result; + } + return null; + } + + public void delete(@NonNull String type, @NotNull String value) { + List visas = getByTypeAndValue(type, value); + if (visas != null && !visas.isEmpty()) { + visas.stream().forEach(visa -> visaRepository.delete(visa)); + } else { + throw new NotFoundException( + format("No Visa exists with type '%s' and value '%s'", type, value)); + } + } + + // Parses Visa JWT token to convert into Visa Object + public PassportVisa parseVisa(@NonNull String visaJwtToken) throws JsonProcessingException { + String[] split_string = visaJwtToken.split("\\."); + String base64EncodedHeader = split_string[0]; + String base64EncodedBody = split_string[1]; + String base64EncodedSignature = split_string[2]; + Base64 base64Url = new Base64(true); + String header = new String(base64Url.decode(base64EncodedHeader)); + String body = new String(base64Url.decode(base64EncodedBody)); + return new ObjectMapper().readValue(body, PassportVisa.class); + } + + // Checks if the visa is a valid visa + public void isValidVisa(@NonNull String authToken, @NonNull String providerType) + throws JwkException, JsonProcessingException { + DecodedJWT jwt = JWT.decode(authToken); + Jwk jwk = cacheUtil.getPassportBrokerPublicKey(providerType).get(jwt.getKeyId()); + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null); + algorithm.verify(jwt); + } + + public Page listVisa(@NonNull Pageable pageable) { + return visaRepository.findAll(pageable); + } + + public Visa update( + @NotNull UUID visaId, @NonNull VisaRequest updateRequest) { + Visa updateVisa = getById(visaId); + if (updateVisa != null) { + updateVisa.setType(updateRequest.getType()); + updateVisa.setValue(updateRequest.getValue()); + updateVisa.setBy(updateRequest.getBy()); + updateVisa.setSource(updateRequest.getSource()); + return getRepository().save(updateVisa); + + } else { + throw new NotFoundException( + format("No Visa exists with id '%s'", visaId)); + } + } + + @Mapper( + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + unmappedTargetPolicy = ReportingPolicy.WARN) + public abstract static class VisaConverter { + public abstract Visa convertToVisa(VisaRequest request); + } + + @Override + public Visa getWithRelationships(UUID uuid) { + return null; + } +} diff --git a/src/main/java/bio/overture/ego/token/CustomTokenEnhancer.java b/src/main/java/bio/overture/ego/token/CustomTokenEnhancer.java index aa44c8ee8..9aba737d8 100644 --- a/src/main/java/bio/overture/ego/token/CustomTokenEnhancer.java +++ b/src/main/java/bio/overture/ego/token/CustomTokenEnhancer.java @@ -26,8 +26,8 @@ import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; +import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.stereotype.Component; // This class is responsible to modify and customize the jwt claims diff --git a/src/main/java/bio/overture/ego/token/IDToken.java b/src/main/java/bio/overture/ego/token/IDToken.java index 8a0eca68c..92ce43686 100644 --- a/src/main/java/bio/overture/ego/token/IDToken.java +++ b/src/main/java/bio/overture/ego/token/IDToken.java @@ -43,4 +43,6 @@ public class IDToken { @JsonProperty("provider_subject_id") @NonNull String providerSubjectId; + + private String providerIssuerUri; } diff --git a/src/main/java/bio/overture/ego/token/signer/DefaultTokenSigner.java b/src/main/java/bio/overture/ego/token/signer/DefaultTokenSigner.java index 478d5cd7c..b9edb2ad9 100644 --- a/src/main/java/bio/overture/ego/token/signer/DefaultTokenSigner.java +++ b/src/main/java/bio/overture/ego/token/signer/DefaultTokenSigner.java @@ -16,13 +16,13 @@ package bio.overture.ego.token.signer; +import jakarta.annotation.PostConstruct; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Optional; -import javax.annotation.PostConstruct; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/token/signer/JKSTokenSigner.java b/src/main/java/bio/overture/ego/token/signer/JKSTokenSigner.java index 395ba4b1a..5b53b403f 100644 --- a/src/main/java/bio/overture/ego/token/signer/JKSTokenSigner.java +++ b/src/main/java/bio/overture/ego/token/signer/JKSTokenSigner.java @@ -16,12 +16,12 @@ package bio.overture.ego.token.signer; +import jakarta.annotation.PostConstruct; import java.io.FileInputStream; import java.io.IOException; import java.security.*; import java.util.Base64; import java.util.Optional; -import javax.annotation.PostConstruct; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.val; diff --git a/src/main/java/bio/overture/ego/utils/CacheUtil.java b/src/main/java/bio/overture/ego/utils/CacheUtil.java new file mode 100644 index 000000000..ac9d43b9c --- /dev/null +++ b/src/main/java/bio/overture/ego/utils/CacheUtil.java @@ -0,0 +1,68 @@ +package bio.overture.ego.utils; + +import static bio.overture.ego.model.exceptions.InternalServerException.buildInternalServerException; +import static org.springframework.http.HttpMethod.GET; + +import com.auth0.jwk.Jwk; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +@Slf4j +@Component +public class CacheUtil { + + @Autowired + private ClientRegistrationRepository clientRegistrationRepository; + + private RestTemplate restTemplate; + + @Cacheable("getPassportBrokerPublicKey") + public Map getPassportBrokerPublicKey(String providerType) { + ResponseEntity> response; + Map jwkMap = new HashMap(); + + val clientRegistration = clientRegistrationRepository.findByRegistrationId(providerType); + try { + restTemplate = new RestTemplate(); + response = + restTemplate.exchange( + clientRegistration.getProviderDetails().getJwkSetUri(), + GET, + null, + new ParameterizedTypeReference>() {}); + } catch (HttpStatusCodeException err) { + log.error( + "Invalid {} response from passport broker config: {}", + err.getStatusCode().value(), + err.getMessage()); + throw buildInternalServerException( + "Invalid %s response from passport broker config.", err.getStatusCode().value()); + } + for (Object obj : response.getBody().get("keys")) { + Jwk jwkObject = Jwk.fromValues((Map) obj); + jwkMap.put(jwkObject.getId(), jwkObject); + } + return jwkMap; + } + + @CacheEvict(value = "evictAll", allEntries = true) + public void evictAllCaches() {} + + @Scheduled(fixedRate = 6000) + public void evictAllCachesEveryDay() { + evictAllCaches(); + } +} diff --git a/src/main/java/bio/overture/ego/utils/HibernateSessions.java b/src/main/java/bio/overture/ego/utils/HibernateSessions.java index fe6d67920..8e0ff6a49 100644 --- a/src/main/java/bio/overture/ego/utils/HibernateSessions.java +++ b/src/main/java/bio/overture/ego/utils/HibernateSessions.java @@ -6,7 +6,7 @@ import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.hibernate.collection.internal.AbstractPersistentCollection; +import org.hibernate.collection.spi.AbstractPersistentCollection; @Slf4j public class HibernateSessions { diff --git a/src/main/java/bio/overture/ego/utils/SwaggerConstants.java b/src/main/java/bio/overture/ego/utils/SwaggerConstants.java index 6ea6e25b3..1aee5d3d3 100644 --- a/src/main/java/bio/overture/ego/utils/SwaggerConstants.java +++ b/src/main/java/bio/overture/ego/utils/SwaggerConstants.java @@ -1,6 +1,19 @@ package bio.overture.ego.utils; +import java.util.Set; + public class SwaggerConstants { public static final String AUTH_CONTROLLER = "auth-controller"; public static final String POST_ACCESS_TOKEN = "postAccessToken"; + + public static final String SECURITY_SCHEME_NAME = "Bearer"; + + public static final Set POST_ACCESS_TOKEN_PARAMS = + Set.of("client_secret", "client_id", "grant_type"); + public static final Set APPLICATION_SCOPED_PATHS = + Set.of( + "/o/check_api_key", + "/o/check_token", + "/transaction/group_permissions", + "/transaction/mass_delete"); } diff --git a/src/main/java/bio/overture/ego/utils/TypeUtils.java b/src/main/java/bio/overture/ego/utils/TypeUtils.java index 1ac2575d8..6ecff3bed 100644 --- a/src/main/java/bio/overture/ego/utils/TypeUtils.java +++ b/src/main/java/bio/overture/ego/utils/TypeUtils.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.util.regex.Pattern; import lombok.val; public class TypeUtils { @@ -40,4 +41,14 @@ public static T convertToAnotherType(Object fromObject, Class tClass) { mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return mapper.convertValue(fromObject, tClass); } + + private static final Pattern UUID_REGEX_PATTERN = + Pattern.compile("^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"); + + public static boolean isValidUUID(String str) { + if (str == null) { + return false; + } + return UUID_REGEX_PATTERN.matcher(str).matches(); + } } diff --git a/src/main/java/db/migration/V1_1__complete_uuid_migration.java b/src/main/java/db/migration/V1_1__complete_uuid_migration.java index 9a7a8d254..45556a3e7 100644 --- a/src/main/java/db/migration/V1_1__complete_uuid_migration.java +++ b/src/main/java/db/migration/V1_1__complete_uuid_migration.java @@ -8,13 +8,15 @@ import java.util.UUID; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.flywaydb.core.api.migration.spring.SpringJdbcMigration; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; @Slf4j -public class V1_1__complete_uuid_migration implements SpringJdbcMigration { - public void migrate(JdbcTemplate jdbcTemplate) throws Exception { +public class V1_1__complete_uuid_migration extends BaseJavaMigration { + public void migrate(Context context) throws Exception { log.info( "Flyway java migration: V1_1__complete_uuid_migration running ******************************"); @@ -24,6 +26,10 @@ public void migrate(JdbcTemplate jdbcTemplate) throws Exception { UUID userOneId = UUID.randomUUID(); UUID userTwoId = UUID.randomUUID(); + JdbcTemplate jdbcTemplate = + new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true)); + ; + // Test data (if set to true) if (runWithTest) { createTestData(jdbcTemplate, userOneId, userTwoId); diff --git a/src/main/java/db/migration/V1_3__string_to_date.java b/src/main/java/db/migration/V1_3__string_to_date.java index 0146de4ca..963a2059b 100644 --- a/src/main/java/db/migration/V1_3__string_to_date.java +++ b/src/main/java/db/migration/V1_3__string_to_date.java @@ -7,21 +7,26 @@ import java.util.UUID; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.flywaydb.core.api.migration.spring.SpringJdbcMigration; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; @Slf4j -public class V1_3__string_to_date implements SpringJdbcMigration { +public class V1_3__string_to_date extends BaseJavaMigration { @Override - public void migrate(JdbcTemplate jdbcTemplate) throws Exception { + public void migrate(Context context) throws Exception { log.info("Flyway java migration: V1_3__string_to_date running ******************************"); boolean runWithTest = false; UUID userOneId = UUID.randomUUID(); UUID userTwoId = UUID.randomUUID(); + JdbcTemplate jdbcTemplate = + new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true)); + if (runWithTest) { createTestData(jdbcTemplate, userOneId, userTwoId); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 47cbd5234..cdbf3906c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,6 +27,15 @@ refreshToken: cookieIsSecure: false domain: localhost +# Swagger +springdoc: + packagesToScan: bio.overture.ego.controller + swagger-ui: + display-operation-id: true + doc-expansion: none # initial collapse groups + operations-sorter: alpha + tags-sorter: alpha + # security auth: token: @@ -50,23 +59,37 @@ spring: client: registration: google: + provider: google clientName: ego clientId: ego-client clientSecret: authorizationGrantType: authorization_code - clientAuthenticationMethod: post + clientAuthenticationMethod: client_secret_post redirectUri: "http://localhost:8081/oauth/code/google" scope: - openid - email - profile + # Passport brokers must have ga4gh_passport_v1 scope + passport: + clientId: ego-client + clientSecret: + redirectUri: "http://localhost:8081/oauth/code/elixir" + authorizationGrantType: authorization_code + scope: + - openid + - email + - profile + - offline_access + - ga4gh_passport_v1 + github: clientName: ego clientId: ego-client clientSecret: redirectUri: "http://localhost:8081/oauth/code/github" - clientAuthenticationMethod: post + clientAuthenticationMethod: client_secret_post authorizationGrantType: authorization_code scope: - "read:user" @@ -119,9 +142,14 @@ spring: userNameAttribute: sub userRecordUri: https://pub.orcid.org/v2.0 jwkSetUri: https://orcid.org/oauth/jwks + passport: + # OIDC discovery endpoint + issuer-uri: https://login.elixir-czech.org/oidc/ cloud: vault: - enabled: false + kv: + backend: kv + enabled: true flyway: enabled: true locations: "classpath:flyway/sql,classpath:db/migration" @@ -172,6 +200,7 @@ token: private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSU6oy48sJW6xzqzOSU1dAvUUeFKQSBHsCf7wGWUGpOxEczhtFiiyx4YUJtg+fyvwWxa4wO3GnQLBPIxBHY8JsnvjQN2lsTUoLqMB9nGpwF617uA/S2igm1u+cDpfi82kbi6SG1Sg30PM047R6oxTRGDLLkeMRF1gRaTBM0HfSL0j6ccU5KPgwYsFLE2We6jeR56iYJGC2KYLH4v8rcc2jRAdMbUntHMtUByF9BPSW7elQnyQH5Qzr/o0b59XLKwnJFn2Bp2yviC8cdyTDyhQGna0e+oESQR1j6u3Ux/mOmm3slRXscA8sH+pHmOEAtjYVf/ww36U8uZv+ctBCJyFVAgMBAAECggEBALrEeJqAFUfWFCkSmdUSFKT0bW/svFUTjXgGnZy1ncz9GpENpMH3lQDQVibteKpYwcom+Cr0XlQ66VUcudPrDjcOY7vhuMfnSh1YWLYyM4IeRHtcUxDVkFoM+vEFNHLf2zIOqqbgmboW3iDVIurT7iRO7KxAe/YtWJL9aVqMtBn7Lu7S7OvAU4ji5iLIBxjl82JYA+9lu/aQ6YGaoZuSO7bcU8Sivi+DKAahqN9XMKiB1XpC+PpaS/aec2S7xIlTdzoDGxEALRGlMe+xBEeQTBVJHBWrRIDPoHLTREeRC/9Pp+1Y4Dz8hd5Bi0n8/5r/q0liD+0vtmjsdU4E2QrktYECgYEA73qWvhCYHPMREAFtwz1mpp9ZhDCW6SF+njG7fBKcjz8OLcy15LXiTGc268ewtQqTMjPQlm1n2C6hGccGAIlMibQJo3KZHlTs125FUzDpTVgdlei6vU7M+gmfRSZed00J6jC04/qMR1tnV3HME3np7eRTKTA6Ts+zBwEvkbCetSkCgYEA4NY5iSBO1ybouIecDdD15uI2ItLPCBNMzu7IiK7IygIzuf+SyKyjhtFSR4vEi0gScOM7UMlwCMOVU10e4nMDknIWCDG9iFvmIEkGHGxgRrN5hX1Wrq74wF212lvvagH1IVWSHa8cVpMe+UwKu5Q1h4yzuYt6Q9wPQ7Qtn5emBE0CgYB2syispMUA9GnsqQii0Xhj9nAEWaEzhOqhtrzbTs5TIkoA4Yr3BkBY5oAOdjhcRBWZuJ0XMrtaKCKqCEAtW+CYEKkGXvMOWcHbNkkeZwv8zkQ73dNRqhFnjgVn3RDNyV20uteueK23YNLkQP+KV89fnuCpdcIw9joiqq/NYuIHoQKBgB5WaZ8KH/lCA8babYEjv/pubZWXUl4plISbja17wBYZ4/bl+F1hhhMr7Wk//743dF2NG7TT6W0VTvHXr9IoaMP65uQmKgfbNpsGn294ZClGEFClz+t0KpZyTpZvL0fjibr8u+GLfkxkP5qt2wjif7KRlrKjklTTva+KAVn2cW1FAoGBAMkX9ekIwhx/7uY6ndxKl8ZMDerjr6MhV0b08hHp3RxHbYVbcpN0UKspoYvZVgHwP18xlDij8yWRE2fapwgi4m82ZmYlg0qqJmyqIU9vBB3Jow903h1KPQrkmQEZxJ/4H8yrbgVf2HT+WUfjTFgaDZRl01bI3YkydCw91/Ub9HU6 public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lOqMuPLCVusc6szklNXQL1FHhSkEgR7An+8BllBqTsRHM4bRYosseGFCbYPn8r8FsWuMDtxp0CwTyMQR2PCbJ740DdpbE1KC6jAfZxqcBete7gP0tooJtbvnA6X4vNpG4ukhtUoN9DzNOO0eqMU0Rgyy5HjERdYEWkwTNB30i9I+nHFOSj4MGLBSxNlnuo3keeomCRgtimCx+L/K3HNo0QHTG1J7RzLVAchfQT0lu3pUJ8kB+UM6/6NG+fVyysJyRZ9gadsr4gvHHckw8oUBp2tHvqBEkEdY+rt1Mf5jppt7JUV7HAPLB/qR5jhALY2FX/8MN+lPLmb/nLQQichVQIDAQAB + # Default values available for creation of entities default: user: diff --git a/src/main/resources/flyway/sql/V1_22__add_visa.sql b/src/main/resources/flyway/sql/V1_22__add_visa.sql new file mode 100644 index 000000000..bf99727bf --- /dev/null +++ b/src/main/resources/flyway/sql/V1_22__add_visa.sql @@ -0,0 +1,8 @@ +CREATE TABLE GA4GHVISA ( + id UUID PRIMARY KEY, + type varchar(255) NOT NULL, + source varchar(255) NOT NULL, + value varchar(255) NOT NULL, + by varchar(255) NOT NULL +); + diff --git a/src/main/resources/flyway/sql/V1_23__add_visa_pcl.sql b/src/main/resources/flyway/sql/V1_23__add_visa_pcl.sql new file mode 100644 index 000000000..0d2e780c8 --- /dev/null +++ b/src/main/resources/flyway/sql/V1_23__add_visa_pcl.sql @@ -0,0 +1,8 @@ +CREATE TABLE ACLVISAPERMISSION ( + id UUID PRIMARY KEY, + policy_id UUID, + visa_id UUID, + access_level ACLMASK NOT NULL, + FOREIGN KEY (policy_id) REFERENCES POLICY(id), + FOREIGN KEY (visa_id) REFERENCES GA4GHVISA(id) +); diff --git a/src/main/resources/flyway/sql/V1_24__add_passport_identity_provider.sql b/src/main/resources/flyway/sql/V1_24__add_passport_identity_provider.sql new file mode 100644 index 000000000..6f107e538 --- /dev/null +++ b/src/main/resources/flyway/sql/V1_24__add_passport_identity_provider.sql @@ -0,0 +1 @@ +ALTER TYPE providertype ADD VALUE 'PASSPORT'; \ No newline at end of file diff --git a/src/main/resources/flyway/sql/V1_25__add_provider_issuer_uri.sql b/src/main/resources/flyway/sql/V1_25__add_provider_issuer_uri.sql new file mode 100644 index 000000000..b9a55f3b8 --- /dev/null +++ b/src/main/resources/flyway/sql/V1_25__add_provider_issuer_uri.sql @@ -0,0 +1 @@ +ALTER TABLE egouser ADD COLUMN providerissueruri VARCHAR(255); \ No newline at end of file diff --git a/src/test/java/bio/overture/ego/controller/AbstractControllerTest.java b/src/test/java/bio/overture/ego/controller/AbstractControllerTest.java index 79bff5286..1de374b34 100644 --- a/src/test/java/bio/overture/ego/controller/AbstractControllerTest.java +++ b/src/test/java/bio/overture/ego/controller/AbstractControllerTest.java @@ -29,7 +29,7 @@ import lombok.val; import org.junit.Before; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpHeaders; @Slf4j diff --git a/src/test/java/bio/overture/ego/controller/AppJWTTest.java b/src/test/java/bio/overture/ego/controller/AppJWTTest.java index d7c285677..f428bd4ed 100644 --- a/src/test/java/bio/overture/ego/controller/AppJWTTest.java +++ b/src/test/java/bio/overture/ego/controller/AppJWTTest.java @@ -407,5 +407,4 @@ public void applicationJwtIsValidAndUsable() { val users = MAPPER.readTree(resolvedUsers); assertNotNull(users); } - } diff --git a/src/test/java/bio/overture/ego/service/initialization/InitializationServiceTest.java b/src/test/java/bio/overture/ego/service/initialization/InitializationServiceTest.java index 433c829f3..9fdeb6ac5 100644 --- a/src/test/java/bio/overture/ego/service/initialization/InitializationServiceTest.java +++ b/src/test/java/bio/overture/ego/service/initialization/InitializationServiceTest.java @@ -16,7 +16,7 @@ import bio.overture.ego.service.ApplicationService; import bio.overture.ego.service.InitializationService; import bio.overture.ego.utils.EntityGenerator; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.junit.Rule; diff --git a/src/test/java/bio/overture/ego/utils/web/AbstractWebResource.java b/src/test/java/bio/overture/ego/utils/web/AbstractWebResource.java index aaee7a2d1..e9d776630 100644 --- a/src/test/java/bio/overture/ego/utils/web/AbstractWebResource.java +++ b/src/test/java/bio/overture/ego/utils/web/AbstractWebResource.java @@ -169,8 +169,8 @@ private static void logResponse(boolean enable, boolean pretty, ResponseEnti val output = CleanResponse.builder() .body(response.hasBody() ? response.getBody() : null) - .statusCodeName(response.getStatusCode().name()) - .statusCodeValue(response.getStatusCodeValue()) + .statusCodeName(response.getStatusCode().toString()) + .statusCodeValue(response.getStatusCode().value()) .build(); if (pretty) { log.info("[RESPONSE] > \n{}", PRETTY_MAPPER.writeValueAsString(output));