diff --git a/.editorconfig b/.editorconfig index 3e3bd29bb..09345f3d5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ root = true # Change these settings to your own preference indent_style = space indent_size = 4 -continuation_indent_size = 8 +ij_continuation_indent_size = 8 # We recommend you to keep these unchanged end_of_line = lf @@ -16,9 +16,15 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[{*.kt,*.kts}] +ktlint_code_style = ktlint_official +ij_kotlin_packages_to_use_import_on_demand = java.util.* + +[{*.java}] + [*.ts] quote_type = single -continuation_indent_size = 4 +ij_continuation_indent_size = 4 [*.md] trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 379b8893b..511deb4d3 100644 --- a/.gitignore +++ b/.gitignore @@ -89,8 +89,8 @@ out/ ###################### # Gradle ###################### -.gradle/ -/build/ +.gradle +build .gradletasknamecache ###################### diff --git a/Dockerfile b/Dockerfile index bfdc087bc..3b8934d9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,9 @@ COPY .yarn /code/.yarn RUN yarn install --network-timeout 1000000 COPY gradle gradle -COPY gradlew build.gradle gradle.properties settings.gradle /code/ -COPY radar-auth/build.gradle radar-auth/ +COPY gradlew build.gradle.kts gradle.properties settings.gradle.kts /code/ +COPY radar-auth/build.gradle.kts radar-auth/ +COPY buildSrc/ buildSrc/ RUN ./gradlew downloadDependencies @@ -39,7 +40,7 @@ FROM eclipse-temurin:17-jre ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ JHIPSTER_SLEEP=0 \ - JAVA_OPTS="" + JAVA_OPTS="" # Add the war and changelogs files from build stage COPY --from=builder /code/build/libs/*.war /app.war diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 173868dd7..000000000 --- a/build.gradle +++ /dev/null @@ -1,326 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -buildscript { - repositories { - mavenCentral() - } - - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${spring_boot_version}") - } -} - -plugins { - id 'application' - id 'org.springframework.boot' version "${spring_boot_version}" - id "com.github.node-gradle.node" version "3.6.0" - id "io.spring.dependency-management" version "1.1.3" - id 'de.undercouch.download' version '5.5.0' apply false - id "io.github.gradle-nexus.publish-plugin" version "1.3.0" - id("com.github.ben-manes.versions") version "0.47.0" - id 'org.jetbrains.kotlin.jvm' version "1.9.22" - id "org.jetbrains.kotlin.kapt" version "1.9.22" - id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false - id 'org.jetbrains.dokka' version "1.8.20" - id "org.jetbrains.kotlin.plugin.allopen" version "1.9.10" -} - -apply plugin: 'org.springframework.boot' -apply plugin: 'war' -apply plugin: 'com.github.node-gradle.node' -apply plugin: 'io.spring.dependency-management' - -allprojects { - group 'org.radarbase' - version '2.1.2-SNAPSHOT' // project version - - // The comment on the previous line is only there to identify the project version line easily - // with a sed command, to auto-update the version number with the prepare-release-branch.sh - // script, do not remove it. - - apply plugin: 'java' - apply plugin: 'java-library' - apply plugin: 'idea' - - ext.githubRepoName = 'RADAR-base/ManagementPortal' - ext.githubUrl = 'https://github.com/RADAR-base/ManagementPortal' - ext.website = 'https://radar-base.org' - - repositories { - mavenCentral() - - // This repo is used temporarily when we use an internal SNAPSHOT library version. - // When we publish the library, we comment out the repository again to speed up dependency resolution. -// maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } - } - - idea { - module { - downloadSources = true - } - } - - tasks.register("ghPages") { - dependsOn(provider { - tasks.findAll { task -> task.name.startsWith("ghPages") && task.name != "ghPages" } - }) - } -} - -description = 'MangementPortal application to manage studies and participants' - -defaultTasks 'bootRun' - -configurations { - compile.exclude module: "spring-boot-starter-tomcat" -} - -application { - mainClass.set('org.radarbase.management.ManagementPortalApp') - applicationDefaultJvmArgs = [ - '--add-modules', 'java.se', - '--add-exports', 'java.base/jdk.internal.ref=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '--add-opens', 'java.base/java.nio=ALL-UNNAMED', - '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', - '--add-opens', 'java.management/sun.management=ALL-UNNAMED', - '--add-opens', 'jdk.management/com.sun.management.internal=ALL-UNNAMED', - ] -} - -bootWar { - launchScript() -} - -springBoot { - buildInfo() -} - - -bootRun { - sourceResources sourceSets.main -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -tasks.withType(KotlinCompile).configureEach { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - apiVersion = KotlinVersion.KOTLIN_1_8 - languageVersion = KotlinVersion.KOTLIN_1_8 - } -} - -test { - jvmArgs = [ - '--add-modules', 'java.se', - '--add-exports', 'java.base/jdk.internal.ref=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '--add-opens', 'java.base/java.nio=ALL-UNNAMED', - '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', - '--add-opens', 'java.management/sun.management=ALL-UNNAMED', - '--add-opens', 'jdk.management/com.sun.management.internal=ALL-UNNAMED', - ] - testLogging { - exceptionFormat = 'full' - } - useJUnitPlatform() -} - -apply from: 'gradle/liquibase.gradle' -apply from: 'gradle/gatling.gradle' -apply from: 'gradle/mapstruct.gradle' -apply from: 'gradle/docker.gradle' -apply from: 'gradle/style.gradle' -apply from: 'gradle/openapi.gradle' - -if (project.hasProperty('prod')) { - apply from: 'gradle/profile_prod.gradle' -} else { - apply from: 'gradle/profile_dev.gradle' -} - -//ext.moduleDescription = 'Management Portal application' -ext.findbugAnnotationVersion = '3.0.2' - -dependencies { - implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}") - implementation("tech.jhipster:jhipster-framework:${jhipster_server_version}") - implementation("tech.jhipster:jhipster-dependencies:${jhipster_server_version}") - implementation("io.micrometer:micrometer-core:${micrometer_version}") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutines_version") - implementation(platform("io.ktor:ktor-bom:$ktor_version")) - implementation("io.ktor:ktor-client-core") - implementation("io.ktor:ktor-client-cio") - implementation("io.ktor:ktor-client-content-negotiation") - implementation("io.ktor:ktor-serialization-kotlinx-json") - implementation("com.fasterxml.jackson.core:jackson-core:${jackson_version}") - runtimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jackson_version}") - implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:${jackson_version}") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv:${jackson_version}") - implementation "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}" - implementation("com.fasterxml.jackson.core:jackson-databind:${jackson_version}") - implementation "com.hazelcast:hazelcast:${hazelcast_version}" - implementation "com.hazelcast:hazelcast-spring:${hazelcast_version}" - runtimeOnly "com.hazelcast:hazelcast-hibernate53:${hazelcast_hibernate_version}" - runtimeOnly("com.zaxxer:HikariCP:${hikaricp_version}") - implementation group: 'com.google.code.findbugs', name: 'jsr305', version: findbugAnnotationVersion - implementation("org.liquibase:liquibase-core:${liquibase_version}") - runtimeOnly "com.mattbertolini:liquibase-slf4j:${liquibase_slf4j_version}" - implementation("org.springframework.boot:spring-boot-starter-actuator") - implementation("org.springframework.boot:spring-boot-autoconfigure") - implementation "org.springframework.boot:spring-boot-starter-mail" - runtimeOnly "org.springframework.boot:spring-boot-starter-logging" - runtimeOnly("org.springframework.boot:spring-boot-starter-data-jpa") { - exclude group: 'org.hibernate', module: 'hibernate-entitymanager' - } - implementation "org.springframework.security:spring-security-data" - - implementation("org.springframework.boot:spring-boot-starter-web") { - exclude module: 'spring-boot-starter-tomcat' - } - runtimeOnly "org.springframework.boot:spring-boot-starter-security" - implementation("org.springframework.boot:spring-boot-starter-undertow") - - implementation "org.hibernate:hibernate-core" - implementation "org.hibernate:hibernate-envers" - implementation "org.hibernate:hibernate-validator:${hibernate_validator_version}" - - runtimeOnly "org.postgresql:postgresql:${postgresql_version}" - runtimeOnly "org.hsqldb:hsqldb:${hsqldb_version}" - - // Fix vulnerabilities - runtimeOnly("io.undertow:undertow-websockets-jsr:${undertow_version}") - runtimeOnly("io.undertow:undertow-servlet:${undertow_version}") - runtimeOnly("io.undertow:undertow-core:${undertow_version}") - - implementation "org.springframework.boot:spring-boot-starter-thymeleaf" - runtimeOnly("org.thymeleaf:thymeleaf:${thymeleaf_version}") - runtimeOnly("org.thymeleaf:thymeleaf-spring5:${thymeleaf_version}") - implementation("org.springframework:spring-context-support") - implementation("org.springframework.session:spring-session-hazelcast") - - implementation('org.springframework.security.oauth:spring-security-oauth2:2.5.2.RELEASE') - implementation('org.springframework.security:spring-security-web:5.7.8') - implementation "org.springdoc:springdoc-openapi-ui:${springdoc_version}" - runtimeOnly("javax.inject:javax.inject:1") - implementation project(':radar-auth') - implementation "org.springframework.data:spring-data-envers" - - implementation "org.mockito:mockito-core:$mockito_version" - implementation "org.mockito.kotlin:mockito-kotlin:${mockito_kotlin_version}" - - runtimeOnly("jakarta.xml.bind:jakarta.xml.bind-api:${javax_xml_bind_version}") - runtimeOnly("org.glassfish.jaxb:jaxb-core:${javax_jaxb_core_version}") - runtimeOnly("org.glassfish.jaxb:jaxb-runtime:${javax_jaxb_runtime_version}") - runtimeOnly("javax.activation:activation:${javax_activation}") - runtimeOnly 'org.javassist:javassist:3.29.2-GA' - - testImplementation "com.jayway.jsonpath:json-path" - testImplementation ("org.springframework.boot:spring-boot-starter-test") { - exclude group: "com.vaadin.external.google", module:"android-json" - exclude group: "junit", module: "junit" - exclude group: "org.junit.vintage", module: "junit-vintage-engine" - } - testImplementation "org.springframework.security:spring-security-test" - testImplementation "org.springframework.boot:spring-boot-test" - testImplementation "org.assertj:assertj-core:${assertj_version}" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.mockito.kotlin:mockito-kotlin:${mockito_kotlin_version}" - testImplementation "com.mattbertolini:liquibase-slf4j:${liquibase_slf4j_version}" - testImplementation "org.hamcrest:hamcrest-library" - testImplementation "org.testcontainers:testcontainers:${testcontainers_version}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" - - annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") -} - -allOpen { - annotation("org.springframework.stereotype.Component") - annotation("org.springframework.boot.test.context.SpringBootTest") - annotation("org.springframework.web.bind.annotation.RestController") -} - -dependencyManagement { - imports { - mavenBom "com.fasterxml.jackson:jackson-bom:$jackson_version" - mavenBom "org.springframework:spring-framework-bom:$spring_framework_version" - mavenBom "org.springframework.boot:spring-boot-dependencies:$spring_boot_version" - mavenBom "org.springframework.data:spring-data-bom:$spring_data_version" - mavenBom "org.springframework.session:spring-session-bom:$spring_session_version" - } -} - -clean { - delete "target" -} - -tasks.register('cleanResources', Delete) { - delete 'build/resources' -} - -wrapper { - gradleVersion '8.3' -} - -tasks.register('stage') { - dependsOn 'bootWar' -} - -tasks.register('ghPagesJavadoc', Copy) { - from javadoc.destinationDir - into file("$rootDir/public/management-portal-javadoc") - dependsOn tasks.named('javadoc') -} - -tasks.register('ghPagesOpenApiSpec', Copy) { - from file(layout.buildDirectory.dir("swagger-spec")) - into file("$rootDir/public/apidoc") -} - -compileJava.dependsOn processResources -processResources.dependsOn cleanResources, bootBuildInfo -bootBuildInfo.mustRunAfter cleanResources - -tasks.register('downloadDependencies') { - description "Pre-downloads dependencies" - configurations.compileClasspath.files - configurations.runtimeClasspath.files -} - -ext.projectLanguage = "java" -apply from: 'gradle/artifacts.gradle' - -nexusPublishing { - repositories { - sonatype { - username = project.hasProperty("ossrh.user") ? project.property("ossrh.user") : System.getenv("OSSRH_USER") - password = project.hasProperty("ossrh.password") ? project.property("ossrh.password") : System.getenv("OSSRH_PASSWORD") - } - } -} - -def isNonStable = { String version -> - def stableKeyword = ["RELEASE", "FINAL", "GA"].any { version.toUpperCase().contains(it) } - def regex = /^[0-9,.v-]+(-r)?$/ - return !stableKeyword && !(version ==~ regex) -} - -tasks.named("dependencyUpdates").configure { - doFirst { - allprojects { - repositories.removeAll { - it instanceof MavenArtifactRepository && it.url.toString().contains("snapshot") - } - } - } - rejectVersionIf { - it.currentVersion.split('\\.')[0] != it.candidate.version.split('\\.')[0] - || isNonStable(it.candidate.version) - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..aa6e2f911 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,261 @@ +plugins { + id("project-conventions") + id("org.radarbase.radar-root-project") + id("org.jetbrains.kotlin.kapt") + + alias(libs.plugins.kotlin.allopen) + + application + war + + alias(libs.plugins.undercouch.download) + alias(libs.plugins.node.gradle) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) +} + +radarRootProject { + projectVersion.set(properties["projectVersion"] as String) +} + +repositories { + mavenCentral() + // This repo can be used for local testing and development. + // Keep commented when making a PR. +// mavenLocal() + // This repo is used temporarily when we use an internal SNAPSHOT library version. + // When we publish the library, we comment out the repository again to speed up dependency resolution. +// maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } +} + +description = "MangementPortal application to manage studies and participants" + +dependencies { + // Project dependencies + implementation(project(":radar-auth")) + implementation(project(":managementportal-client")) + + // Radar dependencies + implementation(libs.radar.commons.kotlin) + + // Versions are determined by Spring Boot dependency-management plugin + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-thymeleaf") + implementation("org.springframework.boot:spring-boot-starter-mail") + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-undertow") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") { + exclude(group = "org.hibernate", module = "hibernate-entitymanager") + } + implementation("org.springframework.security:spring-security-data") + implementation("org.springframework:spring-context-support") + testImplementation("org.springframework.security:spring-security-test") + testImplementation("org.springframework.boot:spring-boot-starter-test") { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + exclude(group = "com.vaadin.external.google", module = "android-json") + } + testImplementation("org.springframework.boot:spring-boot-test") + implementation("org.springframework.session:spring-session-hazelcast") + implementation("org.springframework.boot:spring-boot-configuration-processor") + "developmentOnly"("org.springframework.boot:spring-boot-devtools") + implementation("org.springframework.data:spring-data-envers") + implementation("org.hibernate:hibernate-envers") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") + implementation("com.fasterxml.jackson.core:jackson-annotations") + implementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation("org.hamcrest:hamcrest-library") + + // Other + implementation(platform(libs.ktor.bom)) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + + implementation(platform(libs.kotlin.coroutines.bom)) + implementation(libs.kotlin.coroutines.reactor) + + implementation(libs.kotlin.reflect) + implementation(libs.spring.security.oauth) + implementation(libs.springdoc.openapi) + implementation(libs.jhipster.framework) + implementation(libs.jhipster.dependencies) + implementation(libs.hazelcast) + implementation(libs.hazelcast.spring) + implementation(libs.hibernate.validator) + runtimeOnly(libs.hazelcast.hybernate53) + runtimeOnly(libs.hikari.cp) + runtimeOnly(libs.postgresql) + implementation(libs.swagger.annotations) + runtimeOnly(libs.javax.activation) + runtimeOnly(libs.javax.inject) + implementation(libs.liquibase.core) + runtimeOnly(libs.liquibase.slf4j) + implementation(libs.google.findbugs) + testImplementation(libs.mockito.kotlin) + testImplementation(libs.wiremock) + testImplementation(libs.assertj.core) + testRuntimeOnly(libs.hsqldb) + testRuntimeOnly(libs.slf4j.simple) + testImplementation(libs.testcontainers) +} + +ext { + // This is needed to prevent conflict with junit version provided by root project... + set("junit-jupiter.version", "5.10.0") +} + +kotlin { + compilerOptions { + freeCompilerArgs = listOf("-Xjsr305=strict", "-Xjvm-default=all") + suppressWarnings = true + } +} + +allOpen { + annotation("org.springframework.stereotype.Component") + annotation("org.springframework.boot.test.context.SpringBootTest") + annotation("org.springframework.web.bind.annotation.RestController") +} + +dependencyManagement { + imports { + mavenBom("com.fasterxml.jackson:jackson-bom:${libs.versions.jackson.get()}") + mavenBom("org.springframework:spring-framework-bom:${libs.versions.springFramework.get()}") + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springBoot.get()}") + mavenBom("org.springframework.data:spring-data-bom:${libs.versions.springData.get()}") + mavenBom("org.springframework.session:spring-session-bom:${libs.versions.springSession.get()}") + } + // Prevents loading of detachedConfigurations that takes a lot of time; remove after development? + applyMavenExclusions(false) +} + +radarKotlin { + slf4jVersion.set(libs.versions.slf4j) +} + +defaultTasks("bootRun") + +configurations { + compileOnly { + exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") + } +} + +application { + mainClass.set("org.radarbase.management.ManagementPortalApp") + applicationDefaultJvmArgs = + listOf( + "--add-modules", + "java.se", + "--add-exports", + "java.base/jdk.internal.ref=ALL-UNNAMED", + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "--add-opens", + "java.base/java.nio=ALL-UNNAMED", + "--add-opens", + "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", + "java.management/sun.management=ALL-UNNAMED", + "--add-opens", + "jdk.management/com.sun.management.internal=ALL-UNNAMED", + ) +} + +springBoot { + mainClass = "org.radarbase.management.ManagementPortalApp" + buildInfo() +} + +tasks.bootWar { + launchScript() +} + +tasks.bootRun { + sourceResources(sourceSets.main.get()) +} + +tasks.test { + jvmArgs = + listOf( + "--add-modules", + "java.se", + "--add-exports", + "java.base/jdk.internal.ref=ALL-UNNAMED", + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "--add-opens", + "java.base/java.nio=ALL-UNNAMED", + "--add-opens", + "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", + "java.management/sun.management=ALL-UNNAMED", + "--add-opens", + "jdk.management/com.sun.management.internal=ALL-UNNAMED", + ) + useJUnitPlatform() +} + +apply(from = "$rootDir/gradle/liquibase.gradle") +apply(from = "$rootDir/gradle/gatling.gradle") +apply(from = "$rootDir/gradle/mapstruct.gradle") +apply(from = "$rootDir/gradle/docker.gradle") +apply(from = "$rootDir/gradle/openapi.gradle") + +if (project.hasProperty("prod")) { + apply(from = "$rootDir/gradle/profile_prod.gradle") +} else { + apply(from = "$rootDir/gradle/profile_dev.gradle") +} + +idea { + module { + isDownloadSources = true + } +} + +tasks.clean { + delete("target") +} + +tasks.register("cleanResources") { + delete("build/resources") +} + +tasks.register("stage") { + dependsOn("bootWar") +} + +tasks.register("ghPagesJavadoc") { + copySpec { + from(tasks.javadoc.get().destinationDir) + into(file("$rootDir/public/management-portal-javadoc")) + } + dependsOn(tasks.named("javadoc")) +} + +tasks.register("ghPagesOpenApiSpec") { + copySpec { + from(layout.buildDirectory.dir("swagger-spec")) + into(file("$rootDir/public/apidoc")) + } +} + +tasks.compileJava { + dependsOn("processResources") +} + +tasks.processResources { + dependsOn("cleanResources", "bootBuildInfo") +} + +tasks.named("bootBuildInfo") { + mustRunAfter("cleanResources") +} + +node { + download = true +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..1c9e8d01b --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2024 The Hyve + * * + * * 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. + * + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +dependencies { + implementation(libs.gradle.radar.kotlin) + implementation(libs.gradle.kotlin.jvm) + implementation(libs.gradle.kotlin.serialization) +} + +repositories { + gradlePluginPortal() + mavenCentral() + // This repo can be used for local testing and development. + // Keep commented when making a PR. +// mavenLocal() + // This repo is used temporarily when we use an internal SNAPSHOT library version. + // When we publish the library, we comment out the repository again to speed up dependency resolution. +// maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } +} + +tasks.withType { + compilerOptions { + jvmTarget.set(JvmTarget.fromTarget(libs.versions.java.get())) + } +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 000000000..62fa173f7 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,8 @@ +dependencyResolutionManagement { + // Allows to use the version catalog in the buildSrc module + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/buildSrc/src/main/kotlin/project-conventions.gradle.kts b/buildSrc/src/main/kotlin/project-conventions.gradle.kts new file mode 100644 index 000000000..9df5c8f29 --- /dev/null +++ b/buildSrc/src/main/kotlin/project-conventions.gradle.kts @@ -0,0 +1,42 @@ +plugins { + idea + id("org.radarbase.radar-kotlin") + id("org.radarbase.radar-publishing") + id("org.radarbase.radar-dependency-management") + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlin.plugin.serialization") +} + +group = "org.radarbase" + +radarPublishing { + githubUrl.set("https://github.com/RADAR-base/ManagementPortal") + developers { + developer { + id.set("pvanierop") + name.set("Pim van Nierop") + email.set("pim@thehyve.nl") + organization.set("The Hyve") + } + developer { + id.set("nivemaham") + name.set("Nivethika Mahasivam") + email.set("nivethika@thehyve.nl") + organization.set("The Hyve") + } + developer { + id.set("dennyverbeeck") + name.set("Denny Verbeeck") + email.set("dverbeec@its.jnj.com") + organization.set("Janssen R&D") + } + } +} + +tasks.register("ghPages") { + dependsOn( + provider { + tasks.filter { it.name.startsWith("ghPages") && it.name != "ghPages" } + }, + ) +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index d2528c77b..000000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle.properties b/gradle.properties index 6684b33e0..854db3186 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,45 +1,6 @@ -rootProject.name=management-portal +projectVersion=2.1.6-SNAPSHOT + profile=dev -assertj_version=3.23.1 -jhipster_server_version=7.9.3 -hazelcast_version=5.2.5 -hazelcast_hibernate_version=5.1.0 -hikaricp_version=5.0.1 -liquibase_slf4j_version=4.1.0 -liquibase_version=4.22.0 -postgresql_version=42.6.1 -springdoc_version=1.6.15 -spring_boot_version=2.7.15 -spring_framework_version=5.3.27 -spring_data_version=2021.2.5 -thymeleaf_version=3.1.2.RELEASE -spring_session_version=2021.2.0 -gatling_version=3.8.4 -mapstruct_version=1.5.5.Final -jackson_version=2.16.1 -javax_xml_bind_version=2.3.3 -javax_jaxb_core_version=2.3.0.1 -javax_jaxb_runtime_version=2.3.8 -javax_activation=1.1.1 -mockito_version=4.8.1 -mockito_kotlin_version=5.1.0 -slf4j_version=2.0.7 -logback_version=1.4.11 -oauth_jwt_version=4.4.0 -junit_version=5.10.0 -okhttp_version=4.10.0 -hsqldb_version=2.7.2 -coroutines_version=1.8.0 -ktor_version=2.3.9 -radar_commons_version=1.0.0 -hamcrest_version=2.2 -wiremock_version=3.0.1 -kotlin_version=1.9.22 -micrometer_version=1.12.3 -hibernate_orm_version=6.4.4.Final -hibernate_validator_version=8.0.0.Final -testcontainers_version=1.19.7 -undertow_version=2.2.34.Final kotlin.code.style=official org.gradle.vfs.watch=true diff --git a/gradle/artifacts.gradle b/gradle/artifacts.gradle deleted file mode 100644 index 967de7104..000000000 --- a/gradle/artifacts.gradle +++ /dev/null @@ -1,35 +0,0 @@ - -def sharedManifest = manifest { - attributes("Implementation-Title": project.name, - "Implementation-Version": version) -} - -jar { - manifest.from sharedManifest -} - -// custom tasks for creating source/javadoc jars -tasks.register('sourcesJar', Jar) { - archiveClassifier.set('sources') - from sourceSets.main.allSource - manifest.from sharedManifest - dependsOn(classes) -} - -if (!project.hasProperty("projectLanguage") || projectLanguage == "java") { - tasks.register('javadocJar', Jar) { - archiveClassifier.set('javadoc') - from javadoc.destinationDir - manifest.from sharedManifest - dependsOn(javadoc) - } -} else if (projectLanguage == "kotlin") { - tasks.register('javadocJar', Jar) { - from("$buildDir/dokka/javadoc") - archiveClassifier.set("javadoc") - manifest.from sharedManifest - dependsOn(dokkaJavadoc) - } -} - -assemble.dependsOn(javadocJar, sourcesJar) diff --git a/gradle/gatling.gradle b/gradle/gatling.gradle index 371bec29c..b6aca9cb5 100644 --- a/gradle/gatling.gradle +++ b/gradle/gatling.gradle @@ -12,7 +12,7 @@ sourceSets { } dependencies { - gatlingImplementation "io.gatling.highcharts:gatling-charts-highcharts:${gatling_version}" + gatlingImplementation "io.gatling.highcharts:gatling-charts-highcharts:${libs.versions.gatling}" } //noinspection GroovyAssignabilityCheck diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..363c14673 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,112 @@ +[versions] + +# platform +java="17" +kotlin="1.9.21" + +# libraries +assertJ = "3.23.1" +auth0-jwt = "4.4.0" +commonsCompress = "1.26.0" +coroutines = "1.7.3" +gatling = "3.8.4" +google-findbugs = "3.0.2" +guava = "32.1.1-jre" +hamcrest = "2.2" +hazelcast = "5.2.5" +hazelcastHibernate = "5.1.0" +hibernate-validator = "6.2.5.Final" +hikariCp = "5.0.1" +hsqldb = "2.7.2" +jackson = "2.16.1" +javaxActivation = "1.1.1" +javaxInject = "1" +jhipster = "7.9.3" +junit = "5.10.0" +ktor = "2.3.4" +liquibase = "4.22.0" +liquibaseSlf4j = "4.1.0" +logback = "1.4.11" +mapstruct = "1.5.5.Final" +mockito = "5.5.0" +mockitoKotlin = "5.1.0" +opencsv = "5.8" +postgresql = "42.6.1" +radarCommons = "1.1.3-SNAPSHOT" +radarSchemas = "0.8.8" +slf4j = "2.0.16" +snappy = "1.1.10.5" +springBoot = "2.7.15" +springData = "2021.2.5" +springDoc = "1.6.15" +springFramework = "5.3.27" +springSecurityOauth = "2.5.2.RELEASE" +springSession = "2021.2.0" +swagger = "2.2.22" +testContainers = "1.19.7" +wireMock = "2.27.2" + +# plugins +nodeGradle = "3.6.0" +springDependencyManagement = "1.1.3" +undercouchDownload = "5.5.0" + +[libraries] +assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertJ" } +auth0-jwt = { group = "com.auth0", name = "java-jwt", version.ref = "auth0-jwt" } +google-findbugs = { group = "com.google.code.findbugs", name = "jsr305", version.ref = "google-findbugs" } +gradle-radar-kotlin = { group = "org.radarbase.radar-kotlin", name = "org.radarbase.radar-kotlin.gradle.plugin", version.ref = "radarCommons" } +gradle-kotlin-jvm = { group = "org.jetbrains.kotlin.jvm", name = "org.jetbrains.kotlin.jvm.gradle.plugin", version.ref = "kotlin" } +gradle-kotlin-serialization = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" } +hamcrest = { group = "org.hamcrest", name = "hamcrest", version.ref = "hamcrest" } +hazelcast = { group = "com.hazelcast", name = "hazelcast", version.ref = "hazelcast" } +hazelcast-hybernate53 = { group = "com.hazelcast", name = "hazelcast-hibernate53", version.ref = "hazelcastHibernate" } +hazelcast-spring = { group = "com.hazelcast", name = "hazelcast-spring", version.ref = "hazelcast" } +hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version.ref = "hibernate-validator" } +hikari-cp = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariCp" } +hsqldb = { group = "org.hsqldb", name = "hsqldb", version.ref = "hsqldb" } +javax-activation = { group = "javax.activation", name = "activation", version.ref = "javaxActivation" } +javax-inject = { group = "javax.inject", name = "javax.inject", version.ref = "javaxInject" } +jhipster-dependencies = { group = "tech.jhipster", name = "jhipster-dependencies", version.ref = "jhipster"} +jhipster-framework = { group = "tech.jhipster", name = "jhipster-framework", version.ref = "jhipster"} +junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } +junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" } +kotlin-coroutines-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-bom", version.ref = "coroutines" } +kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } +kotlin-coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor" } +kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } +kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-client-auth = { module = "io.ktor:ktor-client-auth" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation" } +ktor-client-core = { module = "io.ktor:ktor-client-core" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } +liquibase-core = { group = "org.liquibase", name = "liquibase-core", version.ref = "liquibase" } +liquibase-slf4j = { group = "com.mattbertolini", name = "liquibase-slf4j", version.ref = "liquibaseSlf4j" } +logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } +mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } +postgresql = { group = "org.postgresql", name = "postgresql", version.ref = "postgresql" } +radar-commons-kotlin = { group = "org.radarbase", name = "radar-commons-kotlin", version.ref = "radarCommons" } +slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } +spring-security-oauth = { group = "org.springframework.security.oauth", name = "spring-security-oauth2", version.ref = "springSecurityOauth" } +springdoc-openapi = { group = "org.springdoc", name = "springdoc-openapi-ui", version.ref = "springDoc" } +swagger-annotations = { group = "io.swagger.core.v3", name = "swagger-annotations", version.ref = "swagger" } +testcontainers = { group = "org.testcontainers", name = "testcontainers", version.ref = "testContainers" } +wiremock = { group = "com.github.tomakehurst", name = "wiremock", version.ref = "wireMock" } + +[plugins] +kotlin-allopen = { id = "org.jetbrains.kotlin.plugin.allopen", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +node-gradle = { id = "com.github.node-gradle.node", version.ref = "nodeGradle" } +radar-dependency-management = { id = "org.radarbase.radar-dependency-management", version.ref = "radarCommons" } +radar-kotlin = { id = "org.radarbase.radar-kotlin", version.ref = "radarCommons" } +radar-publishing = { id = "org.radarbase.radar-publishing", version.ref = "radarCommons" } +radar-root-project = { id = "org.radarbase.radar-root-project", version.ref = "radarCommons" } +spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" } +spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" } +undercouch-download = { id = "de.undercouch.download", version.ref = "undercouchDownload" } diff --git a/gradle/liquibase.gradle b/gradle/liquibase.gradle index 0d16cec86..b906b7796 100644 --- a/gradle/liquibase.gradle +++ b/gradle/liquibase.gradle @@ -5,7 +5,7 @@ configurations { } dependencies { - liquibase "org.liquibase.ext:liquibase-hibernate5:${liquibase_version}" + liquibase "org.liquibase.ext:liquibase-hibernate5:${libs.versions.liquibase}" } if (OperatingSystem.current().isWindows()) { diff --git a/gradle/mapstruct.gradle b/gradle/mapstruct.gradle index 10a398198..38d43cd36 100644 --- a/gradle/mapstruct.gradle +++ b/gradle/mapstruct.gradle @@ -1,5 +1,5 @@ dependencies { - implementation group: 'org.mapstruct', name: 'mapstruct', version: mapstruct_version - kapt group: 'org.mapstruct', name: 'mapstruct-processor', version: mapstruct_version + implementation group: 'org.mapstruct', name: 'mapstruct', version: libs.versions.mapstruct.get() + kapt group: 'org.mapstruct', name: 'mapstruct-processor', version: libs.versions.mapstruct.get() } diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle deleted file mode 100644 index e2db4e754..000000000 --- a/gradle/publishing.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'maven-publish' -apply plugin: 'signing' - -apply from: "$rootDir/gradle/artifacts.gradle" - -publishing { - publications { - mavenJar(MavenPublication) { publication -> - from components.java - artifact sourcesJar - artifact javadocJar - - pom { - name = project.name - description = project.description - url = githubUrl - licenses { - license { - name = 'The Apache Software License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution = 'repo' - } - } - developers { - developer { - id = 'dennyverbeeck' - name = 'Denny Verbeeck' - email = 'dverbeec@its.jnj.com' - organization = 'Janssen R&D' - } - developer { - id = 'blootsvoets' - name = 'Joris Borgdorff' - email = 'joris@thehyve.nl' - organization = 'The Hyve' - } - developer { - id = 'nivemaham' - name = 'Nivethika Mahasivam' - email = 'nivethika@thehyve.nl' - organization = 'The Hyve' - } - } - issueManagement { - system = 'GitHub' - url = githubUrl + '/issues' - } - organization { - name = 'RADAR-base' - url = website - } - scm { - connection = 'scm:git:' + githubUrl - url = githubUrl - } - } - - } - } -} - -signing { - useGpgCmd() - required { true } - sign(tasks["sourcesJar"], tasks["javadocJar"]) - sign(publishing.publications["mavenJar"]) -} - -tasks.withType(Sign).configureEach { - onlyIf { gradle.taskGraph.hasTask(project.tasks["publish"]) } -} diff --git a/gradle/style.gradle b/gradle/style.gradle deleted file mode 100644 index b00e0ac32..000000000 --- a/gradle/style.gradle +++ /dev/null @@ -1,24 +0,0 @@ -apply plugin: 'checkstyle' -apply plugin: 'pmd' - -checkstyle { - toolVersion = '9.2' - ignoreFailures = false - maxWarnings = 0 - - configFile = rootProject.file('config/checkstyle/checkstyle.xml') -} - -pmd { - toolVersion = '6.41.0' - ignoreFailures = false - - consoleOutput = true - - ruleSets = [] - ruleSetFiles = rootProject.files("config/pmd/ruleset.xml") -} - -pmdTest { - ruleSetFiles = rootProject.files("config/pmd/test_ruleset.xml") -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e8..b82aa23a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/managementportal-client/build.gradle b/managementportal-client/build.gradle deleted file mode 100644 index 17811d741..000000000 --- a/managementportal-client/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -/* - * Copyright (c) 2020. The Hyve - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * See the file LICENSE in the root of this repository. - */ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'org.jetbrains.kotlin.plugin.serialization' - id 'org.jetbrains.dokka' - id 'maven-publish' -} - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -description = "Kotlin ManagementPortal client" - -dependencies { - api("org.jetbrains.kotlin:kotlin-stdlib:1.9.10") - implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.10") - implementation("org.radarbase:radar-commons-kotlin:1.0.0") - - api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:$coroutines_version")) - api(platform("io.ktor:ktor-bom:$ktor_version")) - - api("io.ktor:ktor-client-core") - api("io.ktor:ktor-client-auth") - implementation("io.ktor:ktor-client-cio") - implementation("io.ktor:ktor-client-content-negotiation") - implementation("io.ktor:ktor-serialization-kotlinx-json") - - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test") - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version - testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.27.2' - testImplementation group: 'org.mockito.kotlin', name: 'mockito-kotlin', version: mockito_kotlin_version - testImplementation group: 'org.hamcrest', name: 'hamcrest', version: '2.2' - - testRuntimeOnly group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version -} - -tasks.withType(KotlinCompile).configureEach { - compilerOptions { - jvmTarget = JvmTarget.JVM_11 - apiVersion = KotlinVersion.KOTLIN_1_7 - languageVersion = KotlinVersion.KOTLIN_1_9 - } -} - -tasks.register('ghPagesJavadoc', Copy) { - from file("$buildDir/dokka/javadoc") - into file("$rootDir/public/managementportal-client-javadoc") - dependsOn(dokkaJavadoc) -} - -test { - testLogging { - exceptionFormat = 'full' - } - useJUnitPlatform() -} - -ext.projectLanguage = "kotlin" -ext.publishToMavenCentral = true - -apply from: "$rootDir/gradle/publishing.gradle" diff --git a/managementportal-client/build.gradle.kts b/managementportal-client/build.gradle.kts new file mode 100644 index 000000000..63f9dd4f7 --- /dev/null +++ b/managementportal-client/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("project-conventions") +} + +description = "Kotlin ManagementPortal client" + +dependencies { + api(libs.kotlin.stdlib) + api(platform(libs.kotlin.coroutines.bom)) + + api(platform(libs.ktor.bom)) + api(libs.ktor.client.core) + api(libs.ktor.client.auth) + api(libs.ktor.client.cio) + api(libs.ktor.client.content.negotiation) + api(libs.ktor.serialization.kotlinx.json) + + implementation(libs.kotlin.reflect) + implementation(libs.radar.commons.kotlin) + + testImplementation(libs.kotlin.coroutines.test) + testImplementation(libs.junit.jupiter.api) + testImplementation(libs.wiremock) + testImplementation(libs.mockito.kotlin) + testImplementation(libs.hamcrest) + + testRuntimeOnly(libs.slf4j.simple) + testRuntimeOnly(libs.junit.jupiter.engine) +} + +radarKotlin { + slf4jVersion.set(libs.versions.slf4j) +} + +tasks.register("ghPagesJavadoc") { + from("${layout.buildDirectory}/dokka/javadoc") + into("$rootDir/public/managementportal-client-javadoc") + dependsOn(tasks.named("dokkaJavadoc")) +} + +tasks.test { + useJUnitPlatform() +} + +idea { + module { + isDownloadSources = true + } +} diff --git a/managementportal-client/src/main/kotlin/org/radarbase/management/client/HttpStatusException.kt b/managementportal-client/src/main/kotlin/org/radarbase/management/client/HttpStatusException.kt index e11fcf835..4314cd870 100644 --- a/managementportal-client/src/main/kotlin/org/radarbase/management/client/HttpStatusException.kt +++ b/managementportal-client/src/main/kotlin/org/radarbase/management/client/HttpStatusException.kt @@ -1,6 +1,6 @@ package org.radarbase.management.client -import io.ktor.http.* +import io.ktor.http.HttpStatusCode import java.io.IOException class HttpStatusException( diff --git a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPClient.kt b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPClient.kt index 649dcd6f8..64593969b 100644 --- a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPClient.kt +++ b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPClient.kt @@ -9,88 +9,94 @@ package org.radarbase.management.client -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.auth.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.request +import io.ktor.client.request.url +import io.ktor.client.statement.bodyAsText +import io.ktor.client.statement.request +import io.ktor.http.isSuccess +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withContext import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.contextual import org.radarbase.ktor.auth.OAuth2AccessToken import java.util.* import kotlin.time.Duration.Companion.seconds -fun mpClient(config: MPClient.Config.() -> Unit): MPClient { - return MPClient(MPClient.Config().apply(config)) -} +fun mpClient(config: MPClient.Config.() -> Unit): MPClient = MPClient(MPClient.Config().apply(config)) /** * Client for the ManagementPortal REST API. */ @Suppress("unused", "MemberVisibilityCanBePrivate") -class MPClient(config: Config) { +class MPClient( + config: Config, +) { lateinit var token: Flow - private val url: String = requireNotNull(config.url) { - "Missing server URL" - }.trimEnd('/') + '/' + private val url: String = + requireNotNull(config.url) { + "Missing server URL" + }.trimEnd('/') + '/' - /** HTTP client to make requests with. */ + /** HTTP client to make requests with. */ private val originalHttpClient: HttpClient? = config.httpClient private val auth: Auth.() -> Flow = config.auth - val httpClient = (originalHttpClient ?: HttpClient(CIO)).config { - install(HttpTimeout) { - connectTimeoutMillis = 10.seconds.inWholeMilliseconds - socketTimeoutMillis = 10.seconds.inWholeMilliseconds - requestTimeoutMillis = 30.seconds.inWholeMilliseconds - } - install(ContentNegotiation) { - json(json) - } - install(Auth) { - token = auth() - } - defaultRequest { - url(this@MPClient.url) + val httpClient = + (originalHttpClient ?: HttpClient(CIO)).config { + install(HttpTimeout) { + connectTimeoutMillis = 10.seconds.inWholeMilliseconds + socketTimeoutMillis = 10.seconds.inWholeMilliseconds + requestTimeoutMillis = 30.seconds.inWholeMilliseconds + } + install(ContentNegotiation) { + json(json) + } + install(Auth) { + token = auth() + } + defaultRequest { + url(this@MPClient.url) + } } - } /** Request list of organizations from ManagementPortal */ suspend fun requestOrganizations( page: Int = 0, size: Int = Int.MAX_VALUE, - ): List = request { - url("api/organizations") - with(url.parameters) { - append("page", page.toString()) - append("size", size.toString()) + ): List = + request { + url("api/organizations") + with(url.parameters) { + append("page", page.toString()) + append("size", size.toString()) + } } - } /** Request list of projects from ManagementPortal. */ suspend fun requestProjects( page: Int = 0, size: Int = Int.MAX_VALUE, ): List { - val body = requestText { - url("api/projects") - with(url.parameters) { - append("page", page.toString()) - append("size", size.toString()) + val body = + requestText { + url("api/projects") + with(url.parameters) { + append("page", page.toString()) + append("size", size.toString()) + } } - } return json.decodeFromString(ListSerializer(MPProjectSerializer), body) } @@ -102,14 +108,14 @@ class MPClient(config: Config) { projectId: String, page: Int = 0, size: Int = Int.MAX_VALUE, - ): List = request> { - url("api/projects/$projectId/subjects") - with(url.parameters) { - append("page", page.toString()) - append("size", size.toString()) - } - } - .map { it.copy(projectId = projectId) } + ): List = + request> { + url("api/projects/$projectId/subjects") + with(url.parameters) { + append("page", page.toString()) + append("size", size.toString()) + } + }.map { it.copy(projectId = projectId) } /** * Request list of OAuth 2.0 clients from ManagementPortal. @@ -117,35 +123,34 @@ class MPClient(config: Config) { suspend fun requestClients( page: Int = 0, size: Int = Int.MAX_VALUE, - ): List = request { - url("api/oauth-clients") - with(url.parameters) { - append("page", page.toString()) - append("size", size.toString()) + ): List = + request { + url("api/oauth-clients") + with(url.parameters) { + append("page", page.toString()) + append("size", size.toString()) + } } - } - suspend inline fun request( - crossinline block: HttpRequestBuilder.() -> Unit, - ): T = withContext(Dispatchers.IO) { - with(httpClient.request(block)) { - if (!status.isSuccess()) { - throw HttpStatusException(status, "Request to ${request.url} failed (code $status)") + suspend inline fun request(crossinline block: HttpRequestBuilder.() -> Unit): T = + withContext(Dispatchers.IO) { + with(httpClient.request(block)) { + if (!status.isSuccess()) { + throw HttpStatusException(status, "Request to ${request.url} failed (code $status)") + } + body() } - body() } - } - suspend inline fun requestText( - crossinline block: HttpRequestBuilder.() -> Unit, - ): String = withContext(Dispatchers.IO) { - with(httpClient.request(block)) { - if (!status.isSuccess()) { - throw HttpStatusException(status, "Request to ${request.url} failed (code $status)") + suspend inline fun requestText(crossinline block: HttpRequestBuilder.() -> Unit): String = + withContext(Dispatchers.IO) { + with(httpClient.request(block)) { + if (!status.isSuccess()) { + throw HttpStatusException(status, "Request to ${request.url} failed (code $status)") + } + bodyAsText() } - bodyAsText() } - } fun config(config: Config.() -> Unit): MPClient { val oldConfig = toConfig() @@ -153,11 +158,12 @@ class MPClient(config: Config) { return if (oldConfig != newConfig) MPClient(newConfig) else this } - private fun toConfig(): Config = Config().apply { - httpClient = this@MPClient.originalHttpClient - url = this@MPClient.url - auth = this@MPClient.auth - } + private fun toConfig(): Config = + Config().apply { + httpClient = this@MPClient.originalHttpClient + url = this@MPClient.url + auth = this@MPClient.auth + } class Config { internal var auth: Auth.() -> Flow = { MutableStateFlow(null) } @@ -177,18 +183,19 @@ class MPClient(config: Config) { other as Config - return httpClient == other.httpClient - && url == other.url - && auth == other.auth + return httpClient == other.httpClient && + url == other.url && + auth == other.auth } override fun hashCode(): Int = Objects.hash(httpClient, url) } companion object { - private val json = Json { - ignoreUnknownKeys = true - coerceInputValues = true - } + private val json = + Json { + ignoreUnknownKeys = true + coerceInputValues = true + } } } diff --git a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPRole.kt b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPRole.kt index ba792783a..6c01614a0 100644 --- a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPRole.kt +++ b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPRole.kt @@ -16,5 +16,5 @@ import kotlinx.serialization.Serializable data class MPRole( @SerialName("projectName") val projectId: String? = null, - val authorityName: String + val authorityName: String, ) diff --git a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPSourceData.kt b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPSourceData.kt index 668ccc8ab..37530b53e 100644 --- a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPSourceData.kt +++ b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPSourceData.kt @@ -14,12 +14,12 @@ import kotlinx.serialization.Serializable @Serializable data class MPSourceData( val id: Long, - //Source data type. + // Source data type. val sourceDataType: String, val sourceDataName: String? = null, - //Default data frequency + // Default data frequency val frequency: String? = null, - //Measurement unit. + // Measurement unit. val unit: String? = null, // Define if the samples are RAW data or instead they the result of some computation val processingState: String? = null, @@ -30,5 +30,5 @@ data class MPSourceData( val topic: String? = null, val provider: String? = null, val enabled: Boolean = true, - val sourceType: MPSourceType? = null + val sourceType: MPSourceType? = null, ) diff --git a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPUser.kt b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPUser.kt index 9d2f11d03..07bcc724d 100644 --- a/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPUser.kt +++ b/managementportal-client/src/main/kotlin/org/radarbase/management/client/MPUser.kt @@ -16,15 +16,12 @@ import kotlinx.serialization.Serializable data class MPUser( @SerialName("login") val id: String, - val firstName: String? = null, val lastName: String? = null, val email: String? = null, val activated: Boolean = false, - val langKey: String? = null, val createdBy: String? = null, - /** ZonedDateTime. */ val createdDate: String? = null, val lastModifiedBy: String? = null, diff --git a/managementportal-client/src/test/kotlin/org/radarbase/management/client/MPClientTest.kt b/managementportal-client/src/test/kotlin/org/radarbase/management/client/MPClientTest.kt index 1bd54b3be..d74d38c1d 100644 --- a/managementportal-client/src/test/kotlin/org/radarbase/management/client/MPClientTest.kt +++ b/managementportal-client/src/test/kotlin/org/radarbase/management/client/MPClientTest.kt @@ -10,15 +10,20 @@ package org.radarbase.management.client import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.anyUrl +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo import com.github.tomakehurst.wiremock.matching.EqualToPattern import com.github.tomakehurst.wiremock.stubbing.StubMapping -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import org.apache.http.entity.ContentType import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers import org.hamcrest.Matchers.hasSize @@ -36,6 +41,8 @@ class MPClientTest { private lateinit var wireMockServer: WireMockServer private lateinit var client: MPClient + private val contentTypeJson = "application/json" + @BeforeEach fun setUp() { wireMockServer = WireMockServer(9090) @@ -43,29 +50,38 @@ class MPClientTest { wireMockServer.stubFor( get(anyUrl()) - .willReturn(aResponse() - .withStatus(HTTP_UNAUTHORIZED))) - - authStub = wireMockServer.stubFor( - post(urlEqualTo("/oauth/token")) - .willReturn(aResponse() - .withStatus(HTTP_OK) - .withHeader("content-type", ContentType.APPLICATION_JSON.toString()) - .withBody("{\"access_token\":\"abcdef\"}"))) + .willReturn( + aResponse() + .withStatus(HTTP_UNAUTHORIZED), + ), + ) - client = mpClient { - url = "http://localhost:9090/" - auth { - clientCredentials( - authConfig = ClientCredentialsConfig( - tokenUrl = "http://localhost:9090/oauth/token", - clientId = "testId", - clientSecret = "testSecret", + authStub = + wireMockServer.stubFor( + post(urlEqualTo("/oauth/token")) + .willReturn( + aResponse() + .withStatus(HTTP_OK) + .withHeader("content-type", contentTypeJson) + .withBody("{\"access_token\":\"abcdef\"}"), ), - targetHost = "localhost", - ) + ) + + client = + mpClient { + url = "http://localhost:9090/" + auth { + clientCredentials( + authConfig = + ClientCredentialsConfig( + tokenUrl = "http://localhost:9090/oauth/token", + clientId = "testId", + clientSecret = "testSecret", + ), + targetHost = "localhost", + ) + } } - } } @AfterEach @@ -74,242 +90,284 @@ class MPClientTest { } @Test - fun testClients() = runBlocking { - val body = - """ - [{ - "clientId": "c", - "scope": ["s1", "s2"], - "resourceIds": ["r1"], - "authorizedGrantTypes": null, - "autoApproveScopes": null, - "accessTokenValiditySeconds": 900, - "refreshTokenValiditySeconds": null, - "authorities": ["aud_managementPortal"], - "registeredRedirectUri": null, - "additionalInformation": null - }, - { - "clientId": "d", - "scope": ["s3", "s2"], - "resourceIds": ["r1", "r2"], - "authorizedGrantTypes": ["a1", "a2"], - "autoApproveScopes": ["a1", "a2"], - "accessTokenValiditySeconds": 900, - "refreshTokenValiditySeconds": 86400, - "authorities": ["aud_managementPortal"], - "registeredRedirectUri": ["http://localhost"], - "additionalInformation": {"something": "other"} - }] - """.trimIndent() + fun testClients() = + runBlocking { + val body = + """ + [{ + "clientId": "c", + "scope": ["s1", "s2"], + "resourceIds": ["r1"], + "authorizedGrantTypes": null, + "autoApproveScopes": null, + "accessTokenValiditySeconds": 900, + "refreshTokenValiditySeconds": null, + "authorities": ["aud_managementPortal"], + "registeredRedirectUri": null, + "additionalInformation": null + }, + { + "clientId": "d", + "scope": ["s3", "s2"], + "resourceIds": ["r1", "r2"], + "authorizedGrantTypes": ["a1", "a2"], + "autoApproveScopes": ["a1", "a2"], + "accessTokenValiditySeconds": 900, + "refreshTokenValiditySeconds": 86400, + "authorities": ["aud_managementPortal"], + "registeredRedirectUri": ["http://localhost"], + "additionalInformation": {"something": "other"} + }] + """.trimIndent() - wireMockServer.stubFor( - get(urlPathEqualTo("/api/oauth-clients")) - .withHeader("Authorization", equalTo("Bearer abcdef")) - .willReturn(aResponse() - .withStatus(HTTP_OK) - .withHeader("content-type", ContentType.APPLICATION_JSON.toString()) - .withBody(body))) + wireMockServer.stubFor( + get(urlPathEqualTo("/api/oauth-clients")) + .withHeader("Authorization", equalTo("Bearer abcdef")) + .willReturn( + aResponse() + .withStatus(HTTP_OK) + .withHeader("content-type", contentTypeJson) + .withBody(body), + ), + ) - val clients = client.requestClients() + val clients = client.requestClients() - assertThat(clients, hasSize(2)) - assertThat(clients, Matchers.equalTo(listOf( - MPOAuthClient( - id = "c", - scope = listOf("s1", "s2"), - resourceIds = listOf("r1"), - authorizedGrantTypes = emptyList(), - autoApproveScopes = emptyList(), - accessTokenValiditySeconds = 900, - refreshTokenValiditySeconds = null, - authorities = listOf("aud_managementPortal"), - registeredRedirectUri = emptyList(), - additionalInformation = emptyMap(), - ), - MPOAuthClient( - id = "d", - scope = listOf("s3", "s2"), - resourceIds = listOf("r1", "r2"), - authorizedGrantTypes = listOf("a1", "a2"), - autoApproveScopes = listOf("a1", "a2"), - accessTokenValiditySeconds = 900, - refreshTokenValiditySeconds = 86400, - authorities = listOf("aud_managementPortal"), - registeredRedirectUri = listOf("http://localhost"), - additionalInformation = mapOf("something" to "other"), + assertThat(clients, hasSize(2)) + assertThat( + clients, + Matchers.equalTo( + listOf( + MPOAuthClient( + id = "c", + scope = listOf("s1", "s2"), + resourceIds = listOf("r1"), + authorizedGrantTypes = emptyList(), + autoApproveScopes = emptyList(), + accessTokenValiditySeconds = 900, + refreshTokenValiditySeconds = null, + authorities = listOf("aud_managementPortal"), + registeredRedirectUri = emptyList(), + additionalInformation = emptyMap(), + ), + MPOAuthClient( + id = "d", + scope = listOf("s3", "s2"), + resourceIds = listOf("r1", "r2"), + authorizedGrantTypes = listOf("a1", "a2"), + autoApproveScopes = listOf("a1", "a2"), + accessTokenValiditySeconds = 900, + refreshTokenValiditySeconds = 86400, + authorities = listOf("aud_managementPortal"), + registeredRedirectUri = listOf("http://localhost"), + additionalInformation = mapOf("something" to "other"), + ), + ), + ), ) - ))) - wireMockServer.verify(1, postRequestedFor(urlEqualTo("/oauth/token")) - .withRequestBody(EqualToPattern("grant_type=client_credentials&client_id=testId&client_secret=testSecret"))) - wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/oauth-clients"))) - } + wireMockServer.verify( + 1, + postRequestedFor(urlEqualTo("/oauth/token")) + .withRequestBody(EqualToPattern("grant_type=client_credentials&client_id=testId&client_secret=testSecret")), + ) + wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/oauth-clients"))) + } @Test fun testParseToken() { - val json = Json { - ignoreUnknownKeys = true - coerceInputValues = true - } + val json = + Json { + ignoreUnknownKeys = true + coerceInputValues = true + } - val token = json.decodeFromString("""{"access_token":"access token","token_type":"bearer","expires_in":899,"scope":"PROJECT.READ","iss":"ManagementPortal","grant_type":"client_credentials","iat":1600000000,"jti":"some token"}""") - assertThat(token, Matchers.equalTo( - OAuth2AccessToken( - accessToken = "access token", - expiresIn = 899, - tokenType = "bearer", - scope = "PROJECT.READ" + val token = + json.decodeFromString( + """{"access_token":"access token","token_type":"bearer","expires_in":899,"scope":"PROJECT.READ", + |"iss":"ManagementPortal","grant_type":"client_credentials","iat":1600000000,"jti":"some token"} + """.trimMargin(), ) - )) + assertThat( + token, + Matchers.equalTo( + OAuth2AccessToken( + accessToken = "access token", + expiresIn = 899, + tokenType = "bearer", + scope = "PROJECT.READ", + ), + ), + ) } @Test - fun testProjects() = runTest { - val body = - """ - [{ - "id": 1, - "projectName": "p", - "humanReadableProjectName": null, - "description": "d", - "organization": null, - "location": "u", - "startDate": null, - "projectStatus": "ONGOING", - "endDate": null, - "attributes": {}, - "persistentTokenTimeout": null - }, - { - "id": 2, - "projectName": "p2", - "humanReadableProjectName": "P2", - "description": "d2", - "organization": {"id": 1, "name": "Mixed"}, - "location": "here", - "startDate": "2021-06-07T02:02:00Z", - "projectStatus": "ONGOING", - "endDate": "2022-06-07T02:02:00Z", - "attributes": { - "External-project-id": "p2a", - "Human-readable-project-name": "P2" + fun testProjects(): Unit = + runTest { + val body = + """ + [{ + "id": 1, + "projectName": "p", + "humanReadableProjectName": null, + "description": "d", + "organization": null, + "location": "u", + "startDate": null, + "projectStatus": "ONGOING", + "endDate": null, + "attributes": {}, + "persistentTokenTimeout": null }, - "persistentTokenTimeout": null - }] - """.trimIndent() + { + "id": 2, + "projectName": "p2", + "humanReadableProjectName": "P2", + "description": "d2", + "organization": {"id": 1, "name": "Mixed"}, + "location": "here", + "startDate": "2021-06-07T02:02:00Z", + "projectStatus": "ONGOING", + "endDate": "2022-06-07T02:02:00Z", + "attributes": { + "External-project-id": "p2a", + "Human-readable-project-name": "P2" + }, + "persistentTokenTimeout": null + }] + """.trimIndent() - wireMockServer.stubFor( - get(urlPathEqualTo("/api/projects")) - .withHeader("Authorization", equalTo("Bearer abcdef")) - .willReturn(aResponse() - .withStatus(HTTP_OK) - .withHeader("content-type", ContentType.APPLICATION_JSON.toString()) - .withBody(body))) + wireMockServer.stubFor( + get(urlPathEqualTo("/api/projects")) + .withHeader("Authorization", equalTo("Bearer abcdef")) + .willReturn( + aResponse() + .withStatus(HTTP_OK) + .withHeader("content-type", contentTypeJson) + .withBody(body), + ), + ) - val projects = client.requestProjects() - assertThat(projects, hasSize(2)) - assertThat(projects, Matchers.equalTo(listOf( - MPProject( - id = "p", - name = null, - description = "d", - organization = null, - location = "u", - startDate = null, - projectStatus = "ONGOING", - endDate = null, - attributes = emptyMap(), - ), - MPProject( - id = "p2", - name = "P2", - description = "d2", - organization = MPOrganization(id = "Mixed"), - location = "here", - startDate = "2021-06-07T02:02:00Z", - projectStatus = "ONGOING", - endDate = "2022-06-07T02:02:00Z", - attributes = mapOf( - "External-project-id" to "p2a", - "Human-readable-project-name" to "P2", + val projects = client.requestProjects() + assertThat(projects, hasSize(2)) + assertThat( + projects, + Matchers.equalTo( + listOf( + MPProject( + id = "p", + name = null, + description = "d", + organization = null, + location = "u", + startDate = null, + projectStatus = "ONGOING", + endDate = null, + attributes = emptyMap(), + ), + MPProject( + id = "p2", + name = "P2", + description = "d2", + organization = MPOrganization(id = "Mixed"), + location = "here", + startDate = "2021-06-07T02:02:00Z", + projectStatus = "ONGOING", + endDate = "2022-06-07T02:02:00Z", + // formatter:off + attributes = + mapOf( + "External-project-id" to "p2a", + "Human-readable-project-name" to "P2", + ), + // formatter:on + ), + ), ), - ), - ))) + ) - wireMockServer.verify(1, postRequestedFor(urlEqualTo("/oauth/token"))) - wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/projects"))) - } + wireMockServer.verify(1, postRequestedFor(urlEqualTo("/oauth/token"))) + wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/projects"))) + } @Test - fun testMp1Projects() = runTest { - val body = """ - [{ - "id": 1, - "projectName": "A", - "humanReadableProjectName": null, - "description": "d", - "organization": "some", - "location": "l", - "startDate": "2020-01-01T00:00:00Z", - "projectStatus": "ONGOING", - "endDate": "2030-01-01T00:00:00Z", - "attributes": {}, - "persistentTokenTimeout": null - }, { - "id": 2, - "projectName": "a", - "humanReadableProjectName": "p", - "description": "D", - "organization": null, - "location": "L", - "startDate": "2020-01-01T00:00:00Z", - "projectStatus": "ONGOING", - "endDate": "2030-01-01T00:00:00Z", - "attributes": {}, - "persistentTokenTimeout": null - }] - """.trimIndent() - - wireMockServer.stubFor( - get(urlPathEqualTo("/api/projects")) - .withHeader("Authorization", equalTo("Bearer abcdef")) - .willReturn(aResponse() - .withStatus(HTTP_OK) - .withHeader("content-type", ContentType.APPLICATION_JSON.toString()) - .withBody(body))) + fun testMp1Projects() = + runTest { + val body = + """ + [{ + "id": 1, + "projectName": "A", + "humanReadableProjectName": null, + "description": "d", + "organization": "some", + "location": "l", + "startDate": "2020-01-01T00:00:00Z", + "projectStatus": "ONGOING", + "endDate": "2030-01-01T00:00:00Z", + "attributes": {}, + "persistentTokenTimeout": null + }, { + "id": 2, + "projectName": "a", + "humanReadableProjectName": "p", + "description": "D", + "organization": null, + "location": "L", + "startDate": "2020-01-01T00:00:00Z", + "projectStatus": "ONGOING", + "endDate": "2030-01-01T00:00:00Z", + "attributes": {}, + "persistentTokenTimeout": null + }] + """.trimIndent() + wireMockServer.stubFor( + get(urlPathEqualTo("/api/projects")) + .withHeader("Authorization", equalTo("Bearer abcdef")) + .willReturn( + aResponse() + .withStatus(HTTP_OK) + .withHeader("content-type", contentTypeJson) + .withBody(body), + ), + ) - val projects = client.requestProjects() - assertThat(projects, hasSize(2)) - assertThat(projects, Matchers.equalTo(listOf( - MPProject( - id = "A", - name = null, - description = "d", - organization = null, - organizationName = "some", - location = "l", - startDate = "2020-01-01T00:00:00Z", - projectStatus = "ONGOING", - endDate = "2030-01-01T00:00:00Z", - attributes = emptyMap(), - ), - MPProject( - id = "a", - name = "p", - description = "D", - organization = null, - organizationName = null, - location = "L", - startDate = "2020-01-01T00:00:00Z", - projectStatus = "ONGOING", - endDate = "2030-01-01T00:00:00Z", - attributes = emptyMap(), - ), - ))) + val projects = client.requestProjects() + assertThat(projects, hasSize(2)) + assertThat( + projects, + Matchers.equalTo( + listOf( + MPProject( + id = "A", + name = null, + description = "d", + organization = null, + organizationName = "some", + location = "l", + startDate = "2020-01-01T00:00:00Z", + projectStatus = "ONGOING", + endDate = "2030-01-01T00:00:00Z", + attributes = emptyMap(), + ), + MPProject( + id = "a", + name = "p", + description = "D", + organization = null, + organizationName = null, + location = "L", + startDate = "2020-01-01T00:00:00Z", + projectStatus = "ONGOING", + endDate = "2030-01-01T00:00:00Z", + attributes = emptyMap(), + ), + ), + ), + ) - wireMockServer.verify(1, postRequestedFor(urlEqualTo("/oauth/token"))) - wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/projects"))) - } + wireMockServer.verify(1, postRequestedFor(urlEqualTo("/oauth/token"))) + wireMockServer.verify(2, getRequestedFor(urlPathEqualTo("/api/projects"))) + } } diff --git a/radar-auth/build.gradle b/radar-auth/build.gradle deleted file mode 100644 index 90bf4a5bf..000000000 --- a/radar-auth/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id 'maven-publish' - id 'org.jetbrains.kotlin.jvm' - id 'org.jetbrains.kotlin.plugin.serialization' - id 'org.jetbrains.dokka' -} - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -description = 'Library for authentication and authorization of JWT tokens issued by the RADAR platform' - -dependencies { - api group: 'com.auth0', name: 'java-jwt', version: oauth_jwt_version - api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:$coroutines_version")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") - - implementation("org.radarbase:radar-commons-kotlin:$radar_commons_version") - - implementation(platform("io.ktor:ktor-bom:$ktor_version")) - implementation("io.ktor:ktor-client-core:$ktor_version") - implementation("io.ktor:ktor-client-cio:$ktor_version") - implementation("io.ktor:ktor-client-content-negotiation:$ktor_version") - implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") - - implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version - testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: wiremock_version - testImplementation group: 'org.hamcrest', name: 'hamcrest', version: hamcrest_version - - testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: logback_version - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version - -} - -tasks.withType(KotlinCompile).configureEach { - compilerOptions { - jvmTarget = JvmTarget.JVM_11 - apiVersion = KotlinVersion.KOTLIN_1_8 - languageVersion = KotlinVersion.KOTLIN_1_8 - } -} - -test { - testLogging { - exceptionFormat = 'full' - } - useJUnitPlatform() -} - -tasks.register('ghPagesJavadoc', Copy) { - from file("$buildDir/dokka/javadoc") - into file("$rootDir/public/radar-auth-javadoc") - dependsOn(dokkaJavadoc) -} - -ext.projectLanguage = "kotlin" - -apply from: "$rootDir/gradle/style.gradle" -apply from: "$rootDir/gradle/publishing.gradle" diff --git a/radar-auth/build.gradle.kts b/radar-auth/build.gradle.kts new file mode 100644 index 000000000..4236c3c28 --- /dev/null +++ b/radar-auth/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("project-conventions") +} + +description = "Library for authentication and authorization of JWT tokens issued by the RADAR platform" + +dependencies { + api(libs.auth0.jwt) + api(platform(libs.kotlin.coroutines.bom)) + api(libs.kotlin.coroutines.core) + + implementation(libs.radar.commons.kotlin) + + implementation(platform(libs.ktor.bom)) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + + implementation(libs.slf4j.api) + + testImplementation(libs.junit.jupiter.api) + testImplementation(libs.wiremock) + testImplementation(libs.hamcrest) + + testRuntimeOnly(libs.logback.classic) + testRuntimeOnly(libs.junit.jupiter.engine) +} + +radarKotlin { + slf4jVersion.set(libs.versions.slf4j) +} + +tasks.test { + testLogging { + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + useJUnitPlatform() +} + +tasks.register("ghPagesJavadoc") { + from("${layout.buildDirectory}/dokka/javadoc") + into("$rootDir/public/radar-auth-javadoc") + dependsOn(tasks.named("dokkaJavadoc")) +} + +idea { + module { + isDownloadSources = true + } +} diff --git a/radar-auth/src/main/java/org/radarbase/auth/authentication/StaticTokenVerifierLoader.kt b/radar-auth/src/main/java/org/radarbase/auth/authentication/StaticTokenVerifierLoader.kt index f07c5522f..201cfab39 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authentication/StaticTokenVerifierLoader.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authentication/StaticTokenVerifierLoader.kt @@ -2,7 +2,7 @@ package org.radarbase.auth.authentication /** Load token verifiers as a static object. */ class StaticTokenVerifierLoader( - private val tokenVerifiers: List + private val tokenVerifiers: List, ) : TokenVerifierLoader { override suspend fun fetch(): List = tokenVerifiers } diff --git a/radar-auth/src/main/java/org/radarbase/auth/authentication/TokenValidator.kt b/radar-auth/src/main/java/org/radarbase/auth/authentication/TokenValidator.kt index 17d210682..e35ed852d 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authentication/TokenValidator.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authentication/TokenValidator.kt @@ -1,7 +1,7 @@ package org.radarbase.auth.authentication import com.auth0.jwt.exceptions.AlgorithmMismatchException -import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking import org.radarbase.auth.exception.TokenValidationException import org.radarbase.auth.token.RadarToken import org.radarbase.kotlin.coroutines.CacheConfig @@ -19,158 +19,164 @@ private typealias TokenVerifierCache = CachedValue> * contexts. */ class TokenValidator -@JvmOverloads -constructor( - /** Loaders for token verifiers to use in the token authenticator. */ - verifierLoaders: List?, - /** Minimum fetch timeout before a token is attempted to be fetched again. */ - fetchTimeout: Duration = Duration.ofMillis(1), - /** Maximum time that the token verifier does not need to be fetched. */ - maxAge: Duration = Duration.ofDays(1), -) { - private val algorithmLoaders: List? - - init { - val config = CacheConfig( - retryDuration = fetchTimeout.toKotlinDuration(), - refreshDuration = maxAge.toKotlinDuration(), - maxSimultaneousCompute = 2, - ) - algorithmLoaders = verifierLoaders?.map { loader -> - CachedValue(config, supplier = loader::fetch) - } - } - - /** - * Validates an access token and returns the token as a [RadarToken] object. - * - * This will load all the verifiers. If a token cannot be verified, this method will fetch - * the verifiers again, as the source may have changed. It will then and re-check the token. - * However, the public key will not be fetched more than once every `fetchTimeout`, - * to prevent (malicious) clients from loading external token verifiers too frequently. - * - * This implementation calls [runBlocking]. If calling from Kotlin, prefer to use [validate] - * with coroutines instead. - * - * @param token The access token - * @return The decoded access token - * @throws TokenValidationException If the token can not be validated. - */ - @Throws(TokenValidationException::class) - fun validateBlocking(token: String): RadarToken = runBlocking { - validate(token) - } - - /** - * Validates an access token and returns the token as a [RadarToken] object. - * - * This will load all the verifiers. If a token cannot be verified, this method will fetch - * the verifiers again, as the source may have changed. It will then and re-check the token. - * However, the public key will not be fetched more than once every `fetchTimeout`, - * to prevent (malicious) clients from loading external token verifiers too frequently. - * - * @param token The access token - * @return The decoded access token - * @throws TokenValidationException If the token can not be validated. - */ - @Throws(TokenValidationException::class) - suspend fun validate(token: String): RadarToken { - val result: Result = consumeFirst { emit -> - val causes = algorithmLoaders - ?.forkJoin { cache -> - val result = cache.verify(token) - // short-circuit to return the first successful result - if (result.isSuccess) emit(result) - result + @JvmOverloads + constructor( + /** Loaders for token verifiers to use in the token authenticator. */ + verifierLoaders: List?, + /** Minimum fetch timeout before a token is attempted to be fetched again. */ + fetchTimeout: Duration = Duration.ofMillis(1), + /** Maximum time that the token verifier does not need to be fetched. */ + maxAge: Duration = Duration.ofDays(1), + ) { + private val algorithmLoaders: List? + + init { + val config = + CacheConfig( + retryDuration = fetchTimeout.toKotlinDuration(), + refreshDuration = maxAge.toKotlinDuration(), + maxSimultaneousCompute = 2, + ) + algorithmLoaders = + verifierLoaders?.map { loader -> + CachedValue(config, supplier = loader::fetch) } - ?.flatMap { - it.exceptionOrNull() - ?.suppressedExceptions - ?: emptyList() - } ?: emptyList() - - val message = if (causes.isEmpty()) { - "No registered validator in could authenticate this token" - } else { - val suppressedMessage = causes.joinToString { it.message ?: it.javaClass.simpleName } - "No registered validator in could authenticate this token: $suppressedMessage" - } - emit(TokenValidationException(message).toFailure(causes)) } - return result.getOrThrow() - } - - /** Refresh the token verifiers from cache on the next validation. */ - fun refresh() { - algorithmLoaders?.forEach { it.clear() } - } - - companion object { - private val logger = LoggerFactory.getLogger(TokenValidator::class.java) - /** - * Verify the token using the TokenVerifier lists from cache. - * If verification fails and the TokenVerifier list was retrieved from cache - * try to reload the TokenVerifier list and verify again. - * If none of the verifications succeed, return a result of TokenValidationException - * with suppressed exceptions all the exceptions returned from a TokenVerifier. + * Validates an access token and returns the token as a [RadarToken] object. + * + * This will load all the verifiers. If a token cannot be verified, this method will fetch + * the verifiers again, as the source may have changed. It will then and re-check the token. + * However, the public key will not be fetched more than once every `fetchTimeout`, + * to prevent (malicious) clients from loading external token verifiers too frequently. + * + * This implementation calls [runBlocking]. If calling from Kotlin, prefer to use [validate] + * with coroutines instead. + * + * @param token The access token + * @return The decoded access token + * @throws TokenValidationException If the token can not be validated. */ - private suspend fun TokenVerifierCache.verify(token: String): Result { - val verifiers = getOrEmpty { false } - - val firstResult = verifiers.value.anyVerify(token) - if ( - firstResult.isSuccess || - // already fetched new verifiers, no need to fetch it again - verifiers is CachedValue.CacheMiss - ) { - return firstResult + @Throws(TokenValidationException::class) + fun validateBlocking(token: String): RadarToken = + runBlocking { + validate(token) } - val refreshedVerifiers = getOrEmpty { true } - return if (refreshedVerifiers != verifiers) { - refreshedVerifiers.value.anyVerify(token) - } else { - // The verifiers didn't change, so the result won't change - firstResult - } + /** + * Validates an access token and returns the token as a [RadarToken] object. + * + * This will load all the verifiers. If a token cannot be verified, this method will fetch + * the verifiers again, as the source may have changed. It will then and re-check the token. + * However, the public key will not be fetched more than once every `fetchTimeout`, + * to prevent (malicious) clients from loading external token verifiers too frequently. + * + * @param token The access token + * @return The decoded access token + * @throws TokenValidationException If the token can not be validated. + */ + @Throws(TokenValidationException::class) + suspend fun validate(token: String): RadarToken { + val result: Result = + consumeFirst { emit -> + val causes = + algorithmLoaders + ?.forkJoin { cache -> + val result = cache.verify(token) + // short-circuit to return the first successful result + if (result.isSuccess) emit(result) + result + }?.flatMap { + it + .exceptionOrNull() + ?.suppressedExceptions + ?: emptyList() + } ?: emptyList() + + val message = + if (causes.isEmpty()) { + "No registered validator in could authenticate this token" + } else { + val suppressedMessage = causes.joinToString { it.message ?: it.javaClass.simpleName } + "No registered validator in could authenticate this token: $suppressedMessage" + } + emit(TokenValidationException(message).toFailure(causes)) + } + + return result.getOrThrow() } - private suspend fun List.anyVerify(token: String): Result { - var exceptions: MutableList? = null + /** Refresh the token verifiers from cache on the next validation. */ + fun refresh() { + algorithmLoaders?.forEach { it.clear() } + } - forEach { verifier -> - try { - val radarToken = verifier.verify(token) - return Result.success(radarToken) - } catch (ex: Throwable) { - if (ex !is AlgorithmMismatchException) { - if (exceptions == null) { - exceptions = mutableListOf() + companion object { + private val logger = LoggerFactory.getLogger(TokenValidator::class.java) + + /** + * Verify the token using the TokenVerifier lists from cache. + * If verification fails and the TokenVerifier list was retrieved from cache + * try to reload the TokenVerifier list and verify again. + * If none of the verifications succeed, return a result of TokenValidationException + * with suppressed exceptions all the exceptions returned from a TokenVerifier. + */ + private suspend fun TokenVerifierCache.verify(token: String): Result { + val verifiers = getOrEmpty { false } + + val firstResult = verifiers.value.anyVerify(token) + if ( + firstResult.isSuccess || + // already fetched new verifiers, no need to fetch it again + verifiers is CachedValue.CacheMiss + ) { + return firstResult + } + + val refreshedVerifiers = getOrEmpty { true } + return if (refreshedVerifiers != verifiers) { + refreshedVerifiers.value.anyVerify(token) + } else { + // The verifiers didn't change, so the result won't change + firstResult + } + } + + private suspend fun List.anyVerify(token: String): Result { + var exceptions: MutableList? = null + + forEach { verifier -> + try { + val radarToken = verifier.verify(token) + return Result.success(radarToken) + } catch (ex: Throwable) { + if (ex !is AlgorithmMismatchException) { + if (exceptions == null) { + exceptions = mutableListOf() + } + exceptions!!.add(ex) } - exceptions!!.add(ex) } } + + return TokenValidationException("Failed to validate token") + .toFailure(exceptions ?: emptyList()) } - return TokenValidationException("Failed to validate token") - .toFailure(exceptions ?: emptyList()) - } + private suspend fun TokenVerifierCache.getOrEmpty( + refresh: (List) -> Boolean, + ): CachedValue.CacheResult> = + try { + get(refresh) + } catch (ex: Throwable) { + logger.warn("Failed to load authentication algorithm keys: {}", ex.message) + CachedValue.CacheMiss(emptyList()) + } - private suspend fun TokenVerifierCache.getOrEmpty( - refresh: (List) -> Boolean - ): CachedValue.CacheResult> = - try { - get(refresh) - } catch (ex: Throwable) { - logger.warn("Failed to load authentication algorithm keys: {}", ex.message) - CachedValue.CacheMiss(emptyList()) + private fun Throwable.toFailure(causes: Iterable = emptyList()): Result { + causes.forEach { addSuppressed(it) } + return Result.failure(this) } - - private fun Throwable.toFailure(causes: Iterable = emptyList()): Result { - causes.forEach { addSuppressed(it) } - return Result.failure(this) } } -} diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorityReference.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorityReference.kt index 0810a6676..8b4436580 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorityReference.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorityReference.kt @@ -19,9 +19,11 @@ data class AuthorityReference( val role: RoleAuthority, val authority: String, val referent: String?, -): Serializable { +) : Serializable { init { - require(role.scope == RoleAuthority.Scope.GLOBAL || referent != null) { "Non-global authority references require a referent entity" } + require( + role.scope == RoleAuthority.Scope.GLOBAL || referent != null, + ) { "Non-global authority references require a referent entity" } } /** diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorizationOracle.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorizationOracle.kt index b2b148058..b0514d5aa 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorizationOracle.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/AuthorizationOracle.kt @@ -1,8 +1,6 @@ package org.radarbase.auth.authorization import org.radarbase.auth.token.RadarToken -import java.util.* -import java.util.function.Consumer interface AuthorizationOracle { /** @@ -33,7 +31,10 @@ interface AuthorizationOracle { * check whether [identity] has access to a specific entity or global access. * @return true if identity has scope, false otherwise */ - fun hasScope(identity: RadarToken, permission: Permission): Boolean + fun hasScope( + identity: RadarToken, + permission: Permission, + ): Boolean /** * Return a list of referents, per scope, that given [identity] has given [permission] on. @@ -44,12 +45,10 @@ interface AuthorizationOracle { */ fun referentsByScope( identity: RadarToken, - permission: Permission + permission: Permission, ): AuthorityReferenceSet - fun Collection.mayBeGranted(permission: Permission): Boolean = - any { it.mayBeGranted(permission) } + fun Collection.mayBeGranted(permission: Permission): Boolean = any { it.mayBeGranted(permission) } fun RoleAuthority.mayBeGranted(permission: Permission): Boolean } - diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityDetails.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityDetails.kt index 222c589a4..042ac894f 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityDetails.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityDetails.kt @@ -1,7 +1,6 @@ package org.radarbase.auth.authorization import java.util.function.Consumer -import kotlin.math.min /** Entity details to check with AuthorizationOracle. */ data class EntityDetails( @@ -20,47 +19,49 @@ data class EntityDetails( * Return the entity most basic in this EntityDetails. * If no field is set, e.g. this is a global Entity, returns null. */ - fun minimumEntityOrNull(): Permission.Entity? = when { - user != null -> Permission.Entity.USER - source != null -> Permission.Entity.SOURCE - subject != null -> Permission.Entity.SUBJECT - project != null -> Permission.Entity.PROJECT - organization != null -> Permission.Entity.ORGANIZATION - else -> null - } + fun minimumEntityOrNull(): Permission.Entity? = + when { + user != null -> Permission.Entity.USER + source != null -> Permission.Entity.SOURCE + subject != null -> Permission.Entity.SUBJECT + project != null -> Permission.Entity.PROJECT + organization != null -> Permission.Entity.ORGANIZATION + else -> null + } val isGlobal: Boolean get() = minimumEntityOrNull() == null - fun organization(organization: String?) = apply { - this.organization = organization - } + fun organization(organization: String?) = + apply { + this.organization = organization + } - fun project(project: String?) = apply { - this.project = project - } + fun project(project: String?) = + apply { + this.project = project + } - fun subject(subject: String?) = apply { - this.subject = subject - } + fun subject(subject: String?) = + apply { + this.subject = subject + } - fun user(user: String?) = apply { - this.user = user - } + fun user(user: String?) = + apply { + this.user = user + } - fun source(source: String?) = apply { - this.source = source - } + fun source(source: String?) = + apply { + this.source = source + } companion object { val global = EntityDetails() } } -inline fun entityDetails( - config: EntityDetails.() -> Unit, -): EntityDetails = EntityDetails().apply(config) +inline fun entityDetails(config: EntityDetails.() -> Unit): EntityDetails = EntityDetails().apply(config) -fun entityDetailsBuilder( - config: Consumer -): EntityDetails = entityDetails(config::accept) +fun entityDetailsBuilder(config: Consumer): EntityDetails = entityDetails(config::accept) diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityRelationService.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityRelationService.kt index 4035aec70..373ffd716 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityRelationService.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/EntityRelationService.kt @@ -9,6 +9,8 @@ interface EntityRelationService { suspend fun findOrganizationOfProject(project: String): String? /** Whether given [organization] name has a [project] with given name. */ - suspend fun organizationContainsProject(organization: String, project: String): Boolean = - findOrganizationOfProject(project) == organization + suspend fun organizationContainsProject( + organization: String, + project: String, + ): Boolean = findOrganizationOfProject(project) == organization } diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/MPAuthorizationOracle.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/MPAuthorizationOracle.kt index 35601eeeb..e84543df6 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/MPAuthorizationOracle.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/MPAuthorizationOracle.kt @@ -5,7 +5,7 @@ import org.radarbase.kotlin.coroutines.forkAny import java.util.* class MPAuthorizationOracle( - private val relationService: EntityRelationService + private val relationService: EntityRelationService, ) : AuthorizationOracle { /** * Whether [identity] has permission [permission], regarding given [entity]. An additional @@ -34,7 +34,10 @@ class MPAuthorizationOracle( * check whether [identity] has access to a specific entity or global access. * @return true if identity has scope, false otherwise */ - override fun hasScope(identity: RadarToken, permission: Permission): Boolean { + override fun hasScope( + identity: RadarToken, + permission: Permission, + ): Boolean { if (permission.scope() !in identity.scopes) return false if (identity.isClientCredentials) return true @@ -51,7 +54,7 @@ class MPAuthorizationOracle( */ override fun referentsByScope( identity: RadarToken, - permission: Permission + permission: Permission, ): AuthorityReferenceSet { if (identity.isClientCredentials) { return AuthorityReferenceSet(global = true) @@ -108,8 +111,10 @@ class MPAuthorizationOracle( // can be found. val minEntityScope = entity.minimumEntityOrNull() ?: return false return hasAuthority(identity, permission, entity, entityScope) && - (entityScope == minEntityScope || - hasAuthority(identity, permission, entity, minEntityScope)) + ( + entityScope == minEntityScope || + hasAuthority(identity, permission, entity, minEntityScope) + ) } /** @@ -121,36 +126,60 @@ class MPAuthorizationOracle( permission: Permission, entity: EntityDetails, entityScope: Permission.Entity, - ): Boolean = when (entityScope) { - Permission.Entity.MEASUREMENT -> hasAuthority(identity, permission, entity, - Permission.Entity.SOURCE - ) - Permission.Entity.SOURCE -> (!role.isPersonal || - // no specific source is mentioned -> just check the subject - entity.source == null || - entity.source in identity.sources) && - hasAuthority(identity, permission, entity, Permission.Entity.SUBJECT) - Permission.Entity.SUBJECT -> (!role.isPersonal || - entity.subject == identity.subject) && - hasAuthority(identity, permission, entity, Permission.Entity.PROJECT) - Permission.Entity.PROJECT -> when (role.scope) { - RoleAuthority.Scope.PROJECT -> referent == entity.project - RoleAuthority.Scope.ORGANIZATION -> entity.findOrganization() == referent - else -> false - } - Permission.Entity.ORGANIZATION -> when (role.scope) { - RoleAuthority.Scope.PROJECT -> referent == entity.project || entity.organizationContainsProject(referent!!) - RoleAuthority.Scope.ORGANIZATION -> entity.findOrganization() == referent - else -> false + ): Boolean = + when (entityScope) { + Permission.Entity.MEASUREMENT -> + hasAuthority( + identity, + permission, + entity, + Permission.Entity.SOURCE, + ) + + Permission.Entity.SOURCE -> + ( + !role.isPersonal || + // no specific source is mentioned -> just check the subject + entity.source == null || + entity.source in identity.sources + ) && + hasAuthority(identity, permission, entity, Permission.Entity.SUBJECT) + + Permission.Entity.SUBJECT -> + ( + !role.isPersonal || + entity.subject == identity.subject + ) && + hasAuthority(identity, permission, entity, Permission.Entity.PROJECT) + + Permission.Entity.PROJECT -> + when (role.scope) { + RoleAuthority.Scope.PROJECT -> referent == entity.project + RoleAuthority.Scope.ORGANIZATION -> entity.findOrganization() == referent + else -> false + } + + Permission.Entity.ORGANIZATION -> + when (role.scope) { + RoleAuthority.Scope.PROJECT -> + referent == entity.project || + entity.organizationContainsProject( + referent!!, + ) + + RoleAuthority.Scope.ORGANIZATION -> entity.findOrganization() == referent + else -> false + } + + Permission.Entity.USER -> entity.user == identity.username || !role.isPersonal + else -> true } - Permission.Entity.USER -> entity.user == identity.username || !role.isPersonal - else -> true - } private suspend fun EntityDetails.findOrganization(): String? { organization?.let { return it } val p = project ?: return null - return relationService.findOrganizationOfProject(p) + return relationService + .findOrganizationOfProject(p) .also { this.organization = it } } @@ -181,9 +210,7 @@ class MPAuthorizationOracle( * @return An unmodifiable view of the set of allowed authorities. */ @JvmStatic - fun allowedRoles(permission: Permission): Set { - return permissionMatrix[permission] ?: emptySet() - } + fun allowedRoles(permission: Permission): Set = permissionMatrix[permission] ?: emptySet() /** * Static permission matrix based on the currently agreed upon security rules. @@ -196,97 +223,110 @@ class MPAuthorizationOracle( rolePermissions[RoleAuthority.SYS_ADMIN] = Permission.values().asSequence() // Organization admin can do most things, but not view subjects or measurements - rolePermissions[RoleAuthority.ORGANIZATION_ADMIN] = Permission.values().asSequence() - .exclude( - Permission.ORGANIZATION_CREATE, - Permission.SOURCEDATA_CREATE, - Permission.SOURCETYPE_CREATE - ) - .excludeEntities( - Permission.Entity.AUDIT, - Permission.Entity.AUTHORITY, - Permission.Entity.MEASUREMENT - ) + rolePermissions[RoleAuthority.ORGANIZATION_ADMIN] = + Permission + .values() + .asSequence() + .exclude( + Permission.ORGANIZATION_CREATE, + Permission.SOURCEDATA_CREATE, + Permission.SOURCETYPE_CREATE, + ).excludeEntities( + Permission.Entity.AUDIT, + Permission.Entity.AUTHORITY, + Permission.Entity.MEASUREMENT, + ) // for all authorities except for SYS_ADMIN, the authority is scoped to a project, which // is checked elsewhere // Project Admin - has all currently defined permissions except creating new projects // Note: from radar-auth:0.5.7 we allow PROJECT_ADMIN to create measurements. // This can be done by uploading data through the web application. - rolePermissions[RoleAuthority.PROJECT_ADMIN] = Permission.values().asSequence() - .exclude(Permission.PROJECT_CREATE) - .excludeEntities(Permission.Entity.AUDIT, Permission.Entity.AUTHORITY) - .limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) - - /* Project Owner */ + rolePermissions[RoleAuthority.PROJECT_ADMIN] = + Permission + .values() + .asSequence() + .exclude(Permission.PROJECT_CREATE) + .excludeEntities(Permission.Entity.AUDIT, Permission.Entity.AUTHORITY) + .limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) + + // Project Owner // CRUD operations on subjects to allow enrollment - rolePermissions[RoleAuthority.PROJECT_OWNER] = Permission.values().asSequence() - .exclude(Permission.PROJECT_CREATE) - .excludeEntities( - Permission.Entity.AUDIT, - Permission.Entity.AUTHORITY, - Permission.Entity.USER - ) - .limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) - - /* Project affiliate */ + rolePermissions[RoleAuthority.PROJECT_OWNER] = + Permission + .values() + .asSequence() + .exclude(Permission.PROJECT_CREATE) + .excludeEntities( + Permission.Entity.AUDIT, + Permission.Entity.AUTHORITY, + Permission.Entity.USER, + ).limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) + + // Project affiliate // Create, read and update participant (no delete) - rolePermissions[RoleAuthority.PROJECT_AFFILIATE] = Permission.values().asSequence() - .exclude(Permission.SUBJECT_DELETE) - .excludeEntities( - Permission.Entity.AUDIT, - Permission.Entity.AUTHORITY, - Permission.Entity.USER - ) - .limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) - .limitEntityOperations(Permission.Entity.PROJECT, Permission.Operation.READ) - - /* Project analyst */ + rolePermissions[RoleAuthority.PROJECT_AFFILIATE] = + Permission + .values() + .asSequence() + .exclude(Permission.SUBJECT_DELETE) + .excludeEntities( + Permission.Entity.AUDIT, + Permission.Entity.AUTHORITY, + Permission.Entity.USER, + ).limitEntityOperations(Permission.Entity.ORGANIZATION, Permission.Operation.READ) + .limitEntityOperations(Permission.Entity.PROJECT, Permission.Operation.READ) + + // Project analyst // Can read everything except users, authorities and audits - rolePermissions[RoleAuthority.PROJECT_ANALYST] = Permission.values().asSequence() - .excludeEntities( - Permission.Entity.AUDIT, - Permission.Entity.AUTHORITY, - Permission.Entity.USER - ) - // Can add metadata to sources, only read other things. - .filter { p -> - p.operation == Permission.Operation.READ || + rolePermissions[RoleAuthority.PROJECT_ANALYST] = + Permission + .values() + .asSequence() + .excludeEntities( + Permission.Entity.AUDIT, + Permission.Entity.AUTHORITY, + Permission.Entity.USER, + ) + // Can add metadata to sources, only read other things. + .filter { p -> + p.operation == Permission.Operation.READ || p == Permission.SUBJECT_UPDATE - } + } - /* Participant */ + // Participant // Can update and read own data and can read and write own measurements - rolePermissions[RoleAuthority.PARTICIPANT] = sequenceOf( - Permission.SUBJECT_READ, - Permission.SUBJECT_UPDATE, - Permission.MEASUREMENT_CREATE, - Permission.MEASUREMENT_READ - ) - - /* Inactive participant */ + rolePermissions[RoleAuthority.PARTICIPANT] = + sequenceOf( + Permission.SUBJECT_READ, + Permission.SUBJECT_UPDATE, + Permission.MEASUREMENT_CREATE, + Permission.MEASUREMENT_READ, + ) + + // Inactive participant // Doesn't have any permissions rolePermissions[RoleAuthority.INACTIVE_PARTICIPANT] = emptySequence() // invert map - return rolePermissions.asSequence() + return rolePermissions + .asSequence() .flatMap { (role, permissionSeq) -> permissionSeq.map { p -> Pair(p, role) } - } - .groupingBy { (p, _) -> p } + }.groupingBy { (p, _) -> p } .foldTo( EnumMap(Permission::class.java), initialValueSelector = { _, (_, role) -> enumSetOf(role) }, operation = { _, set, (_, role) -> set += role set - } + }, ) } private fun Sequence.limitEntityOperations( entity: Permission.Entity, - vararg operations: Permission.Operation + vararg operations: Permission.Operation, ): Sequence { val operationSet = enumSetOf(*operations) return filter { p: Permission -> p.entity != entity || p.operation in operationSet } @@ -302,10 +342,12 @@ class MPAuthorizationOracle( return filter { it.entity !in entitySet } } - private inline fun > enumSetOf(vararg values: T): EnumSet = EnumSet.noneOf( - T::class.java - ).apply { - values.forEach { add(it) } - } + private inline fun > enumSetOf(vararg values: T): EnumSet = + EnumSet + .noneOf( + T::class.java, + ).apply { + values.forEach { add(it) } + } } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/Permission.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/Permission.kt index 83872587e..db1bf0e0a 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/Permission.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/Permission.kt @@ -4,7 +4,10 @@ package org.radarbase.auth.authorization * Class to represent the different permissions in the RADAR platform. A permission has an entity * and an operation. */ -enum class Permission(val entity: Entity, val operation: Operation) { +enum class Permission( + val entity: Entity, + val operation: Operation, +) { SOURCETYPE_CREATE(Entity.SOURCETYPE, Operation.CREATE), SOURCETYPE_READ(Entity.SOURCETYPE, Operation.READ), SOURCETYPE_UPDATE(Entity.SOURCETYPE, Operation.UPDATE), @@ -55,18 +58,32 @@ enum class Permission(val entity: Entity, val operation: Operation) { AUTHORITY_READ(Entity.AUTHORITY, Operation.READ), MEASUREMENT_READ(Entity.MEASUREMENT, Operation.READ), - MEASUREMENT_CREATE(Entity.MEASUREMENT, Operation.CREATE); + MEASUREMENT_CREATE(Entity.MEASUREMENT, Operation.CREATE), + ; enum class Entity { // ManagementPortal entities - SOURCETYPE, SOURCEDATA, SOURCE, SUBJECT, USER, ROLE, ORGANIZATION, PROJECT, OAUTHCLIENTS, - AUDIT, AUTHORITY, + SOURCETYPE, + SOURCEDATA, + SOURCE, + SUBJECT, + USER, + ROLE, + ORGANIZATION, + PROJECT, + OAUTHCLIENTS, + AUDIT, + AUTHORITY, + // RMT measurements - MEASUREMENT + MEASUREMENT, } enum class Operation { - CREATE, READ, UPDATE, DELETE + CREATE, + READ, + UPDATE, + DELETE, } override fun toString(): String = "Permission{entity=$entity, operation=$operation}" @@ -81,21 +98,21 @@ enum class Permission(val entity: Entity, val operation: Operation) { companion object { /** Returns all available scope names. */ @JvmStatic - fun scopes(): Array { - return values() + fun scopes(): Array = + values() .map { obj: Permission -> obj.scope() } .toTypedArray() - } /** Return matching permission. */ - fun of(entity: Entity, operation: Operation): Permission = + fun of( + entity: Entity, + operation: Operation, + ): Permission = requireNotNull( - values().firstOrNull { p -> p.entity == entity && p.operation == operation } + values().firstOrNull { p -> p.entity == entity && p.operation == operation }, ) { "No permission found for given entity and operation" } @JvmStatic - fun ofScope(scope: String): Permission { - return valueOf(scope.replace('.', '_')) - } + fun ofScope(scope: String): Permission = valueOf(scope.replace('.', '_')) } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/authorization/RoleAuthority.kt b/radar-auth/src/main/java/org/radarbase/auth/authorization/RoleAuthority.kt index 7a617a6d9..9facebe9d 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/authorization/RoleAuthority.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/authorization/RoleAuthority.kt @@ -16,12 +16,15 @@ enum class RoleAuthority( PROJECT_ANALYST(Scope.PROJECT, false), PARTICIPANT(Scope.PROJECT, true), INACTIVE_PARTICIPANT(Scope.PROJECT, true), - ORGANIZATION_ADMIN(Scope.ORGANIZATION, false); + ORGANIZATION_ADMIN(Scope.ORGANIZATION, false), + ; val authority: String = "ROLE_$name" enum class Scope { - GLOBAL, ORGANIZATION, PROJECT + GLOBAL, + ORGANIZATION, + PROJECT, } companion object { @@ -35,11 +38,12 @@ enum class RoleAuthority( * @throws NullPointerException if given authority is null. */ @JvmStatic - fun valueOfAuthority(authority: String): RoleAuthority = valueOf( - authority - .uppercase() - .removePrefix("ROLE_") - ) + fun valueOfAuthority(authority: String): RoleAuthority = + valueOf( + authority + .uppercase() + .removePrefix("ROLE_"), + ) /** * Find role authority based on authority name. @@ -47,12 +51,11 @@ enum class RoleAuthority( * @return RoleAuthority or null if no role authority exists with the given name. */ @JvmStatic - fun valueOfAuthorityOrNull(authority: String): RoleAuthority? { - return try { + fun valueOfAuthorityOrNull(authority: String): RoleAuthority? = + try { valueOfAuthority(authority) } catch (ex: IllegalArgumentException) { null } - } } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/exception/IdpException.kt b/radar-auth/src/main/java/org/radarbase/auth/exception/IdpException.kt index 609289cd7..7304432a9 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/exception/IdpException.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/exception/IdpException.kt @@ -1,19 +1,17 @@ package org.radarbase.auth.exception /** Exception indicating a problem with the Identity Provider */ -class IdpException: Throwable { +class IdpException : Throwable { var token: String? = null constructor(message: String) : super(message) - constructor(message: String, token: String) : super(message) - { + constructor(message: String, token: String) : super(message) { this.token = token } constructor(message: String, cause: Throwable) : super(message, cause) - constructor(message: String, cause: Throwable, token: String) : super(message, cause) - { + constructor(message: String, cause: Throwable, token: String) : super(message, cause) { this.token = token } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/exception/InvalidPublicKeyException.kt b/radar-auth/src/main/java/org/radarbase/auth/exception/InvalidPublicKeyException.kt index 514832c1f..caa985f51 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/exception/InvalidPublicKeyException.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/exception/InvalidPublicKeyException.kt @@ -1,6 +1,6 @@ package org.radarbase.auth.exception -class InvalidPublicKeyException: TokenValidationException { +class InvalidPublicKeyException : TokenValidationException { constructor(message: String) : super(message) constructor(message: String, cause: Throwable) : super(message, cause) diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/ECPEMCertificateParser.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/ECPEMCertificateParser.kt index 0cadf2c8f..b0882fa25 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/ECPEMCertificateParser.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/ECPEMCertificateParser.kt @@ -11,9 +11,10 @@ class ECPEMCertificateParser : PEMCertificateParser { override val keyHeader: String get() = "-----BEGIN EC PUBLIC KEY-----" - override fun parseAlgorithm(publicKey: String): Algorithm = publicKey - .parsePublicKey(keyFactoryType) - .toAlgorithm() + override fun parseAlgorithm(publicKey: String): Algorithm = + publicKey + .parsePublicKey(keyFactoryType) + .toAlgorithm() override val keyFactoryType: String get() = ALGORITHM_EC diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/Extensions.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/Extensions.kt index bdc9cc51b..60ed46358 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/Extensions.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/Extensions.kt @@ -4,24 +4,27 @@ import com.auth0.jwt.algorithms.Algorithm import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey -fun RSAPublicKey.toAlgorithm(hashSize: RSAJsonWebKey.HashSize = RSAJsonWebKey.HashSize.RS256): Algorithm = when (hashSize) { - RSAJsonWebKey.HashSize.RS256 -> Algorithm.RSA256(this, null) - RSAJsonWebKey.HashSize.RS384 -> Algorithm.RSA384(this, null) - RSAJsonWebKey.HashSize.RS512 -> Algorithm.RSA512(this, null) -} +fun RSAPublicKey.toAlgorithm(hashSize: RSAJsonWebKey.HashSize = RSAJsonWebKey.HashSize.RS256): Algorithm = + when (hashSize) { + RSAJsonWebKey.HashSize.RS256 -> Algorithm.RSA256(this, null) + RSAJsonWebKey.HashSize.RS384 -> Algorithm.RSA384(this, null) + RSAJsonWebKey.HashSize.RS512 -> Algorithm.RSA512(this, null) + } fun ECPublicKey.toAlgorithm(): Algorithm { - val keySize = when (val orderLength = params.order.bitLength()) { - in 0 .. 256 -> ECDSAJsonWebKey.Curve.ES256 - in 257 .. 384 -> ECDSAJsonWebKey.Curve.ES384 - in 385 .. 521 -> ECDSAJsonWebKey.Curve.ES512 - else -> throw IllegalArgumentException("Unknown ECDSA order length $orderLength") - } + val keySize = + when (val orderLength = params.order.bitLength()) { + in 0..256 -> ECDSAJsonWebKey.Curve.ES256 + in 257..384 -> ECDSAJsonWebKey.Curve.ES384 + in 385..521 -> ECDSAJsonWebKey.Curve.ES512 + else -> throw IllegalArgumentException("Unknown ECDSA order length $orderLength") + } return toAlgorithm(keySize) } -fun ECPublicKey.toAlgorithm(keySize: ECDSAJsonWebKey.Curve): Algorithm = when (keySize) { - ECDSAJsonWebKey.Curve.ES256 -> Algorithm.ECDSA256(this, null) - ECDSAJsonWebKey.Curve.ES384 -> Algorithm.ECDSA384(this, null) - ECDSAJsonWebKey.Curve.ES512 -> Algorithm.ECDSA512(this, null) -} +fun ECPublicKey.toAlgorithm(keySize: ECDSAJsonWebKey.Curve): Algorithm = + when (keySize) { + ECDSAJsonWebKey.Curve.ES256 -> Algorithm.ECDSA256(this, null) + ECDSAJsonWebKey.Curve.ES384 -> Algorithm.ECDSA384(this, null) + ECDSAJsonWebKey.Curve.ES512 -> Algorithm.ECDSA512(this, null) + } diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKey.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKey.kt index 0889628f1..3a59bee67 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKey.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKey.kt @@ -8,7 +8,6 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import org.radarbase.auth.exception.InvalidPublicKeyException -import java.lang.IllegalArgumentException /** * Represents the OAuth 2.0 JsonWebKey for token verification. @@ -17,8 +16,10 @@ import java.lang.IllegalArgumentException sealed interface JsonWebKey { val alg: String? val kty: String + /** X.509 Certificate Chain. */ val x5c: List + /** X.509 Certificate SHA-1 thumbprint. */ val x5t: String? @@ -29,13 +30,16 @@ sealed interface JsonWebKey { } object JavaWebKeyPolymorphicSerializer : JsonContentPolymorphicSerializer(JsonWebKey::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = if ("value" in element.jsonObject) { - MPJsonWebKey.serializer() - } else when (element.jsonObject["kty"]?.jsonPrimitive?.content) { - "EC" -> ECDSAJsonWebKey.serializer() - "RSA" -> RSAJsonWebKey.serializer() - else -> throw SerializationException("Unknown JavaWebKey") - } + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + if ("value" in element.jsonObject) { + MPJsonWebKey.serializer() + } else { + when (element.jsonObject["kty"]?.jsonPrimitive?.content) { + "EC" -> ECDSAJsonWebKey.serializer() + "RSA" -> RSAJsonWebKey.serializer() + else -> throw SerializationException("Unknown JavaWebKey") + } + } } @Serializable @@ -58,7 +62,7 @@ data class RSAJsonWebKey( enum class HashSize { RS256, RS384, - RS512; + RS512, } } @@ -91,10 +95,12 @@ data class ECDSAJsonWebKey( } } - enum class Curve(val ecStdName: String) { + enum class Curve( + val ecStdName: String, + ) { ES256("secp256r1"), ES384("secp384r1"), - ES512("secp521r1"); + ES512("secp521r1"), } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKeySet.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKeySet.kt index 522f83db0..5bedc7f82 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKeySet.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/JsonWebKeySet.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class JsonWebKeySet( - val keys: List = emptyList() + val keys: List = emptyList(), ) diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkAlgorithmParser.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkAlgorithmParser.kt index 4ce32ea67..7a8d13b53 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkAlgorithmParser.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkAlgorithmParser.kt @@ -12,72 +12,84 @@ import java.security.KeyFactory import java.security.NoSuchAlgorithmException import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey -import java.security.spec.* +import java.security.spec.ECGenParameterSpec +import java.security.spec.ECParameterSpec +import java.security.spec.ECPoint +import java.security.spec.ECPublicKeySpec +import java.security.spec.RSAPublicKeySpec import java.util.* class JwkAlgorithmParser( private val supportedAlgorithmsForWebKeySets: List, ) : JwkParser { - constructor() : this(listOf(ECPEMCertificateParser(), RSAPEMCertificateParser())) override fun parse(key: JsonWebKey): Algorithm { if (key.x5c.isNotEmpty()) { - val x5cAlgorithm = supportedAlgorithmsForWebKeySets - .firstNotNullOfOrNull { parser -> - try { - parser.parseAlgorithm(key.x5c[0]) - } catch (ex: Exception) { - null + val x5cAlgorithm = + supportedAlgorithmsForWebKeySets + .firstNotNullOfOrNull { parser -> + try { + parser.parseAlgorithm(key.x5c[0]) + } catch (ex: Exception) { + null + } } - } if (x5cAlgorithm != null) return x5cAlgorithm } return when (key) { - is MPJsonWebKey -> supportedAlgorithmsForWebKeySets - .firstOrNull { algorithm -> key.value.startsWith(algorithm.keyHeader) } - ?.parseAlgorithm(key.value) - ?: throw TokenValidationException("Unsupported public key: $key") - is RSAJsonWebKey -> try { - val keyFactory: KeyFactory = KeyFactory.getInstance(ALGORITHM_RSA) - val publicKeySpec = RSAPublicKeySpec( - BigInteger(1, Base64.getUrlDecoder().decode(key.n)), - BigInteger(1, Base64.getUrlDecoder().decode(key.e)) - ) - (keyFactory.generatePublic(publicKeySpec) as RSAPublicKey) - .toAlgorithm(hashSize = key.keySize()) - } catch (e: GeneralSecurityException) { - throw InvalidPublicKeyException("Invalid public key", e) - } - is ECDSAJsonWebKey -> try { - val keyFactory = KeyFactory.getInstance(ALGORITHM_EC) - val keySize = key.curve() - val ecPublicKeySpec = ECPublicKeySpec( - ECPoint( - BigInteger(1, Base64.getUrlDecoder().decode(key.x)), - BigInteger(1, Base64.getUrlDecoder().decode(key.y)) - ), - AlgorithmParameters.getInstance(ALGORITHM_EC).run { - init(ECGenParameterSpec(keySize.ecStdName)) - getParameterSpec(ECParameterSpec::class.java) - } - ) - (keyFactory.generatePublic(ecPublicKeySpec) as ECPublicKey) - .toAlgorithm(keySize = key.curve()) - } catch (e: NoSuchAlgorithmException) { - throw InvalidPublicKeyException("Invalid algorithm to generate key", e) - } catch (e: GeneralSecurityException) { - throw InvalidPublicKeyException("Invalid public key", e) - } + is MPJsonWebKey -> + supportedAlgorithmsForWebKeySets + .firstOrNull { algorithm -> key.value.startsWith(algorithm.keyHeader) } + ?.parseAlgorithm(key.value) + ?: throw TokenValidationException("Unsupported public key: $key") + + is RSAJsonWebKey -> + try { + val keyFactory: KeyFactory = KeyFactory.getInstance(ALGORITHM_RSA) + val publicKeySpec = + RSAPublicKeySpec( + BigInteger(1, Base64.getUrlDecoder().decode(key.n)), + BigInteger(1, Base64.getUrlDecoder().decode(key.e)), + ) + (keyFactory.generatePublic(publicKeySpec) as RSAPublicKey) + .toAlgorithm(hashSize = key.keySize()) + } catch (e: GeneralSecurityException) { + throw InvalidPublicKeyException("Invalid public key", e) + } + + is ECDSAJsonWebKey -> + try { + val keyFactory = KeyFactory.getInstance(ALGORITHM_EC) + val keySize = key.curve() + val ecPublicKeySpec = + ECPublicKeySpec( + ECPoint( + BigInteger(1, Base64.getUrlDecoder().decode(key.x)), + BigInteger(1, Base64.getUrlDecoder().decode(key.y)), + ), + AlgorithmParameters.getInstance(ALGORITHM_EC).run { + init(ECGenParameterSpec(keySize.ecStdName)) + getParameterSpec(ECParameterSpec::class.java) + }, + ) + (keyFactory.generatePublic(ecPublicKeySpec) as ECPublicKey) + .toAlgorithm(keySize = key.curve()) + } catch (e: NoSuchAlgorithmException) { + throw InvalidPublicKeyException("Invalid algorithm to generate key", e) + } catch (e: GeneralSecurityException) { + throw InvalidPublicKeyException("Invalid public key", e) + } } } - override fun toString(): String = buildString(50) { - append("JwkAlgorithmParser') } - append('>') - } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkParser.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkParser.kt index b5e78774d..819e8846a 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkParser.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwkParser.kt @@ -1,7 +1,6 @@ package org.radarbase.auth.jwks import com.auth0.jwt.algorithms.Algorithm -import org.radarbase.auth.jwks.JsonWebKey interface JwkParser { fun parse(key: JsonWebKey): Algorithm diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwksTokenVerifierLoader.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwksTokenVerifierLoader.kt index aae8d7551..8458f8db8 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/JwksTokenVerifierLoader.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/JwksTokenVerifierLoader.kt @@ -3,15 +3,18 @@ package org.radarbase.auth.jwks import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.interfaces.Verification -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.request.accept +import io.ktor.client.request.request +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.isSuccess +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json @@ -28,20 +31,21 @@ class JwksTokenVerifierLoader( private val resourceName: String, private val algorithmParser: JwkParser, ) : TokenVerifierLoader { - override suspend fun fetch(): List { - val keySet = try { - fetchPublicKeyInfo() - } catch (ex: Exception) { - logger.warn("Failed to fetch token for {}: {}", url, ex.message) - return listOf() - } + val keySet = + try { + fetchPublicKeyInfo() + } catch (ex: Exception) { + logger.warn("Failed to fetch token for {}: {}", url, ex.message) + return listOf() + } return buildList(keySet.keys.size) { keySet.keys.forEach { key -> try { add( - algorithmParser.parse(key) - .toTokenVerifier(resourceName) + algorithmParser + .parse(key) + .toTokenVerifier(resourceName), ) } catch (ex: Exception) { logger.error("Failed to parse key from {}: {}", url, ex.message) @@ -50,49 +54,57 @@ class JwksTokenVerifierLoader( } } - private suspend fun fetchPublicKeyInfo(): JsonWebKeySet = withContext(Dispatchers.IO) { - logger.info("Getting the JWT public key at {}", url) - val response = httpClient.request(url) + private suspend fun fetchPublicKeyInfo(): JsonWebKeySet = + withContext(Dispatchers.IO) { + logger.info("Getting the JWT public key at {}", url) + val response = httpClient.request(url) - if (!response.status.isSuccess()) { - throw TokenValidationException("Cannot fetch token keys (${response.status}) - ${response.bodyAsText()}") - } + if (!response.status.isSuccess()) { + throw TokenValidationException("Cannot fetch token keys (${response.status}) - ${response.bodyAsText()}") + } - response.body() - } + response.body() + } override fun toString(): String = "MPTokenKeyAlgorithmKeyLoader" companion object { @JvmStatic @JvmOverloads - fun Algorithm.toTokenVerifier(resourceName: String, builder: Verification.() -> Unit = {}): JwtTokenVerifier { - val verifier = JWT.require(this).run { - withClaimPresence(SCOPE_CLAIM) - withAudience(resourceName) - builder() - build() - } + fun Algorithm.toTokenVerifier( + resourceName: String, + builder: Verification.() -> Unit = {}, + ): JwtTokenVerifier { + val verifier = + JWT.require(this).run { + withClaimPresence(SCOPE_CLAIM) + withAudience(resourceName) + builder() + build() + } return JwtTokenVerifier(name, verifier) } private val logger = LoggerFactory.getLogger(JwksTokenVerifierLoader::class.java) - private val httpClient = HttpClient(CIO).config { - install(HttpTimeout) { - connectTimeoutMillis = Duration.ofSeconds(10).toMillis() - socketTimeoutMillis = Duration.ofSeconds(10).toMillis() - requestTimeoutMillis = Duration.ofSeconds(30).toMillis() - } - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - coerceInputValues = true - }) - } - defaultRequest { - accept(ContentType.Application.Json) + private val httpClient = + HttpClient(CIO).config { + install(HttpTimeout) { + connectTimeoutMillis = Duration.ofSeconds(10).toMillis() + socketTimeoutMillis = Duration.ofSeconds(10).toMillis() + requestTimeoutMillis = Duration.ofSeconds(30).toMillis() + } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + coerceInputValues = true + }, + ) + } + defaultRequest { + accept(ContentType.Application.Json) + } } - } } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/PEMCertificateParser.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/PEMCertificateParser.kt index 0525fc9c9..5919fc27f 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/PEMCertificateParser.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/PEMCertificateParser.kt @@ -40,10 +40,11 @@ interface PEMCertificateParser { * @param publicKey the public key to parse * @return a PublicKey object representing the supplied public key */ - inline fun String.parsePublicKey(keyFactoryType: String): T { - val trimmedKey = replace("-----BEGIN ([A-Z]+ )?PUBLIC KEY-----".toRegex(), "") - .replace("-----END ([A-Z]+ )?PUBLIC KEY-----".toRegex(), "") - .trim { it <= ' ' } + inline fun String.parsePublicKey(keyFactoryType: String): T { + val trimmedKey = + replace("-----BEGIN ([A-Z]+ )?PUBLIC KEY-----".toRegex(), "") + .replace("-----END ([A-Z]+ )?PUBLIC KEY-----".toRegex(), "") + .trim { it <= ' ' } return try { val decodedPublicKey = Base64.getDecoder().decode(trimmedKey) val spec = X509EncodedKeySpec(decodedPublicKey) diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwks/RSAPEMCertificateParser.kt b/radar-auth/src/main/java/org/radarbase/auth/jwks/RSAPEMCertificateParser.kt index 2e93679f7..7c75f6843 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwks/RSAPEMCertificateParser.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwks/RSAPEMCertificateParser.kt @@ -13,7 +13,8 @@ class RSAPEMCertificateParser : PEMCertificateParser { override val keyHeader: String get() = "-----BEGIN PUBLIC KEY-----" - override fun parseAlgorithm(publicKey: String): Algorithm = publicKey - .parsePublicKey(keyFactoryType) - .toAlgorithm() + override fun parseAlgorithm(publicKey: String): Algorithm = + publicKey + .parsePublicKey(keyFactoryType) + .toAlgorithm() } diff --git a/radar-auth/src/main/java/org/radarbase/auth/jwt/JwtTokenVerifier.kt b/radar-auth/src/main/java/org/radarbase/auth/jwt/JwtTokenVerifier.kt index bee009e5f..d2aa6ec11 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/jwt/JwtTokenVerifier.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/jwt/JwtTokenVerifier.kt @@ -17,22 +17,28 @@ class JwtTokenVerifier( private val algorithm: String, private val verifier: JWTVerifier, ) : TokenVerifier { - override suspend fun verify(token: String): RadarToken = try { - logger.debug("Attempting to verify JWT header {} and payload {}", token.split(".")[0], token.split(".")[1]) + override suspend fun verify(token: String): RadarToken = + try { + logger.debug("Attempting to verify JWT header {} and payload {}", token.split(".")[0], token.split(".")[1]) - val jwt = verifier.verify(token) + val jwt = verifier.verify(token) - // Do not print full token with signature to avoid exposing valid token in logs. - logger.debug("Verified JWT header {} and payload {}", jwt.header, jwt.payload) + // Do not print full token with signature to avoid exposing valid token in logs. + logger.debug("Verified JWT header {} and payload {}", jwt.header, jwt.payload) - jwt.toRadarToken() - } catch (ex: Throwable) { - when (ex) { - is SignatureVerificationException -> logger.debug("Client presented a token with an incorrect signature.") - is JWTVerificationException -> logger.debug("Verifier {} did not accept token: {}", verifier.javaClass, ex.message) + jwt.toRadarToken() + } catch (ex: Throwable) { + when (ex) { + is SignatureVerificationException -> logger.debug("Client presented a token with an incorrect signature.") + is JWTVerificationException -> + logger.debug( + "Verifier {} did not accept token: {}", + verifier.javaClass, + ex.message, + ) + } + throw ex } - throw ex - } override fun toString(): String = "JwtTokenVerifier(algorithm=$algorithm)" @@ -66,48 +72,55 @@ class JwtTokenVerifier( username = claims.stringClaim(USER_NAME_CLAIM), ) } + fun Map.stringListClaim(name: String): List? { val claim = get(name) ?: return null - val claimList = try { - claim.asList(String::class.java) - } catch (ex: JWTDecodeException) { - // skip - null - } - val claims = claimList - ?: claim.asString()?.split(' ') - ?: return null + val claimList = + try { + claim.asList(String::class.java) + } catch (ex: JWTDecodeException) { + // skip + null + } + val claims = + claimList + ?: claim.asString()?.split(' ') + ?: return null return claims.mapNotNull { it?.trimNotEmpty() } } - fun Map.stringClaim(name: String): String? = get(name)?.asString() - ?.trimNotEmpty() + fun Map.stringClaim(name: String): String? = + get(name) + ?.asString() + ?.trimNotEmpty() - private fun String.trimNotEmpty(): String? = trim() - .takeIf { it.isNotEmpty() } + private fun String.trimNotEmpty(): String? = + trim() + .takeIf { it.isNotEmpty() } - private fun Map.parseRoles(): Set = buildSet { - stringListClaim(AUTHORITIES_CLAIM)?.forEach { - val role = RoleAuthority.valueOfAuthorityOrNull(it) - if (role?.scope == RoleAuthority.Scope.GLOBAL) { - add(AuthorityReference(role)) + private fun Map.parseRoles(): Set = + buildSet { + stringListClaim(AUTHORITIES_CLAIM)?.forEach { + val role = RoleAuthority.valueOfAuthorityOrNull(it) + if (role?.scope == RoleAuthority.Scope.GLOBAL) { + add(AuthorityReference(role)) + } } - } - stringListClaim(ROLES_CLAIM)?.forEach { input -> - val v = input.split(':') - try { - add( - if (v.size == 1 || v[1].isEmpty()) { - AuthorityReference(v[0]) - } else { - AuthorityReference(v[1], v[0]) - } - ) - } catch (ex: IllegalArgumentException) { - // skip + stringListClaim(ROLES_CLAIM)?.forEach { input -> + val v = input.split(':') + try { + add( + if (v.size == 1 || v[1].isEmpty()) { + AuthorityReference(v[0]) + } else { + AuthorityReference(v[1], v[0]) + }, + ) + } catch (ex: IllegalArgumentException) { + // skip + } } } - } } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosSessionDTO.kt b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosSessionDTO.kt index 218792df8..e7222e391 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosSessionDTO.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosSessionDTO.kt @@ -11,7 +11,6 @@ import org.radarbase.auth.token.DataRadarToken import org.radarbase.auth.token.RadarToken import java.time.Instant - @Serializable class KratosSessionDTO( val id: String, @@ -25,7 +24,7 @@ class KratosSessionDTO( @Serializable(with = InstantSerializer::class) val issued_at: Instant, val identity: Identity, - val devices: ArrayList + val devices: ArrayList, ) { @Serializable data class AuthenticationMethod( @@ -33,7 +32,7 @@ class KratosSessionDTO( val aal: String? = null, @Serializable(with = InstantSerializer::class) val completed_at: Instant? = null, - val provider: String? = null + val provider: String? = null, ) @Serializable @@ -58,51 +57,51 @@ class KratosSessionDTO( val created_at: Instant? = null, @Serializable(with = InstantSerializer::class) val updated_at: Instant? = null, - ) - { - - - fun parseRoles(): Set = buildSet { - if (metadata_public?.authorities?.isNotEmpty() == true) { - for (roleValue in metadata_public.authorities) { - val authority = RoleAuthority.valueOfAuthorityOrNull(roleValue) - if (authority?.scope == RoleAuthority.Scope.GLOBAL) { - add(AuthorityReference(authority)) + ) { + fun parseRoles(): Set = + buildSet { + if (metadata_public?.authorities?.isNotEmpty() == true) { + for (roleValue in metadata_public.authorities) { + val authority = RoleAuthority.valueOfAuthorityOrNull(roleValue) + if (authority?.scope == RoleAuthority.Scope.GLOBAL) { + add(AuthorityReference(authority)) + } } } - } - if (metadata_public?.roles?.isNotEmpty() == true) { - for (roleValue in metadata_public.roles) { - val role = RoleAuthority.valueOfAuthorityOrNull(roleValue) - if (role?.scope == RoleAuthority.Scope.GLOBAL) { - add(AuthorityReference(role)) + if (metadata_public?.roles?.isNotEmpty() == true) { + for (roleValue in metadata_public.roles) { + val role = RoleAuthority.valueOfAuthorityOrNull(roleValue) + if (role?.scope == RoleAuthority.Scope.GLOBAL) { + add(AuthorityReference(role)) + } } } } - } } - @Serializable - class Traits ( + class Traits( val name: String? = null, val email: String? = null, ) @Serializable - class Metadata ( + class Metadata( val roles: List, val authorities: Set, val scope: List, val sources: List, val aud: List, - val mp_login: String? + val mp_login: String?, ) - fun toDataRadarToken() : DataRadarToken { - return DataRadarToken( + fun toDataRadarToken(): DataRadarToken = + DataRadarToken( roles = this.identity.parseRoles(), - scopes = this.identity.metadata_public?.scope?.toSet() ?: emptySet(), + scopes = + this.identity.metadata_public + ?.scope + ?.toSet() ?: emptySet(), sources = this.identity.metadata_public?.sources ?: emptyList(), grantType = "session", subject = this.identity.id, @@ -114,19 +113,19 @@ class KratosSessionDTO( type = "type", clientId = "ManagementPortalapp", username = this.identity.metadata_public?.mp_login, - authenticatorAssuranceLevel = this.authenticator_assurance_level + authenticatorAssuranceLevel = this.authenticator_assurance_level, ) - } } object InstantSerializer : KSerializer { override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): Instant { - return Instant.parse(decoder.decodeString()) - } + override fun deserialize(decoder: Decoder): Instant = Instant.parse(decoder.decodeString()) - override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: Instant) { + override fun serialize( + encoder: kotlinx.serialization.encoding.Encoder, + value: Instant, + ) { encoder.encodeString(value.toString()) } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifier.kt b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifier.kt index 321ec1a92..e3ee7d13d 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifier.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifier.kt @@ -1,29 +1,34 @@ package org.radarbase.auth.kratos + import org.radarbase.auth.authentication.TokenVerifier import org.radarbase.auth.exception.IdpException import org.radarbase.auth.exception.InsufficientAuthenticationLevelException import org.radarbase.auth.token.RadarToken import org.slf4j.LoggerFactory -//TODO Better error screen for no AAL2 -class KratosTokenVerifier(private val sessionService: SessionService, private val requireAal2: Boolean) : TokenVerifier { +// TODO Better error screen for no AAL2 +class KratosTokenVerifier( + private val sessionService: SessionService, + private val requireAal2: Boolean, +) : TokenVerifier { @Throws(IdpException::class) - override suspend fun verify(token: String): RadarToken = try { - val kratosSession = sessionService.getSession(token) + override suspend fun verify(token: String): RadarToken = + try { + val kratosSession = sessionService.getSession(token) - val radarToken = kratosSession.toDataRadarToken() - if (radarToken.authenticatorAssuranceLevel != RadarToken.AuthenticatorAssuranceLevel.aal2 && requireAal2) - { - val msg = "found a token of with aal: ${radarToken.authenticatorAssuranceLevel}, which is insufficient for this" + - " action" - throw InsufficientAuthenticationLevelException(msg) + val radarToken = kratosSession.toDataRadarToken() + if (radarToken.authenticatorAssuranceLevel != RadarToken.AuthenticatorAssuranceLevel.AAL2 && requireAal2) { + val msg = + "found a token of with aal: ${radarToken.authenticatorAssuranceLevel}, which is insufficient for this" + + " action" + throw InsufficientAuthenticationLevelException(msg) + } + radarToken + } catch (ex: InsufficientAuthenticationLevelException) { + throw ex + } catch (ex: Throwable) { + throw IdpException("could not verify token", ex) } - radarToken - } catch (ex: InsufficientAuthenticationLevelException) { - throw ex - } catch (ex: Throwable) { - throw IdpException("could not verify token", ex) - } override fun toString(): String = "org.radarbase.auth.kratos.KratosTokenVerifier" diff --git a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifierLoader.kt b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifierLoader.kt index 7c567cc3a..4fb985f26 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifierLoader.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/kratos/KratosTokenVerifierLoader.kt @@ -1,14 +1,16 @@ package org.radarbase.auth.kratos + import org.radarbase.auth.authentication.TokenVerifier import org.radarbase.auth.authentication.TokenVerifierLoader -class KratosTokenVerifierLoader(private val serverUrl: String, private val requireAal2: Boolean) : TokenVerifierLoader { - - override suspend fun fetch(): List { - return listOf( - KratosTokenVerifier(SessionService(serverUrl), requireAal2) +class KratosTokenVerifierLoader( + private val serverUrl: String, + private val requireAal2: Boolean, +) : TokenVerifierLoader { + override suspend fun fetch(): List = + listOf( + KratosTokenVerifier(SessionService(serverUrl), requireAal2), ) - } override fun toString(): String = "KratosTokenKeyAlgorithmKeyLoader" } diff --git a/radar-auth/src/main/java/org/radarbase/auth/kratos/SessionService.kt b/radar-auth/src/main/java/org/radarbase/auth/kratos/SessionService.kt index 28dab9799..d18846e86 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/kratos/SessionService.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/kratos/SessionService.kt @@ -1,13 +1,17 @@ package org.radarbase.auth.kratos -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.accept +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.url +import io.ktor.http.ContentType +import io.ktor.http.isSuccess +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable @@ -19,8 +23,9 @@ import java.time.Duration /** * Service class for handling Kratos sessions but may be extended in the future. */ -class SessionService(private val serverUrl: String) { - +class SessionService( + private val serverUrl: String, +) { /** Get a [KratosSessionDTO] for a given session token. Returns the generated [KratosSessionDTO] */ @Throws(IdpException::class) suspend fun getSession(token: String): KratosSessionDTO { @@ -32,11 +37,12 @@ class SessionService(private val serverUrl: String) { log.debug("requesting session at $address") withContext(Dispatchers.IO) { - val response = httpClient.get { - header("Cookie", cookie) - url(address) - accept(ContentType.Application.Json) - } + val response = + httpClient.get { + header("Cookie", cookie) + url(address) + accept(ContentType.Application.Json) + } if (response.status.isSuccess()) { kratosSession = response.body() @@ -57,11 +63,12 @@ class SessionService(private val serverUrl: String) { val address = "$serverUrl/self-service/logout/browser" log.debug("requesting logout url at $address") withContext(Dispatchers.IO) { - val response = httpClient.get { - header("Cookie", cookie) - url(address) - accept(ContentType.Application.Json) - } + val response = + httpClient.get { + header("Cookie", cookie) + url(address) + accept(ContentType.Application.Json) + } if (response.status.isSuccess()) { logOutResponse = response.body() @@ -74,25 +81,28 @@ class SessionService(private val serverUrl: String) { } @Serializable - class LogoutResponse ( + class LogoutResponse( val logout_url: String?, ) companion object { private val log = LoggerFactory.getLogger(SessionService::class.java) - private val httpClient = HttpClient(CIO).config { - install(HttpTimeout) { - connectTimeoutMillis = Duration.ofSeconds(10).toMillis() - socketTimeoutMillis = Duration.ofSeconds(10).toMillis() - requestTimeoutMillis = Duration.ofSeconds(300).toMillis() + private val httpClient = + HttpClient(CIO).config { + install(HttpTimeout) { + connectTimeoutMillis = Duration.ofSeconds(10).toMillis() + socketTimeoutMillis = Duration.ofSeconds(10).toMillis() + requestTimeoutMillis = Duration.ofSeconds(300).toMillis() + } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + coerceInputValues = true + }, + ) + } } - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - coerceInputValues = true - }) - } - } } } diff --git a/radar-auth/src/main/java/org/radarbase/auth/token/DataRadarToken.kt b/radar-auth/src/main/java/org/radarbase/auth/token/DataRadarToken.kt index 9de954830..bbd48bdbf 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/token/DataRadarToken.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/token/DataRadarToken.kt @@ -13,104 +13,91 @@ data class DataRadarToken( * @return non-null set describing the roles defined in this token. */ override val roles: Set, - /** * Get a list of scopes assigned to this token. * @return non-null list of scope names */ override val scopes: Set, - /** * Get a list of source names associated with this token. * @return non-null list of source names */ override val sources: List = emptyList(), - /** * Get this token's OAuth2 grant type. * @return grant type */ override val grantType: String?, - /** * Get the token subject. * @return non-null subject */ override val subject: String? = null, - /** * Get the token username. */ override val username: String? = null, - /** * Get the date this token was issued. * @return date this token was issued or null */ override val issuedAt: Instant? = null, - /** * Get the date this token expires. * @return date this token expires or null */ override val expiresAt: Instant, - /** * Get the audience of the token. * @return non-null list of resources that are allowed to accept the token */ override val audience: List = listOf(), - /** * Get a string representation of this token. * @return string representation of this token */ override val token: String? = null, - /** * Get the issuer. * @return issuer */ override val issuer: String? = null, - /** * Get the token type. * @return token type. */ override val type: String? = null, - /** * the authenticator assurance level of the token * @return default. */ - override val authenticatorAssuranceLevel: RadarToken.AuthenticatorAssuranceLevel = RadarToken.AuthenticatorAssuranceLevel.aal1, - + override val authenticatorAssuranceLevel: RadarToken.AuthenticatorAssuranceLevel = RadarToken.AuthenticatorAssuranceLevel.AAL1, /** * Client that the token is associated to. * @return client ID if set or null if unknown. */ override val clientId: String? = null, - - ): RadarToken, Serializable { - constructor(radarToken: RadarToken) : this( - roles = radarToken.roles, - scopes = radarToken.scopes, - sources = radarToken.sources, - grantType = radarToken.grantType, - subject = radarToken.subject, - username = radarToken.username, - issuedAt = radarToken.issuedAt, - expiresAt = radarToken.expiresAt, - audience = radarToken.audience, - token = radarToken.token, - issuer = radarToken.issuer, - type = radarToken.type, - clientId = radarToken.clientId, - ) - - override fun copyWithRoles(roles: Set): DataRadarToken = copy(roles = roles) - - companion object { - fun RadarToken.toDataRadarToken(): DataRadarToken = DataRadarToken(this) +) : RadarToken, + Serializable { + constructor(radarToken: RadarToken) : this( + roles = radarToken.roles, + scopes = radarToken.scopes, + sources = radarToken.sources, + grantType = radarToken.grantType, + subject = radarToken.subject, + username = radarToken.username, + issuedAt = radarToken.issuedAt, + expiresAt = radarToken.expiresAt, + audience = radarToken.audience, + token = radarToken.token, + issuer = radarToken.issuer, + type = radarToken.type, + clientId = radarToken.clientId, + ) + + override fun copyWithRoles(roles: Set): DataRadarToken = copy(roles = roles) + + companion object { + fun RadarToken.toDataRadarToken(): DataRadarToken = DataRadarToken(this) + } } -} diff --git a/radar-auth/src/main/java/org/radarbase/auth/token/RadarToken.kt b/radar-auth/src/main/java/org/radarbase/auth/token/RadarToken.kt index 03910caf4..60f4357e6 100644 --- a/radar-auth/src/main/java/org/radarbase/auth/token/RadarToken.kt +++ b/radar-auth/src/main/java/org/radarbase/auth/token/RadarToken.kt @@ -1,8 +1,8 @@ package org.radarbase.auth.token import org.radarbase.auth.authorization.AuthorityReference -import org.radarbase.auth.token.RadarToken.AuthenticatorAssuranceLevel.aal1 -import org.radarbase.auth.token.RadarToken.AuthenticatorAssuranceLevel.aal2 +import org.radarbase.auth.token.RadarToken.AuthenticatorAssuranceLevel.AAL1 +import org.radarbase.auth.token.RadarToken.AuthenticatorAssuranceLevel.AAL2 import java.time.Instant /** @@ -109,11 +109,11 @@ interface RadarToken { /** * Authenticator assurance level, commonly referred to as MFA. AAL1 means no MFA, AAL2 means MFA * - * @property aal1 Represents the first level of authenticator assurance (e.g. password based). - * @property aal2 Represents the second level of authenticator assurance (i.e. MFA). + * @property AAL1 Represents the first level of authenticator assurance (e.g. password based). + * @property AAL2 Represents the second level of authenticator assurance (i.e. MFA). */ enum class AuthenticatorAssuranceLevel { - aal1, - aal2 + AAL1, + AAL2, } } diff --git a/radar-auth/src/test/java/org/radarbase/auth/authentication/TokenValidatorTest.kt b/radar-auth/src/test/java/org/radarbase/auth/authentication/TokenValidatorTest.kt index 2dca2a112..d7f1dff52 100644 --- a/radar-auth/src/test/java/org/radarbase/auth/authentication/TokenValidatorTest.kt +++ b/radar-auth/src/test/java/org/radarbase/auth/authentication/TokenValidatorTest.kt @@ -29,20 +29,23 @@ internal class TokenValidatorTest { @BeforeEach fun setUp() { wireMockServer.stubFor( - WireMock.get(WireMock.urlEqualTo(TokenTestUtils.PUBLIC_KEY_PATH)) + WireMock + .get(WireMock.urlEqualTo(TokenTestUtils.PUBLIC_KEY_PATH)) .willReturn( - WireMock.aResponse() + WireMock + .aResponse() .withStatus(200) .withHeader("Content-type", TokenTestUtils.APPLICATION_JSON) - .withBody(TokenTestUtils.PUBLIC_KEY_BODY) - ) + .withBody(TokenTestUtils.PUBLIC_KEY_BODY), + ), ) val algorithmParser = JwkAlgorithmParser(listOf(RSAPEMCertificateParser())) - val verifierLoader = JwksTokenVerifierLoader( - "http://localhost:" + WIREMOCK_PORT + TokenTestUtils.PUBLIC_KEY_PATH, - "unit_test", - algorithmParser - ) + val verifierLoader = + JwksTokenVerifierLoader( + "http://localhost:" + WIREMOCK_PORT + TokenTestUtils.PUBLIC_KEY_PATH, + "unit_test", + algorithmParser, + ) validator = TokenValidator(listOf(verifierLoader)) } @@ -59,21 +62,21 @@ internal class TokenValidatorTest { @Test fun testIncorrectAudienceToken() { Assertions.assertThrows( - TokenValidationException::class.java + TokenValidationException::class.java, ) { validator.validateBlocking(TokenTestUtils.INCORRECT_AUDIENCE_TOKEN) } } @Test fun testExpiredToken() { Assertions.assertThrows( - TokenValidationException::class.java + TokenValidationException::class.java, ) { validator.validateBlocking(TokenTestUtils.EXPIRED_TOKEN) } } @Test fun testIncorrectAlgorithmToken() { Assertions.assertThrows( - TokenValidationException::class.java + TokenValidationException::class.java, ) { validator.validateBlocking(TokenTestUtils.INCORRECT_ALGORITHM_TOKEN) } } @@ -83,10 +86,11 @@ internal class TokenValidatorTest { @JvmStatic @BeforeAll fun loadToken() { - wireMockServer = WireMockServer( - WireMockConfiguration() - .port(WIREMOCK_PORT) - ) + wireMockServer = + WireMockServer( + WireMockConfiguration() + .port(WIREMOCK_PORT), + ) wireMockServer.start() } diff --git a/radar-auth/src/test/java/org/radarbase/auth/authorization/RadarAuthorizationTest.kt b/radar-auth/src/test/java/org/radarbase/auth/authorization/RadarAuthorizationTest.kt index a0d101e9b..5e2f38c19 100644 --- a/radar-auth/src/test/java/org/radarbase/auth/authorization/RadarAuthorizationTest.kt +++ b/radar-auth/src/test/java/org/radarbase/auth/authorization/RadarAuthorizationTest.kt @@ -16,191 +16,211 @@ import org.radarbase.auth.util.TokenTestUtils */ internal class RadarAuthorizationTest { private lateinit var oracle: AuthorizationOracle + @BeforeEach fun setUp() { - oracle = MPAuthorizationOracle( - MockEntityRelationService() - ) + oracle = + MPAuthorizationOracle( + MockEntityRelationService(), + ) } @Test - fun testCheckPermissionOnProject() = runBlocking { - val project = "PROJECT1" - // let's get all permissions a project admin has - val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT1") - - permissionMatrix.asSequence() - .filter { (_, roles) -> RoleAuthority.PROJECT_ADMIN in roles } - .map { (p, _) -> p } - .distinct() - .forEach { p -> assertTrue(oracle.hasPermission(token, p, entity)) } - - permissionMatrix.asSequence() - .filter { (_, roles) -> RoleAuthority.PROJECT_ADMIN !in roles } - .map { (p, _) -> p } - .distinct() - .forEach { p -> - assertFalse( - oracle.hasPermission(token, p, entity), - String.format("Token should not have permission %s on project %s", p, project), - ) - } - } + fun testCheckPermissionOnProject() = + runBlocking { + val project = "PROJECT1" + // let's get all permissions a project admin has + val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT1") - @Test - fun testCheckPermissionOnOrganization() = runBlocking { - val token = TokenTestUtils.ORGANIZATION_ADMIN_TOKEN.toRadarToken() - val entity = EntityDetails(organization = "main") - assertFalse( - oracle.hasPermission(token, Permission.ORGANIZATION_CREATE, entity), - "Token should not be able to create organization" - ) - assertTrue(oracle.hasPermission(token, Permission.PROJECT_CREATE, entity)) - assertTrue( - oracle.hasPermission( - token, - Permission.SUBJECT_CREATE, - entity.copy(project = "PROJECT1"), - ), - ) - assertFalse( - oracle.hasPermission( - token, - Permission.PROJECT_CREATE, - EntityDetails(organization = "otherOrg"), - ), - "Token should not be able to create project in other organization", - ) - assertFalse( - oracle.hasPermission( - token, - Permission.SUBJECT_CREATE, - EntityDetails(organization = "otherOrg"), - ), - "Token should not be able to create subject in other organization" - ) - } + permissionMatrix + .asSequence() + .filter { (_, roles) -> RoleAuthority.PROJECT_ADMIN in roles } + .map { (p, _) -> p } + .distinct() + .forEach { p -> assertTrue(oracle.hasPermission(token, p, entity)) } - @Test - fun testCheckPermission() = runBlocking { - val token: RadarToken = TokenTestUtils.SUPER_USER_TOKEN.toRadarToken() - for (p in Permission.values()) { - assertTrue(oracle.hasGlobalPermission(token, p)) + permissionMatrix + .asSequence() + .filter { (_, roles) -> RoleAuthority.PROJECT_ADMIN !in roles } + .map { (p, _) -> p } + .distinct() + .forEach { p -> + assertFalse( + oracle.hasPermission(token, p, entity), + String.format("Token should not have permission %s on project %s", p, project), + ) + } } - } @Test - fun testCheckPermissionOnSelf() = runBlocking { - // this token is participant in PROJECT2 - val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT2", subject = token.subject) - listOf( - Permission.MEASUREMENT_CREATE, - Permission.MEASUREMENT_READ, - Permission.SUBJECT_UPDATE, - Permission.SUBJECT_READ - ).forEach { p -> assertTrue(oracle.hasPermission(token, p, entity)) } - } - - @Test - fun testCheckPermissionOnOtherSubject() = runBlocking { - // is only participant in project2, so should not have any permission on another subject - val entity = EntityDetails(project = "PROJECT2", subject = "other-subject") - // this token is participant in PROJECT2 - val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() - Permission.values().forEach { p -> + fun testCheckPermissionOnOrganization() = + runBlocking { + val token = TokenTestUtils.ORGANIZATION_ADMIN_TOKEN.toRadarToken() + val entity = EntityDetails(organization = "main") + assertFalse( + oracle.hasPermission(token, Permission.ORGANIZATION_CREATE, entity), + "Token should not be able to create organization", + ) + assertTrue(oracle.hasPermission(token, Permission.PROJECT_CREATE, entity)) + assertTrue( + oracle.hasPermission( + token, + Permission.SUBJECT_CREATE, + entity.copy(project = "PROJECT1"), + ), + ) + assertFalse( + oracle.hasPermission( + token, + Permission.PROJECT_CREATE, + EntityDetails(organization = "otherOrg"), + ), + "Token should not be able to create project in other organization", + ) assertFalse( - oracle.hasPermission(token, p, entity), - "Token should not have permission $p on another subject", + oracle.hasPermission( + token, + Permission.SUBJECT_CREATE, + EntityDetails(organization = "otherOrg"), + ), + "Token should not be able to create subject in other organization", ) } - } @Test - fun testCheckPermissionOnSubject() = runBlocking { - // project admin should have all permissions on subject in his project - // this token is participant in PROJECT2 - val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT1", subject = "some-subject") - Permission.values() - .asSequence() - .filter { p -> p.entity === Permission.Entity.SUBJECT } - .forEach { p -> - assertTrue(oracle.hasPermission(token, p, entity)) + fun testCheckPermission() = + runBlocking { + val token: RadarToken = TokenTestUtils.SUPER_USER_TOKEN.toRadarToken() + for (p in Permission.values()) { + assertTrue(oracle.hasGlobalPermission(token, p)) } - } + } @Test - fun testMultipleRolesInProjectToken() = runBlocking { - val token: RadarToken = TokenTestUtils.MULTIPLE_ROLES_IN_PROJECT_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT2", subject = "some-subject") - Permission.values() - .asSequence() - .filter { p -> p.entity === Permission.Entity.SUBJECT } - .forEach { p -> - assertTrue(oracle.hasPermission(token, p, entity)) - } - } + fun testCheckPermissionOnSelf() = + runBlocking { + // this token is participant in PROJECT2 + val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT2", subject = token.subject) + listOf( + Permission.MEASUREMENT_CREATE, + Permission.MEASUREMENT_READ, + Permission.SUBJECT_UPDATE, + Permission.SUBJECT_READ, + ).forEach { p -> assertTrue(oracle.hasPermission(token, p, entity)) } + } @Test - fun testCheckPermissionOnSource() = runBlocking { - // this token is participant in PROJECT2 - val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT2", subject = "some-subject", source = "source-1") - Permission.values() - .forEach { p: Permission -> + fun testCheckPermissionOnOtherSubject() = + runBlocking { + // is only participant in project2, so should not have any permission on another subject + val entity = EntityDetails(project = "PROJECT2", subject = "other-subject") + // this token is participant in PROJECT2 + val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() + Permission.values().forEach { p -> assertFalse( oracle.hasPermission(token, p, entity), "Token should not have permission $p on another subject", ) } - } + } @Test - fun testCheckPermissionOnOwnSource() = runBlocking { - val token: RadarToken = TokenTestUtils.MULTIPLE_ROLES_IN_PROJECT_TOKEN.toRadarToken() - val entity = EntityDetails(project = "PROJECT2", subject = token.subject, source = "source-1") - Permission.values() - .asSequence() - .filter { p -> p.entity === Permission.Entity.MEASUREMENT } - .forEach { p -> - assertTrue(oracle.hasPermission(token, p, entity)) - } - } + fun testCheckPermissionOnSubject() = + runBlocking { + // project admin should have all permissions on subject in his project + // this token is participant in PROJECT2 + val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT1", subject = "some-subject") + Permission + .values() + .asSequence() + .filter { p -> p.entity === Permission.Entity.SUBJECT } + .forEach { p -> + assertTrue(oracle.hasPermission(token, p, entity)) + } + } @Test - fun testScopeOnlyToken() = runBlocking { - val token: RadarToken = TokenTestUtils.SCOPE_TOKEN.toRadarToken() - // test that we can do the things we have a scope for - val entities = listOf( - EntityDetails.global, - EntityDetails(project = "PROJECT1"), - EntityDetails(project = "PROJECT1", subject = ""), - EntityDetails(project = "PROJECT1", subject = "", source = ""), - ) - listOf( - Permission.SUBJECT_READ, - Permission.SUBJECT_CREATE, - Permission.PROJECT_READ, - Permission.MEASUREMENT_CREATE - ).forEach { p -> - entities.forEach { e -> - assertTrue(oracle.hasPermission(token, p, e)) - } + fun testMultipleRolesInProjectToken() = + runBlocking { + val token: RadarToken = TokenTestUtils.MULTIPLE_ROLES_IN_PROJECT_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT2", subject = "some-subject") + Permission + .values() + .asSequence() + .filter { p -> p.entity === Permission.Entity.SUBJECT } + .forEach { p -> + assertTrue(oracle.hasPermission(token, p, entity)) + } } - // test we can do nothing else, for each of the checkPermission methods - Permission.values() - .asSequence() - .filter { p -> p.scope() !in token.scopes } - .forEach { p -> - entities.forEach { e -> + @Test + fun testCheckPermissionOnSource() = + runBlocking { + // this token is participant in PROJECT2 + val token: RadarToken = TokenTestUtils.PROJECT_ADMIN_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT2", subject = "some-subject", source = "source-1") + Permission + .values() + .forEach { p: Permission -> assertFalse( - oracle.hasPermission(token, p, e), - "Permission $p is granted but not in scope.", + oracle.hasPermission(token, p, entity), + "Token should not have permission $p on another subject", ) } + } + + @Test + fun testCheckPermissionOnOwnSource() = + runBlocking { + val token: RadarToken = TokenTestUtils.MULTIPLE_ROLES_IN_PROJECT_TOKEN.toRadarToken() + val entity = EntityDetails(project = "PROJECT2", subject = token.subject, source = "source-1") + Permission + .values() + .asSequence() + .filter { p -> p.entity === Permission.Entity.MEASUREMENT } + .forEach { p -> + assertTrue(oracle.hasPermission(token, p, entity)) + } + } + + @Test + fun testScopeOnlyToken() = + runBlocking { + val token: RadarToken = TokenTestUtils.SCOPE_TOKEN.toRadarToken() + // test that we can do the things we have a scope for + val entities = + listOf( + EntityDetails.global, + EntityDetails(project = "PROJECT1"), + EntityDetails(project = "PROJECT1", subject = ""), + EntityDetails(project = "PROJECT1", subject = "", source = ""), + ) + listOf( + Permission.SUBJECT_READ, + Permission.SUBJECT_CREATE, + Permission.PROJECT_READ, + Permission.MEASUREMENT_CREATE, + ).forEach { p -> + entities.forEach { e -> + assertTrue(oracle.hasPermission(token, p, e)) + } } - } + + // test we can do nothing else, for each of the checkPermission methods + Permission + .values() + .asSequence() + .filter { p -> p.scope() !in token.scopes } + .forEach { p -> + entities.forEach { e -> + assertFalse( + oracle.hasPermission(token, p, e), + "Permission $p is granted but not in scope.", + ) + } + } + } } diff --git a/radar-auth/src/test/java/org/radarbase/auth/security/jwk/JsonWebKeyTest.kt b/radar-auth/src/test/java/org/radarbase/auth/security/jwk/JsonWebKeyTest.kt index 3a943a271..24e51c2b2 100644 --- a/radar-auth/src/test/java/org/radarbase/auth/security/jwk/JsonWebKeyTest.kt +++ b/radar-auth/src/test/java/org/radarbase/auth/security/jwk/JsonWebKeyTest.kt @@ -1,8 +1,8 @@ package org.radarbase.auth.security.jwk -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Test import org.radarbase.auth.jwks.ECDSAJsonWebKey import org.radarbase.auth.jwks.JsonWebKey @@ -14,11 +14,12 @@ class JsonWebKeyTest { assertInstanceOf(ECDSAJsonWebKey::class.java, result) assertEquals( ECDSAJsonWebKey( - kty = "EC", - crv = "EC-512", - x = "abcd", - y = "cdef", - ), result) + kty = "EC", + crv = "EC-512", + x = "abcd", + y = "cdef", + ), + result, + ) } - } diff --git a/radar-auth/src/test/java/org/radarbase/auth/token/AbstractRadarTokenTest.kt b/radar-auth/src/test/java/org/radarbase/auth/token/AbstractRadarTokenTest.kt index ecef256cc..aa46e8164 100644 --- a/radar-auth/src/test/java/org/radarbase/auth/token/AbstractRadarTokenTest.kt +++ b/radar-auth/src/test/java/org/radarbase/auth/token/AbstractRadarTokenTest.kt @@ -5,7 +5,13 @@ import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.radarbase.auth.authorization.* +import org.radarbase.auth.authorization.AuthorityReference +import org.radarbase.auth.authorization.AuthorizationOracle +import org.radarbase.auth.authorization.EntityDetails +import org.radarbase.auth.authorization.EntityRelationService +import org.radarbase.auth.authorization.MPAuthorizationOracle +import org.radarbase.auth.authorization.Permission +import org.radarbase.auth.authorization.RoleAuthority import org.radarbase.auth.token.RadarToken.Companion.CLIENT_CREDENTIALS import java.time.Instant @@ -13,20 +19,22 @@ class AbstractRadarTokenTest { private lateinit var oracle: AuthorizationOracle private lateinit var token: DataRadarToken - private fun createMockToken() = DataRadarToken( - roles = emptySet(), - scopes = emptySet(), - grantType = "refresh_token", - expiresAt = Instant.MAX, - ) - - class MockEntityRelationService @JvmOverloads constructor( - private val projectToOrganization: Map = mapOf() - ) : EntityRelationService { - override suspend fun findOrganizationOfProject(project: String): String { - return projectToOrganization[project] ?: "main" + private fun createMockToken() = + DataRadarToken( + roles = emptySet(), + scopes = emptySet(), + grantType = "refresh_token", + expiresAt = Instant.MAX, + ) + + class MockEntityRelationService + @JvmOverloads + constructor( + private val projectToOrganization: Map = mapOf(), + ) : EntityRelationService { + override suspend fun findOrganizationOfProject(project: String): String = + projectToOrganization[project] ?: "main" } - } @BeforeEach fun setUp() { @@ -41,188 +49,211 @@ class AbstractRadarTokenTest { @Test fun notHasPermissionWithoutAuthority() { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - ) + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + ) assertFalse(oracle.hasScope(token, Permission.MEASUREMENT_CREATE)) } @Test fun hasPermissionAsAdmin() { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)) - ) + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)), + ) assertTrue(oracle.hasScope(token, Permission.MEASUREMENT_CREATE)) } @Test fun hasPermissionAsUser() { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "some")), - ) + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "some")), + ) assertTrue(oracle.hasScope(token, Permission.MEASUREMENT_CREATE)) } @Test fun hasPermissionAsClient() { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - grantType = CLIENT_CREDENTIALS - ) + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + grantType = CLIENT_CREDENTIALS, + ) assertTrue(oracle.hasScope(token, Permission.MEASUREMENT_CREATE)) } @Test - fun notHasPermissionOnProjectWithoutScope() = runBlocking { - assertFalse( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project") + fun notHasPermissionOnProjectWithoutScope() = + runBlocking { + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project"), + ), ) - ) - } + } @Test - fun notHasPermissioOnProjectnWithoutAuthority() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()) - ) - assertFalse( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project") + fun notHasPermissioOnProjectnWithoutAuthority() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + ) + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project"), + ), ) - ) - } + } @Test - fun hasPermissionOnProjectAsAdmin() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)), - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project") + fun hasPermissionOnProjectAsAdmin() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)), + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project"), + ), ) - ) - } + } @Test - fun hasPermissionOnProjectAsUser() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "project")), - subject = "subject", - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject") + fun hasPermissionOnProjectAsUser() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "project")), + subject = "subject", + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), ) - ) - assertFalse( - oracle.hasPermission( - token, Permission.MEASUREMENT_CREATE, EntityDetails(project = "project"), + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project"), + ), ) - ) - } + } @Test - fun hasPermissionOnProjectAsClient() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - grantType = CLIENT_CREDENTIALS, - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project") + fun hasPermissionOnProjectAsClient() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + grantType = CLIENT_CREDENTIALS, + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project"), + ), ) - ) - } + } @Test - fun notHasPermissionOnSubjectWithoutScope() = runBlocking { - assertFalse( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject") + fun notHasPermissionOnSubjectWithoutScope() = + runBlocking { + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), ) - ) - } + } @Test - fun notHasPermissioOnSubjectnWithoutAuthority() = runBlocking { - token = token.copy(scopes = setOf(Permission.MEASUREMENT_CREATE.scope())) - assertFalse( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject") - ), - ) - } + fun notHasPermissioOnSubjectnWithoutAuthority() = + runBlocking { + token = token.copy(scopes = setOf(Permission.MEASUREMENT_CREATE.scope())) + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), + ) + } @Test - fun hasPermissionOnSubjectAsAdmin() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)), - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject") + fun hasPermissionOnSubjectAsAdmin() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.SYS_ADMIN)), + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), ) - ) - } + } @Test - fun hasPermissionOnSubjectAsUser() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "project")), - subject = "subject", - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject") + fun hasPermissionOnSubjectAsUser() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + roles = setOf(AuthorityReference(RoleAuthority.PARTICIPANT, "project")), + subject = "subject", + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), ) - ) - assertFalse( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "otherSubject") + assertFalse( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "otherSubject"), + ), ) - ) - } + } @Test - fun hasPermissionOnSubjectAsClient() = runBlocking { - token = token.copy( - scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), - grantType = CLIENT_CREDENTIALS - ) - assertTrue( - oracle.hasPermission( - token, - Permission.MEASUREMENT_CREATE, - EntityDetails(project = "project", subject = "subject"), + fun hasPermissionOnSubjectAsClient() = + runBlocking { + token = + token.copy( + scopes = setOf(Permission.MEASUREMENT_CREATE.scope()), + grantType = CLIENT_CREDENTIALS, + ) + assertTrue( + oracle.hasPermission( + token, + Permission.MEASUREMENT_CREATE, + EntityDetails(project = "project", subject = "subject"), + ), ) - ) - } + } } diff --git a/radar-auth/src/test/java/org/radarbase/auth/util/TokenTestUtils.kt b/radar-auth/src/test/java/org/radarbase/auth/util/TokenTestUtils.kt index e509eab39..056dbdf39 100644 --- a/radar-auth/src/test/java/org/radarbase/auth/util/TokenTestUtils.kt +++ b/radar-auth/src/test/java/org/radarbase/auth/util/TokenTestUtils.kt @@ -23,22 +23,43 @@ object TokenTestUtils { const val PUBLIC_KEY_PATH = "/oauth/token_key" val PUBLIC_KEY_STRING: String - @JvmField val PUBLIC_KEY_BODY: String - @JvmField val VALID_RSA_TOKEN: String - @JvmField val INCORRECT_AUDIENCE_TOKEN: String - @JvmField val EXPIRED_TOKEN: String - @JvmField val INCORRECT_ALGORITHM_TOKEN: String - @JvmField val SCOPE_TOKEN: DecodedJWT - @JvmField val ORGANIZATION_ADMIN_TOKEN: DecodedJWT - @JvmField val PROJECT_ADMIN_TOKEN: DecodedJWT - @JvmField val SUPER_USER_TOKEN: DecodedJWT - @JvmField val MULTIPLE_ROLES_IN_PROJECT_TOKEN: DecodedJWT + + @JvmField + val PUBLIC_KEY_BODY: String + + @JvmField + val VALID_RSA_TOKEN: String + + @JvmField + val INCORRECT_AUDIENCE_TOKEN: String + + @JvmField + val EXPIRED_TOKEN: String + + @JvmField + val INCORRECT_ALGORITHM_TOKEN: String + + @JvmField + val SCOPE_TOKEN: DecodedJWT + + @JvmField + val ORGANIZATION_ADMIN_TOKEN: DecodedJWT + + @JvmField + val PROJECT_ADMIN_TOKEN: DecodedJWT + + @JvmField + val SUPER_USER_TOKEN: DecodedJWT + + @JvmField + val MULTIPLE_ROLES_IN_PROJECT_TOKEN: DecodedJWT val AUTHORITIES = arrayOf("ROLE_SYS_ADMIN", "ROLE_USER") val ALL_SCOPES = Permission.scopes() - val ROLES = arrayOf( - "PROJECT1:ROLE_PROJECT_ADMIN", - "PROJECT2:ROLE_PARTICIPANT" - ) + val ROLES = + arrayOf( + "PROJECT1:ROLE_PROJECT_ADMIN", + "PROJECT2:ROLE_PARTICIPANT", + ) val SOURCES = arrayOf() const val CLIENT = "unit_test" const val USER = "admin" @@ -48,21 +69,24 @@ object TokenTestUtils { const val WIREMOCK_PORT = 8089 init { - val provider: RSAKeyProvider = try { - loadKeys() - } catch (e: GeneralSecurityException) { - throw IllegalStateException("Failed to load keys for test", e) - } catch (e: IOException) { - throw IllegalStateException("Failed to load keys for test", e) - } + val provider: RSAKeyProvider = + try { + loadKeys() + } catch (e: GeneralSecurityException) { + throw IllegalStateException("Failed to load keys for test", e) + } catch (e: IOException) { + throw IllegalStateException("Failed to load keys for test", e) + } val publicKey = provider.getPublicKeyById("selfsigned") - PUBLIC_KEY_STRING = String( - Base64.getEncoder().encode( - provider.getPublicKeyById("selfsigned").encoded + PUBLIC_KEY_STRING = + String( + Base64.getEncoder().encode( + provider.getPublicKeyById("selfsigned").encoded, + ), ) - ) val algorithm = Algorithm.RSA256(publicKey, provider.privateKey) - PUBLIC_KEY_BODY = """ + PUBLIC_KEY_BODY = + """ { "keys" : [ { "alg" : "${algorithm.name}", @@ -90,17 +114,19 @@ object TokenTestUtils { @Throws(GeneralSecurityException::class, IOException::class) private fun loadKeys(): RSAKeyProvider { val ks = KeyStore.getInstance("PKCS12") - Thread.currentThread().contextClassLoader + Thread + .currentThread() + .contextClassLoader .getResourceAsStream("keystore.p12") .use { keyStream -> ks.load(keyStream, "radarbase".toCharArray()) } - val privateKey = ks.getKey( - "selfsigned", - "radarbase".toCharArray() - ) as RSAPrivateKey + val privateKey = + ks.getKey( + "selfsigned", + "radarbase".toCharArray(), + ) as RSAPrivateKey val cert = ks.getCertificate("selfsigned") return object : RSAKeyProvider { - override fun getPublicKeyById(keyId: String): RSAPublicKey = - cert.publicKey as RSAPublicKey + override fun getPublicKeyById(keyId: String): RSAPublicKey = cert.publicKey as RSAPublicKey override fun getPrivateKey(): RSAPrivateKey = privateKey @@ -108,8 +134,13 @@ object TokenTestUtils { } } - private fun initExpiredToken(algorithm: Algorithm, past: Instant, iatpast: Instant): String { - return JWT.create() + private fun initExpiredToken( + algorithm: Algorithm, + past: Instant, + iatpast: Instant, + ): String = + JWT + .create() .withIssuer(ISS) .withIssuedAt(iatpast) .withExpiresAt(past) @@ -124,12 +155,15 @@ object TokenTestUtils { .withClaim("jti", JTI) .withClaim("grant_type", "password") .sign(algorithm) - } - private fun initIncorrectAlgorithmToken(exp: Instant, iat: Instant): String { + private fun initIncorrectAlgorithmToken( + exp: Instant, + iat: Instant, + ): String { val psk = Algorithm.HMAC256("super-secret-stuff") // token signed with a pre-shared key - return JWT.create() + return JWT + .create() .withIssuer(ISS) .withIssuedAt(iat) .withExpiresAt(exp) @@ -147,10 +181,12 @@ object TokenTestUtils { } private fun initIncorrectAudienceToken( - algorithm: Algorithm, exp: Instant, - iat: Instant - ): String { - return JWT.create() + algorithm: Algorithm, + exp: Instant, + iat: Instant, + ): String = + JWT + .create() .withIssuer(ISS) .withIssuedAt(iat) .withExpiresAt(exp) @@ -165,87 +201,99 @@ object TokenTestUtils { .withClaim("jti", JTI) .withClaim("grant_type", "password") .sign(algorithm) - } private fun initMultipleRolesToken( - algorithm: Algorithm, exp: Instant, - iat: Instant + algorithm: Algorithm, + exp: Instant, + iat: Instant, ): DecodedJWT { - val multipleRolesInProjectToken = JWT.create() - .withIssuer(ISS) - .withIssuedAt(iat) - .withExpiresAt(exp) - .withAudience(CLIENT) - .withSubject(USER) - .withArrayClaim("scope", ALL_SCOPES) - .withArrayClaim("authorities", arrayOf("ROLE_PROJECT_ADMIN")) - .withArrayClaim( - "roles", arrayOf( - "PROJECT2:ROLE_PROJECT_ADMIN", - "PROJECT2:ROLE_PARTICIPANT" - ) - ) - .withArrayClaim("sources", arrayOf("source-1")) - .withClaim("client_id", CLIENT) - .withClaim("user_name", USER) - .withClaim("jti", JTI) - .withClaim("grant_type", "password") - .sign(algorithm) + val multipleRolesInProjectToken = + JWT + .create() + .withIssuer(ISS) + .withIssuedAt(iat) + .withExpiresAt(exp) + .withAudience(CLIENT) + .withSubject(USER) + .withArrayClaim("scope", ALL_SCOPES) + .withArrayClaim("authorities", arrayOf("ROLE_PROJECT_ADMIN")) + .withArrayClaim( + "roles", + arrayOf( + "PROJECT2:ROLE_PROJECT_ADMIN", + "PROJECT2:ROLE_PARTICIPANT", + ), + ).withArrayClaim("sources", arrayOf("source-1")) + .withClaim("client_id", CLIENT) + .withClaim("user_name", USER) + .withClaim("jti", JTI) + .withClaim("grant_type", "password") + .sign(algorithm) return JWT.decode(multipleRolesInProjectToken) } private fun initOrgananizationAdminToken( - algorithm: Algorithm, exp: Instant, - iat: Instant + algorithm: Algorithm, + exp: Instant, + iat: Instant, ): DecodedJWT { - val projectAdminToken = JWT.create() - .withIssuer(ISS) - .withIssuedAt(iat) - .withExpiresAt(exp) - .withAudience(CLIENT) - .withSubject(USER) - .withArrayClaim("scope", ALL_SCOPES) - .withArrayClaim("authorities", arrayOf("ROLE_ORGANIZATION_ADMIN")) - .withArrayClaim("roles", arrayOf("main:ROLE_ORGANIZATION_ADMIN")) - .withArrayClaim("sources", arrayOf()) - .withClaim("client_id", CLIENT) - .withClaim("user_name", USER) - .withClaim("jti", JTI) - .withClaim("grant_type", "password") - .sign(algorithm) + val projectAdminToken = + JWT + .create() + .withIssuer(ISS) + .withIssuedAt(iat) + .withExpiresAt(exp) + .withAudience(CLIENT) + .withSubject(USER) + .withArrayClaim("scope", ALL_SCOPES) + .withArrayClaim("authorities", arrayOf("ROLE_ORGANIZATION_ADMIN")) + .withArrayClaim("roles", arrayOf("main:ROLE_ORGANIZATION_ADMIN")) + .withArrayClaim("sources", arrayOf()) + .withClaim("client_id", CLIENT) + .withClaim("user_name", USER) + .withClaim("jti", JTI) + .withClaim("grant_type", "password") + .sign(algorithm) return JWT.decode(projectAdminToken) } private fun initProjectAdminToken( algorithm: Algorithm, exp: Instant, - iat: Instant + iat: Instant, ): DecodedJWT { - val projectAdminToken = JWT.create() - .withIssuer(ISS) - .withIssuedAt(iat) - .withExpiresAt(exp) - .withAudience(CLIENT) - .withSubject(USER) - .withArrayClaim("scope", ALL_SCOPES) - .withArrayClaim( - "authorities", arrayOf( - "ROLE_PROJECT_ADMIN", - "ROLE_PARTICIPANT" - ) - ) - .withArrayClaim("roles", ROLES) - .withArrayClaim("sources", arrayOf()) - .withClaim("client_id", CLIENT) - .withClaim("user_name", USER) - .withClaim("jti", JTI) - .withClaim("grant_type", "password") - .sign(algorithm) + val projectAdminToken = + JWT + .create() + .withIssuer(ISS) + .withIssuedAt(iat) + .withExpiresAt(exp) + .withAudience(CLIENT) + .withSubject(USER) + .withArrayClaim("scope", ALL_SCOPES) + .withArrayClaim( + "authorities", + arrayOf( + "ROLE_PROJECT_ADMIN", + "ROLE_PARTICIPANT", + ), + ).withArrayClaim("roles", ROLES) + .withArrayClaim("sources", arrayOf()) + .withClaim("client_id", CLIENT) + .withClaim("user_name", USER) + .withClaim("jti", JTI) + .withClaim("grant_type", "password") + .sign(algorithm) return JWT.decode(projectAdminToken) } - private fun initValidToken(algorithm: Algorithm, exp: Instant, iat: Instant): String { - return JWT.create() + private fun initValidToken( + algorithm: Algorithm, + exp: Instant, + iat: Instant, + ): String = + JWT + .create() .withIssuer(ISS) .withIssuedAt(iat) .withExpiresAt(exp) @@ -260,25 +308,32 @@ object TokenTestUtils { .withClaim("jti", JTI) .withClaim("grant_type", "password") .sign(algorithm) - } - private fun initTokenWithScopes(algorithm: Algorithm, exp: Instant, iat: Instant): DecodedJWT { - val token = JWT.create() - .withIssuer(ISS) - .withIssuedAt(iat) - .withExpiresAt(exp) - .withAudience(CLIENT) - .withSubject("i'm a trusted oauth client") - .withArrayClaim( - "scope", arrayOf( - "PROJECT.READ", "SUBJECT.CREATE", - "SUBJECT.READ", "MEASUREMENT.CREATE" - ) - ) - .withClaim("client_id", "i'm a trusted oauth client") - .withClaim("jti", JTI) - .withClaim("grant_type", "client_credentials") - .sign(algorithm) + private fun initTokenWithScopes( + algorithm: Algorithm, + exp: Instant, + iat: Instant, + ): DecodedJWT { + val token = + JWT + .create() + .withIssuer(ISS) + .withIssuedAt(iat) + .withExpiresAt(exp) + .withAudience(CLIENT) + .withSubject("i'm a trusted oauth client") + .withArrayClaim( + "scope", + arrayOf( + "PROJECT.READ", + "SUBJECT.CREATE", + "SUBJECT.READ", + "MEASUREMENT.CREATE", + ), + ).withClaim("client_id", "i'm a trusted oauth client") + .withClaim("jti", JTI) + .withClaim("grant_type", "client_credentials") + .sign(algorithm) return JWT.decode(token) } } diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index b22204b40..000000000 --- a/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'management-portal' -include ':radar-auth' -include ':managementportal-client' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..b2ba4ce1a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,14 @@ +rootProject.name = "management-portal" +include(":radar-auth") +include(":managementportal-client") + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + mavenLocal() + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} diff --git a/src/gatling/resources/logback.xml b/src/gatling/resources/logback.xml index 7b037e681..3ee1a1d32 100644 --- a/src/gatling/resources/logback.xml +++ b/src/gatling/resources/logback.xml @@ -16,7 +16,7 @@ - + diff --git a/src/main/docker/app.yml b/src/main/docker/app.yml index 9aa4bd223..bad6243df 100644 --- a/src/main/docker/app.yml +++ b/src/main/docker/app.yml @@ -1,111 +1,111 @@ version: '3.7' services: - ## MP + ## MP - managementportal-app: - image: managementportal -# image: radarbase/management-portal - environment: - - SPRING_PROFILES_ACTIVE=prod,api-docs - - SPRING_DATASOURCE_URL=jdbc:postgresql://managementportal-postgresql:5432/managementportal - - SPRING_DATASOURCE_USERNAME=radarbase - - SPRING_DATASOURCE_PASSWORD=radarbase - - SPRING_LIQUIBASE_CONTEXTS=dev #includes testing_data, remove for production builds - - MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET=secret - - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERURL=http://kratos - - MANAGEMENTPORTAL_OAUTH_CLIENTS_FILE=/mp-includes/config/oauth_client_details.csv - - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application - - JAVA_OPTS=-Xmx512m # maximum heap size for the JVM running ManagementPortal, increase this as necessary - ports: - - "8080:8080" - networks: - - all-net - - mp-net - volumes: - - ./etc:/mp-includes + managementportal-app: + image: managementportal + # image: radarbase/management-portal + environment: + - SPRING_PROFILES_ACTIVE=prod,api-docs + - SPRING_DATASOURCE_URL=jdbc:postgresql://managementportal-postgresql:5432/managementportal + - SPRING_DATASOURCE_USERNAME=radarbase + - SPRING_DATASOURCE_PASSWORD=radarbase + - SPRING_LIQUIBASE_CONTEXTS=dev #includes testing_data, remove for production builds + - MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET=secret + - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERURL=http://kratos + - MANAGEMENTPORTAL_OAUTH_CLIENTS_FILE=/mp-includes/config/oauth_client_details.csv + - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application + - JAVA_OPTS=-Xmx512m # maximum heap size for the JVM running ManagementPortal, increase this as necessary + ports: + - "8080:8080" + networks: + - all-net + - mp-net + volumes: + - ./etc:/mp-includes - managementportal-postgresql: - extends: - file: postgresql.yml - service: managementportal-postgresql - networks: - - mp-net + managementportal-postgresql: + extends: + file: postgresql.yml + service: managementportal-postgresql + networks: + - mp-net - ## ORY + ## ORY - # Kratos - kratos-selfservice-ui-node: - image: - oryd/kratos-selfservice-ui-node - environment: - - LOG_LEAK_SENSITIVE_VALUES=true - - KRATOS_PUBLIC_URL=http://kratos:4433 - - KRATOS_ADMIN_URL=http://kratos:4434 - - SECURITY_MODE=standalone - - KRATOS_BROWSER_URL=http://127.0.0.1:4433 - - COOKIE_SECRET=unsafe_cookie_secret - - CSRF_COOKIE_NAME=radar - - CSRF_COOKIE_SECRET=unsafe_csrf_cookie_secret - ports: - - "3000:3000" - networks: - - all-net - volumes: - - /tmp/ui-node/logs:/root/.npm/_logs + # Kratos + kratos-selfservice-ui-node: + image: + oryd/kratos-selfservice-ui-node + environment: + - LOG_LEAK_SENSITIVE_VALUES=true + - KRATOS_PUBLIC_URL=http://kratos:4433 + - KRATOS_ADMIN_URL=http://kratos:4434 + - SECURITY_MODE=standalone + - KRATOS_BROWSER_URL=http://127.0.0.1:4433 + - COOKIE_SECRET=unsafe_cookie_secret + - CSRF_COOKIE_NAME=radar + - CSRF_COOKIE_SECRET=unsafe_csrf_cookie_secret + ports: + - "3000:3000" + networks: + - all-net + volumes: + - /tmp/ui-node/logs:/root/.npm/_logs - kratos: - depends_on: - - kratos-migrate - image: oryd/kratos:v1.0.0 - ports: - - "4433:4433" # public - - "4434:4434" # admin, should be closed in production - restart: unless-stopped - environment: - - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier - volumes: - - type: bind - source: ./etc/config/kratos - target: /etc/config/kratos - networks: - - all-net - - kratos-net + kratos: + depends_on: + - kratos-migrate + image: oryd/kratos:v1.0.0 + ports: + - "4433:4433" # public + - "4434:4434" # admin, should be closed in production + restart: unless-stopped + environment: + - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier + volumes: + - type: bind + source: ./etc/config/kratos + target: /etc/config/kratos + networks: + - all-net + - kratos-net - kratos-migrate: - image: - oryd/kratos:v1.0.0 - environment: - - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - volumes: - - type: bind - source: ./etc/config/kratos - target: /etc/config/kratos - command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes - restart: on-failure - networks: - - kratos-net + kratos-migrate: + image: + oryd/kratos:v1.0.0 + environment: + - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + volumes: + - type: bind + source: ./etc/config/kratos + target: /etc/config/kratos + command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes + restart: on-failure + networks: + - kratos-net - postgresd-kratos: - image: postgres:11.8 - environment: - - POSTGRES_USER=kratos - - POSTGRES_PASSWORD=secret - - POSTGRES_DB=kratos - networks: - - kratos-net + postgresd-kratos: + image: postgres:11.8 + environment: + - POSTGRES_USER=kratos + - POSTGRES_PASSWORD=secret + - POSTGRES_DB=kratos + networks: + - kratos-net - mailslurper: - image: oryd/mailslurper:latest-smtps - ports: - - "4436:4436" - - "4437:4437" - networks: - - kratos-net + mailslurper: + image: oryd/mailslurper:latest-smtps + ports: + - "4436:4436" + - "4437:4437" + networks: + - kratos-net networks: - all-net: - mp-net: - kratos-net: + all-net: + mp-net: + kratos-net: diff --git a/src/main/docker/etc/config/kratos/identities/identity.schema.user.json b/src/main/docker/etc/config/kratos/identities/identity.schema.user.json index 060b3fa3c..70f9fb6fc 100644 --- a/src/main/docker/etc/config/kratos/identities/identity.schema.user.json +++ b/src/main/docker/etc/config/kratos/identities/identity.schema.user.json @@ -1,37 +1,39 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "user", - "title": "user", - "type": "object", - "properties": { - "traits": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email", - "title": "E-Mail", - "minLength": 5, - "ory.sh/kratos": { - "credentials": { - "password": { - "identifier": true - }, - "totp": { - "account_name": true - } + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "user", + "title": "user", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "E-Mail", + "minLength": 5, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + }, + "totp": { + "account_name": true + } + }, + "verification": { + "via": "email" + }, + "recovery": { + "via": "email" + } + } + } }, - "verification": { - "via": "email" - }, - "recovery": { - "via": "email" - } - } + "required": [ + "email" + ] } - }, - "required": ["email"] - } - }, - "additionalProperties": false + }, + "additionalProperties": false } diff --git a/src/main/docker/managementportal.yml b/src/main/docker/managementportal.yml index 5487f5cb7..c939a36e7 100644 --- a/src/main/docker/managementportal.yml +++ b/src/main/docker/managementportal.yml @@ -1,22 +1,22 @@ version: '2' services: - managementportal-app: - build: - context: ../../../ - dockerfile: ./Dockerfile - environment: - - SPRING_PROFILES_ACTIVE=prod,api-docs - - SPRING_DATASOURCE_URL=jdbc:postgresql://managementportal-postgresql:5432/managementportal - - SPRING_DATASOURCE_USERNAME=radarbase - - SPRING_DATASOURCE_PASSWORD=radarbase - - MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET=secret - - MANAGEMENTPORTAL_IDENTITYSERVER_ADMINEMAIL=admin-email-here@radar-base.net - - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERURL=http://kratos:4433 - - MANAGEMENTPORTAL_IDENTITYSERVER_LOGINURL=http://localhost:3000 - - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERADMINURL=http://kratos:4434 - - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application - - JAVA_OPTS=-Xmx512m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 #enables remote debugging - ports: - - "8080:8080" - volumes: - - ./etc:/mp-includes + managementportal-app: + build: + context: ../../../ + dockerfile: ./Dockerfile + environment: + - SPRING_PROFILES_ACTIVE=prod,api-docs + - SPRING_DATASOURCE_URL=jdbc:postgresql://managementportal-postgresql:5432/managementportal + - SPRING_DATASOURCE_USERNAME=radarbase + - SPRING_DATASOURCE_PASSWORD=radarbase + - MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET=secret + - MANAGEMENTPORTAL_IDENTITYSERVER_ADMINEMAIL=admin-email-here@radar-base.net + - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERURL=http://kratos:4433 + - MANAGEMENTPORTAL_IDENTITYSERVER_LOGINURL=http://localhost:3000 + - MANAGEMENTPORTAL_IDENTITYSERVER_SERVERADMINURL=http://kratos:4434 + - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application + - JAVA_OPTS=-Xmx512m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 #enables remote debugging + ports: + - "8080:8080" + volumes: + - ./etc:/mp-includes diff --git a/src/main/docker/mysql.yml b/src/main/docker/mysql.yml index a24555e2c..87581ffcb 100644 --- a/src/main/docker/mysql.yml +++ b/src/main/docker/mysql.yml @@ -1,14 +1,14 @@ version: '3.8' services: - managementportal-mysql: - image: mysql:5.7.13 - # volumes: - # - ~/volumes/jhipster/ManagementPortal/mysql/:/var/lib/mysql/ - environment: - - MYSQL_USER=root - - MYSQL_ALLOW_EMPTY_PASSWORD=yes - - MYSQL_DATABASE=managementportal - ports: - - "3306:3306" - command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8 + managementportal-mysql: + image: mysql:5.7.13 + # volumes: + # - ~/volumes/jhipster/ManagementPortal/mysql/:/var/lib/mysql/ + environment: + - MYSQL_USER=root + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=managementportal + ports: + - "3306:3306" + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8 diff --git a/src/main/docker/ory_stack.yml b/src/main/docker/ory_stack.yml index b95835b53..021745226 100644 --- a/src/main/docker/ory_stack.yml +++ b/src/main/docker/ory_stack.yml @@ -1,60 +1,60 @@ version: '3.8' services: - kratos-selfservice-ui-node: - image: - oryd/kratos-selfservice-ui-node - environment: - - LOG_LEAK_SENSITIVE_VALUES=true - - KRATOS_PUBLIC_URL=http://kratos:4433 - - KRATOS_ADMIN_URL=http://kratos:4434 - - SECURITY_MODE=standalone - - KRATOS_BROWSER_URL=http://127.0.0.1:4433 - - COOKIE_SECRET=unsafe_cookie_secret - - CSRF_COOKIE_NAME=radar - - CSRF_COOKIE_SECRET=unsafe_csrf_cookie_secret - ports: - - "3000:3000" - volumes: - - /tmp/ui-node/logs:/root/.npm/_logs + kratos-selfservice-ui-node: + image: + oryd/kratos-selfservice-ui-node + environment: + - LOG_LEAK_SENSITIVE_VALUES=true + - KRATOS_PUBLIC_URL=http://kratos:4433 + - KRATOS_ADMIN_URL=http://kratos:4434 + - SECURITY_MODE=standalone + - KRATOS_BROWSER_URL=http://127.0.0.1:4433 + - COOKIE_SECRET=unsafe_cookie_secret + - CSRF_COOKIE_NAME=radar + - CSRF_COOKIE_SECRET=unsafe_csrf_cookie_secret + ports: + - "3000:3000" + volumes: + - /tmp/ui-node/logs:/root/.npm/_logs - kratos: - depends_on: - - kratos-migrate - image: oryd/kratos:v1.0.0 - ports: - - "4433:4433" # public - - "4434:4434" # admin, should be closed in production - restart: unless-stopped - environment: - - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier - volumes: - - type: bind - source: ./etc/config/kratos - target: /etc/config/kratos + kratos: + depends_on: + - kratos-migrate + image: oryd/kratos:v1.0.0 + ports: + - "4433:4433" # public + - "4434:4434" # admin, should be closed in production + restart: unless-stopped + environment: + - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier + volumes: + - type: bind + source: ./etc/config/kratos + target: /etc/config/kratos - kratos-migrate: - image: - oryd/kratos:v1.0.0 - environment: - - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - volumes: - - type: bind - source: ./etc/config/kratos - target: /etc/config/kratos - command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes - restart: on-failure + kratos-migrate: + image: + oryd/kratos:v1.0.0 + environment: + - DSN=postgres://kratos:secret@postgresd-kratos/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + volumes: + - type: bind + source: ./etc/config/kratos + target: /etc/config/kratos + command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes + restart: on-failure - postgresd-kratos: - image: postgres:11.8 - environment: - - POSTGRES_USER=kratos - - POSTGRES_PASSWORD=secret - - POSTGRES_DB=kratos + postgresd-kratos: + image: postgres:11.8 + environment: + - POSTGRES_USER=kratos + - POSTGRES_PASSWORD=secret + - POSTGRES_DB=kratos - mailslurper: - image: oryd/mailslurper:latest-smtps - ports: - - "4436:4436" - - "4437:4437" + mailslurper: + image: oryd/mailslurper:latest-smtps + ports: + - "4436:4436" + - "4437:4437" diff --git a/src/main/docker/postgres.yml b/src/main/docker/postgres.yml index 58cd56e07..101dc2dc0 100644 --- a/src/main/docker/postgres.yml +++ b/src/main/docker/postgres.yml @@ -1,10 +1,10 @@ version: '2' services: - managementportal-postgresql: - image: postgres - environment: - - POSTGRES_USER=radarbase - - POSTGRES_PASSWORD=radarbase - - POSTGRES_DB=managementportal - ports: - - "5432:5432" + managementportal-postgresql: + image: postgres + environment: + - POSTGRES_USER=radarbase + - POSTGRES_PASSWORD=radarbase + - POSTGRES_DB=managementportal + ports: + - "5432:5432" diff --git a/src/main/docker/sonar.yml b/src/main/docker/sonar.yml index 6e040d114..ae58be92a 100644 --- a/src/main/docker/sonar.yml +++ b/src/main/docker/sonar.yml @@ -1,8 +1,8 @@ version: '3.8' services: - managementportal-sonar: - image: sonarqube:6.2-alpine - ports: - - "9000:9000" - - "9092:9092" + managementportal-sonar: + image: sonarqube:6.2-alpine + ports: + - "9000:9000" + - "9092:9092" diff --git a/src/main/docker/test/ory_helm/kratos-default.yaml b/src/main/docker/test/ory_helm/kratos-default.yaml index b3cf40389..1491b5c05 100644 --- a/src/main/docker/test/ory_helm/kratos-default.yaml +++ b/src/main/docker/test/ory_helm/kratos-default.yaml @@ -5,695 +5,695 @@ replicaCount: 1 # -- Deployment update strategy strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: "25%" - maxUnavailable: "25%" + type: RollingUpdate + rollingUpdate: + maxSurge: "25%" + maxUnavailable: "25%" image: - # -- ORY KRATOS image - repository: oryd/kratos - # -- ORY KRATOS VERSION - # Alternative format: image: oryd/kratos:v0.6.3-alpha.1 - tag: v1.0.0 - pullPolicy: IfNotPresent - # imagePullPolicy: Always - -imagePullSecrets: [] + # -- ORY KRATOS image + repository: oryd/kratos + # -- ORY KRATOS VERSION + # Alternative format: image: oryd/kratos:v0.6.3-alpha.1 + tag: v1.0.0 + pullPolicy: IfNotPresent + # imagePullPolicy: Always + +imagePullSecrets: [ ] nameOverride: "" fullnameOverride: "" service: - admin: - enabled: true - type: ClusterIP - # -- Load balancer IP - loadBalancerIP: "" - port: 80 - # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) - name: http - # -- Provide custom labels. Use the same syntax as for annotations. - labels: {} - # -- If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - # -- Path to the metrics endpoint - metricsPath: /admin/metrics/prometheus - public: - enabled: true - type: ClusterIP - # -- Load balancer IP - loadBalancerIP: "" - port: 80 - # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) - name: http - # -- Provide custom labels. Use the same syntax as for annotations. - labels: {} - # -- If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" + admin: + enabled: true + type: ClusterIP + # -- Load balancer IP + loadBalancerIP: "" + port: 80 + # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) + name: http + # -- Provide custom labels. Use the same syntax as for annotations. + labels: { } + # -- If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + annotations: { } + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # -- Path to the metrics endpoint + metricsPath: /admin/metrics/prometheus + public: + enabled: true + type: ClusterIP + # -- Load balancer IP + loadBalancerIP: "" + port: 80 + # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) + name: http + # -- Provide custom labels. Use the same syntax as for annotations. + labels: { } + # -- If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + annotations: { } + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" secret: - # -- switch to false to prevent creating the secret - enabled: true - # -- Provide custom name of existing secret, or custom name of secret to be created - nameOverride: "" - # nameOverride: "myCustomSecret" - # -- Annotations to be added to secret. Annotations are added only when secret is being created. Existing secret will not be modified. - secretAnnotations: - # Create the secret before installation, and only then. This saves the secret from regenerating during an upgrade - # pre-upgrade is needed to upgrade from 0.7.0 to newer. Can be deleted afterwards. - helm.sh/hook-weight: "0" - helm.sh/hook: "pre-install, pre-upgrade" - helm.sh/hook-delete-policy: "before-hook-creation" - helm.sh/resource-policy: "keep" - # -- switch to false to prevent checksum annotations being maintained and propogated to the pods - hashSumEnabled: true + # -- switch to false to prevent creating the secret + enabled: true + # -- Provide custom name of existing secret, or custom name of secret to be created + nameOverride: "" + # nameOverride: "myCustomSecret" + # -- Annotations to be added to secret. Annotations are added only when secret is being created. Existing secret will not be modified. + secretAnnotations: + # Create the secret before installation, and only then. This saves the secret from regenerating during an upgrade + # pre-upgrade is needed to upgrade from 0.7.0 to newer. Can be deleted afterwards. + helm.sh/hook-weight: "0" + helm.sh/hook: "pre-install, pre-upgrade" + helm.sh/hook-delete-policy: "before-hook-creation" + helm.sh/resource-policy: "keep" + # -- switch to false to prevent checksum annotations being maintained and propogated to the pods + hashSumEnabled: true ingress: - admin: - enabled: false - className: "" - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: kratos.admin.local.com - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - public: - enabled: false - className: "" - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: kratos.public.local.com - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local + admin: + enabled: false + className: "" + annotations: + { } + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: kratos.admin.local.com + paths: + - path: / + pathType: ImplementationSpecific + tls: [ ] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + public: + enabled: false + className: "" + annotations: + { } + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: kratos.public.local.com + paths: + - path: / + pathType: ImplementationSpecific + tls: [ ] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local kratos: - development: false - # -- Enable the initialization job. Required to work with a DB - - # -- Enables database migration - automigration: - enabled: false - # -- Configure the way to execute database migration. Possible values: job, initContainer - # When set to job, the migration will be executed as a job on release or upgrade. - # When set to initContainer, the migration will be executed when Kratos pod is created - # Defaults to job - type: job - # -- Ability to override the entrypoint of the automigration container - # (e.g. to source dynamic secrets or export environment dynamic variables) - customCommand: [] - # -- Ability to override arguments of the entrypoint. Can be used in-depended of customCommand - # eg: - # - sleep 5; - # - kratos - customArgs: [] - # -- resource requests and limits for the automigration initcontainer - resources: {} - - # -- You can add multiple identity schemas here. You can pass JSON schema using `--set-file` Helm CLI argument. - identitySchemas: {} - # identitySchemas: - # "identity.default.schema.json": | - # { - # // ... - # } - # "identity.email.schema.json": | - # { - # // ... - # } - # "identity.phone.schema.json": | - # {{ .Values.phone_schema }} - - # -- You can customize the emails Kratos is sending (also uncomment config.courier.template_override_path below) - emailTemplates: {} - # emailTemplates: - # recovery: - # valid: - # subject: Recover access to your account - # body: |- - # Hi, please recover access to your account by clicking the following link: - # {{ .RecoveryURL }} - # plainBody: |- - # Hi, please recover access to your account by clicking the following link: {{ .RecoveryURL }} - # invalid: - # subject: Account access attempted - # body: |- - # Hi, you (or someone else) entered this email address when trying to recover access to an account. - # However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. - # plainBody: |- - # Hi, you (or someone else) entered this email address when trying to recover access to an account. - # verification: - # valid: - # subject: Please verify your email address - # body: |- - # Hi, please verify your account by clicking the following link: - # {{ .VerificationURL }} - # plainBody: |- - # Hi, please verify your account by clicking the following link: {{ .VerificationURL }} - # invalid: - # subject: - # body: - # plainBody: - - config: - courier: - smtp: {} - #template_override_path: /conf/courier-templates - - serve: - public: - port: 4433 - admin: - port: 4434 - - secrets: {} + development: false + # -- Enable the initialization job. Required to work with a DB + + # -- Enables database migration + automigration: + enabled: false + # -- Configure the way to execute database migration. Possible values: job, initContainer + # When set to job, the migration will be executed as a job on release or upgrade. + # When set to initContainer, the migration will be executed when Kratos pod is created + # Defaults to job + type: job + # -- Ability to override the entrypoint of the automigration container + # (e.g. to source dynamic secrets or export environment dynamic variables) + customCommand: [ ] + # -- Ability to override arguments of the entrypoint. Can be used in-depended of customCommand + # eg: + # - sleep 5; + # - kratos + customArgs: [ ] + # -- resource requests and limits for the automigration initcontainer + resources: { } + + # -- You can add multiple identity schemas here. You can pass JSON schema using `--set-file` Helm CLI argument. + identitySchemas: { } + # identitySchemas: + # "identity.default.schema.json": | + # { + # // ... + # } + # "identity.email.schema.json": | + # { + # // ... + # } + # "identity.phone.schema.json": | + # {{ .Values.phone_schema }} + + # -- You can customize the emails Kratos is sending (also uncomment config.courier.template_override_path below) + emailTemplates: { } + # emailTemplates: + # recovery: + # valid: + # subject: Recover access to your account + # body: |- + # Hi, please recover access to your account by clicking the following link: + # {{ .RecoveryURL }} + # plainBody: |- + # Hi, please recover access to your account by clicking the following link: {{ .RecoveryURL }} + # invalid: + # subject: Account access attempted + # body: |- + # Hi, you (or someone else) entered this email address when trying to recover access to an account. + # However, this email address is not on our database of registered users and therefore the attempt has failed. If this was you, check if you signed up using a different address. If this was not you, please ignore this email. + # plainBody: |- + # Hi, you (or someone else) entered this email address when trying to recover access to an account. + # verification: + # valid: + # subject: Please verify your email address + # body: |- + # Hi, please verify your account by clicking the following link: + # {{ .VerificationURL }} + # plainBody: |- + # Hi, please verify your account by clicking the following link: {{ .VerificationURL }} + # invalid: + # subject: + # body: + # plainBody: + + config: + courier: + smtp: { } + #template_override_path: /conf/courier-templates + + serve: + public: + port: 4433 + admin: + port: 4434 + + secrets: { } # -- Configuration options for the k8s deployment deployment: - lifecycle: {} - # -- Configure the livenessProbe parameters - livenessProbe: - initialDelaySeconds: 5 - periodSeconds: 10 - failureThreshold: 5 - # -- Configure the readinessProbe parameters - readinessProbe: - initialDelaySeconds: 5 - periodSeconds: 10 - failureThreshold: 5 - # -- Configure the startupProbe parameters - startupProbe: - failureThreshold: 60 - successThreshold: 1 - periodSeconds: 1 - timeoutSeconds: 1 - - # -- Configure a custom livenessProbe. This overwrites the default object - customLivenessProbe: {} - # -- Configure a custom readinessProbe. This overwrites the default object - customReadinessProbe: {} - # -- Configure a custom startupProbe. This overwrites the default object - customStartupProbe: {} - - # -- Array of extra arguments to be passed down to the deployment. Kubernetes args format is expected - # - --foo - # - --sqa-opt-out - extraArgs: [] - - # -- Array of extra envs to be passed to the deployment. Kubernetes format is expected - # - name: FOO - # value: BAR - extraEnv: [] - # -- If you want to mount external volume - # For example, mount a secret containing Certificate root CA to verify database - # TLS connection. - extraVolumes: [] - # - name: my-volume - # secret: - # secretName: my-secret - extraVolumeMounts: [] - # - name: my-volume - # mountPath: /etc/secrets/my-secret - # readOnly: true - - # extraVolumes: - # - name: postgresql-tls - # secret: - # secretName: postgresql-root-ca - # extraVolumeMounts: - # - name: postgresql-tls - # mountPath: "/etc/postgresql-tls" - # readOnly: true - - # -- If you want to add extra init containers. These are processed before the migration init container. - extraInitContainers: "" - # extraInitContainers: | - # - name: ... - # image: ... - - # -- If you want to add extra sidecar containers. - extraContainers: "" - # extraContainers: | - # - name: ... - # image: ... - - # -- Set desired resource parameters - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - - # -- Pod priority - # https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ - priorityClassName: "" - - # -- Node labels for pod assignment. - nodeSelector: {} - # If you do want to specify node labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. - # foo: bar - - # -- Configure node tolerations. - tolerations: [] - - # -- Configure node affinity - affinity: {} - - # -- Configure pod topologySpreadConstraints. - topologySpreadConstraints: [] - # - maxSkew: 1 - # topologyKey: topology.kubernetes.io/zone - # whenUnsatisfiable: DoNotSchedule - # labelSelector: - # matchLabels: - # app.kubernetes.io/name: kratos - # app.kubernetes.io/instance: kratos - - # -- Configure pod dnsConfig. - dnsConfig: {} - # options: - # - name: "ndots" - # value: "1" - - labels: {} - # If you do want to specify additional labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'labels:'. - # e.g. type: app - - annotations: {} - # If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" - - # -- The secret specified here will be used to load environment variables with envFrom. - # This allows arbitrary environment variables to be provided to the application which is useful for - # sensitive values which should not be in a configMap. - # This secret is not created by the helm chart and must already exist in the namespace. - # https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - # environmentSecretsName: - - # -- Specify the serviceAccountName value. - # In some situations it is needed to provide specific permissions to Kratos deployments. - # Like for example installing Kratos on a cluster with a PosSecurityPolicy and Istio. - # Uncomment if it is needed to provide a ServiceAccount for the Kratos deployment. - serviceAccount: - # -- Specifies whether a service account should be created - create: true - # -- Annotations to add to the service account - annotations: {} - # -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template - name: "" - - # https://github.com/kubernetes/kubernetes/issues/57601 - automountServiceAccountToken: true - - # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects - podMetadata: - # -- Extra pod level labels - labels: {} - # -- Extra pod level annotations - annotations: {} - - # -- Parameters for the automigration initContainer - automigration: - # -- Array of extra envs to be passed to the initContainer. Kubernetes format is expected + lifecycle: { } + # -- Configure the livenessProbe parameters + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 5 + # -- Configure the readinessProbe parameters + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 5 + # -- Configure the startupProbe parameters + startupProbe: + failureThreshold: 60 + successThreshold: 1 + periodSeconds: 1 + timeoutSeconds: 1 + + # -- Configure a custom livenessProbe. This overwrites the default object + customLivenessProbe: { } + # -- Configure a custom readinessProbe. This overwrites the default object + customReadinessProbe: { } + # -- Configure a custom startupProbe. This overwrites the default object + customStartupProbe: { } + + # -- Array of extra arguments to be passed down to the deployment. Kubernetes args format is expected + # - --foo + # - --sqa-opt-out + extraArgs: [ ] + + # -- Array of extra envs to be passed to the deployment. Kubernetes format is expected # - name: FOO # value: BAR - extraEnv: [] - # -- Number of revisions kept in history - revisionHistoryLimit: 5 + extraEnv: [ ] + # -- If you want to mount external volume + # For example, mount a secret containing Certificate root CA to verify database + # TLS connection. + extraVolumes: [ ] + # - name: my-volume + # secret: + # secretName: my-secret + extraVolumeMounts: [ ] + # - name: my-volume + # mountPath: /etc/secrets/my-secret + # readOnly: true + + # extraVolumes: + # - name: postgresql-tls + # secret: + # secretName: postgresql-root-ca + # extraVolumeMounts: + # - name: postgresql-tls + # mountPath: "/etc/postgresql-tls" + # readOnly: true + + # -- If you want to add extra init containers. These are processed before the migration init container. + extraInitContainers: "" + # extraInitContainers: | + # - name: ... + # image: ... + + # -- If you want to add extra sidecar containers. + extraContainers: "" + # extraContainers: | + # - name: ... + # image: ... + + # -- Set desired resource parameters + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + resources: { } + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # -- Pod priority + # https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + priorityClassName: "" + + # -- Node labels for pod assignment. + nodeSelector: { } + # If you do want to specify node labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. + # foo: bar + + # -- Configure node tolerations. + tolerations: [ ] + + # -- Configure node affinity + affinity: { } + + # -- Configure pod topologySpreadConstraints. + topologySpreadConstraints: [ ] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: kratos + # app.kubernetes.io/instance: kratos + + # -- Configure pod dnsConfig. + dnsConfig: { } + # options: + # - name: "ndots" + # value: "1" + + labels: { } + # If you do want to specify additional labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'labels:'. + # e.g. type: app + + annotations: { } + # If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" + + # -- The secret specified here will be used to load environment variables with envFrom. + # This allows arbitrary environment variables to be provided to the application which is useful for + # sensitive values which should not be in a configMap. + # This secret is not created by the helm chart and must already exist in the namespace. + # https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + # environmentSecretsName: + + # -- Specify the serviceAccountName value. + # In some situations it is needed to provide specific permissions to Kratos deployments. + # Like for example installing Kratos on a cluster with a PosSecurityPolicy and Istio. + # Uncomment if it is needed to provide a ServiceAccount for the Kratos deployment. + serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Annotations to add to the service account + annotations: { } + # -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template + name: "" + + # https://github.com/kubernetes/kubernetes/issues/57601 + automountServiceAccountToken: true + + # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects + podMetadata: + # -- Extra pod level labels + labels: { } + # -- Extra pod level annotations + annotations: { } + + # -- Parameters for the automigration initContainer + automigration: + # -- Array of extra envs to be passed to the initContainer. Kubernetes format is expected + # - name: FOO + # value: BAR + extraEnv: [ ] + # -- Number of revisions kept in history + revisionHistoryLimit: 5 ## -- Configuration options for the k8s statefulSet statefulSet: - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - - # -- Array of extra arguments to be passed down to the StatefulSet. Kubernetes args format is expected - extraArgs: [] - # - --foo - # - --sqa-opt-out - - extraEnv: [] - # -- If you want to mount external volume - # For example, mount a secret containing Certificate root CA to verify database - # TLS connection. - extraVolumes: [] - # - name: my-volume - # secret: - # secretName: my-secret - extraVolumeMounts: [] - # - name: my-volume - # mountPath: /etc/secrets/my-secret - # readOnly: true - - # -- If you want to add extra init containers. These are processed before the migration init container. - extraInitContainers: "" - # extraInitContainers: | - # - name: ... - # image: ... - - # -- If you want to add extra sidecar containers. - extraContainers: "" - # extraContainers: | - # - name: ... - # image: ... - - annotations: {} - # If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" - - # -- The secret specified here will be used to load environment variables with envFrom. - # This allows arbitrary environment variables to be provided to the application which is useful for - # sensitive values which should not be in a configMap. - # This secret is not created by the helm chart and must already exist in the namespace. - # https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - # environmentSecretsName: - - labels: {} - # If you do want to specify additional labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'labels:'. - # e.g. type: app - - # -- Pod priority - # https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ - priorityClassName: "" - - # -- Node labels for pod assignment. - nodeSelector: {} - # If you do want to specify node labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. - # foo: bar - - # -- Configure node tolerations. - tolerations: [] - - # -- Configure node affinity - affinity: {} - - # -- Configure pod topologySpreadConstraints. - topologySpreadConstraints: [] - # - maxSkew: 1 - # topologyKey: topology.kubernetes.io/zone - # whenUnsatisfiable: DoNotSchedule - # labelSelector: - # matchLabels: - # app.kubernetes.io/name: kratos - # app.kubernetes.io/instance: kratos - - # -- Configure pod dnsConfig. - dnsConfig: {} - # options: - # - name: "ndots" - # value: "1" - - log: - format: json - level: trace + resources: { } + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi - # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects - podMetadata: - # -- Extra pod level labels - labels: {} - # -- Extra pod level annotations - annotations: {} - # -- Number of revisions kept in history - revisionHistoryLimit: 5 + # -- Array of extra arguments to be passed down to the StatefulSet. Kubernetes args format is expected + extraArgs: [ ] + # - --foo + # - --sqa-opt-out + + extraEnv: [ ] + # -- If you want to mount external volume + # For example, mount a secret containing Certificate root CA to verify database + # TLS connection. + extraVolumes: [ ] + # - name: my-volume + # secret: + # secretName: my-secret + extraVolumeMounts: [ ] + # - name: my-volume + # mountPath: /etc/secrets/my-secret + # readOnly: true + + # -- If you want to add extra init containers. These are processed before the migration init container. + extraInitContainers: "" + # extraInitContainers: | + # - name: ... + # image: ... + + # -- If you want to add extra sidecar containers. + extraContainers: "" + # extraContainers: | + # - name: ... + # image: ... + + annotations: { } + # If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" + + # -- The secret specified here will be used to load environment variables with envFrom. + # This allows arbitrary environment variables to be provided to the application which is useful for + # sensitive values which should not be in a configMap. + # This secret is not created by the helm chart and must already exist in the namespace. + # https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + # environmentSecretsName: + + labels: { } + # If you do want to specify additional labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'labels:'. + # e.g. type: app + + # -- Pod priority + # https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + priorityClassName: "" + + # -- Node labels for pod assignment. + nodeSelector: { } + # If you do want to specify node labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. + # foo: bar + + # -- Configure node tolerations. + tolerations: [ ] + + # -- Configure node affinity + affinity: { } + + # -- Configure pod topologySpreadConstraints. + topologySpreadConstraints: [ ] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: kratos + # app.kubernetes.io/instance: kratos + + # -- Configure pod dnsConfig. + dnsConfig: { } + # options: + # - name: "ndots" + # value: "1" + + log: + format: json + level: trace + + # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects + podMetadata: + # -- Extra pod level labels + labels: { } + # -- Extra pod level annotations + annotations: { } + # -- Number of revisions kept in history + revisionHistoryLimit: 5 # -- Container level security context securityContext: - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 65534 - runAsGroup: 65534 - allowPrivilegeEscalation: false - privileged: false - seccompProfile: - type: RuntimeDefault - seLinuxOptions: - level: "s0:c123,c456" + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + allowPrivilegeEscalation: false + privileged: false + seccompProfile: + type: RuntimeDefault + seLinuxOptions: + level: "s0:c123,c456" # -- Pod level security context podSecurityContext: - fsGroupChangePolicy: "OnRootMismatch" - runAsNonRoot: true - runAsUser: 65534 - fsGroup: 65534 - runAsGroup: 65534 - seccompProfile: - type: RuntimeDefault - sysctls: [] - supplementalGroups: [] + fsGroupChangePolicy: "OnRootMismatch" + runAsNonRoot: true + runAsUser: 65534 + fsGroup: 65534 + runAsGroup: 65534 + seccompProfile: + type: RuntimeDefault + sysctls: [ ] + supplementalGroups: [ ] # -- Horizontal pod autoscaling configuration autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPU: {} - # type: Utilization - # averageUtilization: 80 - targetMemory: {} - # type: Utilization - # averageUtilization: 80 + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPU: { } + # type: Utilization + # averageUtilization: 80 + targetMemory: { } + # type: Utilization + # averageUtilization: 80 # -- Values for initialization job job: - # -- If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - annotations: - helm.sh/hook-weight: "1" - helm.sh/hook: "pre-install, pre-upgrade" - helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - - # -- If you want to add extra sidecar containers. - extraContainers: "" - # extraContainers: | - # - name: ... - # image: ... - - # -- If you want to add extra init containers. - extraInitContainers: "" - # extraInitContainers: | - # - name: ... - # image: ... - - # -- Array of extra envs to be passed to the job. This takes precedence over deployment variables. Kubernetes format is expected - # - name: FOO - # value: BAR - extraEnv: [] - - # -- Node labels for pod assignment. - nodeSelector: {} - # If you do want to specify node labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. - # foo: bar - - # -- resource requests and limits for the job - resources: {} - - # -- Configure node tolerations. - tolerations: [] - - # -- If you want to add lifecycle hooks. - lifecycle: "" - # lifecycle: | - # preStop: - # exec: - # command: [...] - - # -- Set automounting of the SA token - automountServiceAccountToken: true - - # -- Set sharing process namespace - shareProcessNamespace: false - - # -- Specify the serviceAccountName value. - # In some situations it is needed to provide specific permissions to Kratos deployments - # Like for example installing Kratos on a cluster with a PosSecurityPolicy and Istio. - # Uncomment if it is needed to provide a ServiceAccount for the Kratos deployment. - serviceAccount: - # -- Specifies whether a service account should be created - create: true - # -- Annotations to add to the service account + # -- If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. annotations: - helm.sh/hook-weight: "0" - helm.sh/hook: "pre-install, pre-upgrade" - helm.sh/hook-delete-policy: "before-hook-creation" - # -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template - name: "" - - # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects - podMetadata: - # -- Extra pod level labels - labels: {} - # -- Extra pod level annotations - annotations: {} - - spec: - # -- Set job back off limit - backoffLimit: 10 - -# -- Configuration of the courier -courier: - enabled: true - -# -- Configuration of the watcher sidecar -watcher: - enabled: false - image: oryd/k8s-toolbox:0.0.5 - # -- Path to mounted file, which wil be monitored for changes. eg: /etc/secrets/my-secret/foo - mountFile: "" - # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects - podMetadata: - # -- Extra pod level labels - labels: {} - # -- Extra pod level annotations - annotations: {} - # -- Label key used for managing applications - watchLabelKey: "ory.sh/watcher" - # -- Number of revisions kept in history - revisionHistoryLimit: 5 - -# -- SQL cleanup cron job configuration -cleanup: - # -- Enable cleanup of stale database rows by periodically running the cleanup sql command - enabled: false - - # -- Configure how many records are cleaned per run - batchSize: 100 + helm.sh/hook-weight: "1" + helm.sh/hook: "pre-install, pre-upgrade" + helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" - # -- Configure how long to wait between each table cleanup - sleepTables: 1m0s + # -- If you want to add extra sidecar containers. + extraContainers: "" + # extraContainers: | + # - name: ... + # image: ... - # -- Configure the youngest records to keep - keepLast: 6h + # -- If you want to add extra init containers. + extraInitContainers: "" + # extraInitContainers: | + # - name: ... + # image: ... -# -- CronJob configuration -cronjob: - cleanup: - # -- Configure how often the cron job is ran - schedule: "0 */1 * * *" - - # -- Configure the arguments of the entrypoint, overriding the default value - customArgs: [] - - # -- Array of extra envs to be passed to the cronjob. This takes precedence over deployment variables. Kubernetes format is expected + # -- Array of extra envs to be passed to the job. This takes precedence over deployment variables. Kubernetes format is expected # - name: FOO # value: BAR - extraEnv: [] + extraEnv: [ ] + + # -- Node labels for pod assignment. + nodeSelector: { } + # If you do want to specify node labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'nodeSelector:'. + # foo: bar + + # -- resource requests and limits for the job + resources: { } + + # -- Configure node tolerations. + tolerations: [ ] + + # -- If you want to add lifecycle hooks. + lifecycle: "" + # lifecycle: | + # preStop: + # exec: + # command: [...] + + # -- Set automounting of the SA token + automountServiceAccountToken: true + + # -- Set sharing process namespace + shareProcessNamespace: false + + # -- Specify the serviceAccountName value. + # In some situations it is needed to provide specific permissions to Kratos deployments + # Like for example installing Kratos on a cluster with a PosSecurityPolicy and Istio. + # Uncomment if it is needed to provide a ServiceAccount for the Kratos deployment. + serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Annotations to add to the service account + annotations: + helm.sh/hook-weight: "0" + helm.sh/hook: "pre-install, pre-upgrade" + helm.sh/hook-delete-policy: "before-hook-creation" + # -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template + name: "" + + # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects + podMetadata: + # -- Extra pod level labels + labels: { } + # -- Extra pod level annotations + annotations: { } - # -- Set custom cron job level labels - labels: {} + spec: + # -- Set job back off limit + backoffLimit: 10 - # -- Set custom cron job level annotations - annotations: {} +# -- Configuration of the courier +courier: + enabled: true +# -- Configuration of the watcher sidecar +watcher: + enabled: false + image: oryd/k8s-toolbox:0.0.5 + # -- Path to mounted file, which wil be monitored for changes. eg: /etc/secrets/my-secret/foo + mountFile: "" # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects podMetadata: - # -- Extra pod level labels - labels: {} + # -- Extra pod level labels + labels: { } + # -- Extra pod level annotations + annotations: { } + # -- Label key used for managing applications + watchLabelKey: "ory.sh/watcher" + # -- Number of revisions kept in history + revisionHistoryLimit: 5 - # -- Extra pod level annotations - annotations: {} +# -- SQL cleanup cron job configuration +cleanup: + # -- Enable cleanup of stale database rows by periodically running the cleanup sql command + enabled: false - # -- Configure node labels for pod assignment - nodeSelector: {} + # -- Configure how many records are cleaned per run + batchSize: 100 - # -- Configure node tolerations - tolerations: [] + # -- Configure how long to wait between each table cleanup + sleepTables: 1m0s - # -- Configure node affinity - affinity: {} + # -- Configure the youngest records to keep + keepLast: 6h - # -- We usually recommend not to specify default resources and to leave this as a conscious choice for the user. - # This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - resources: - limits: {} - requests: {} +# -- CronJob configuration +cronjob: + cleanup: + # -- Configure how often the cron job is ran + schedule: "0 */1 * * *" + + # -- Configure the arguments of the entrypoint, overriding the default value + customArgs: [ ] + + # -- Array of extra envs to be passed to the cronjob. This takes precedence over deployment variables. Kubernetes format is expected + # - name: FOO + # value: BAR + extraEnv: [ ] + + # -- Set custom cron job level labels + labels: { } + + # -- Set custom cron job level annotations + annotations: { } + + # -- Specify pod metadata, this metadata is added directly to the pod, and not higher objects + podMetadata: + # -- Extra pod level labels + labels: { } + + # -- Extra pod level annotations + annotations: { } + + # -- Configure node labels for pod assignment + nodeSelector: { } + + # -- Configure node tolerations + tolerations: [ ] + + # -- Configure node affinity + affinity: { } + + # -- We usually recommend not to specify default resources and to leave this as a conscious choice for the user. + # This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + resources: + limits: { } + requests: { } # -- PodDistributionBudget configuration pdb: - enabled: false - spec: - minAvailable: "" - maxUnavailable: "" + enabled: false + spec: + minAvailable: "" + maxUnavailable: "" # -- Parameters for the Prometheus ServiceMonitor objects. # Reference: https://docs.openshift.com/container-platform/4.6/rest_api/monitoring_apis/servicemonitor-monitoring-coreos-com-v1.html serviceMonitor: - # -- switch to true to enable creating the ServiceMonitor - enabled: false - # -- HTTP scheme to use for scraping. - scheme: http - # -- Interval at which metrics should be scraped - scrapeInterval: 60s - # -- Timeout after which the scrape is ended - scrapeTimeout: 30s - # -- Provide additional labels to the ServiceMonitor ressource metadata - labels: {} - # -- TLS configuration to use when scraping the endpoint - tlsConfig: {} + # -- switch to true to enable creating the ServiceMonitor + enabled: false + # -- HTTP scheme to use for scraping. + scheme: http + # -- Interval at which metrics should be scraped + scrapeInterval: 60s + # -- Timeout after which the scrape is ended + scrapeTimeout: 30s + # -- Provide additional labels to the ServiceMonitor ressource metadata + labels: { } + # -- TLS configuration to use when scraping the endpoint + tlsConfig: { } configmap: - # -- switch to false to prevent checksum annotations being maintained and propogated to the pods - hashSumEnabled: true - # -- If you do want to specify annotations for configmap, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - annotations: {} + # -- switch to false to prevent checksum annotations being maintained and propogated to the pods + hashSumEnabled: true + # -- If you do want to specify annotations for configmap, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + annotations: { } test: - # -- use a busybox image from another repository - busybox: - repository: busybox - tag: 1 + # -- use a busybox image from another repository + busybox: + repository: busybox + tag: 1 diff --git a/src/main/docker/test/ory_helm/kratos-selfservice-ui-node_default.yaml b/src/main/docker/test/ory_helm/kratos-selfservice-ui-node_default.yaml index 37358bdb0..5fb754c6d 100644 --- a/src/main/docker/test/ory_helm/kratos-selfservice-ui-node_default.yaml +++ b/src/main/docker/test/ory_helm/kratos-selfservice-ui-node_default.yaml @@ -8,164 +8,164 @@ replicaCount: 1 revisionHistoryLimit: 5 # -- Deployment image settings image: - # SELFSERVICE image - repository: oryd/kratos-selfservice-ui-node - # -- ORY KRATOS VERSION - tag: "v0.13.0-20" - pullPolicy: IfNotPresent + # SELFSERVICE image + repository: oryd/kratos-selfservice-ui-node + # -- ORY KRATOS VERSION + tag: "v0.13.0-20" + pullPolicy: IfNotPresent -imagePullSecrets: [] +imagePullSecrets: [ ] nameOverride: "" fullnameOverride: "" # -- Application config config: - csrfCookieName: "radar" - secrets: { - unsafe_csrf_cookie_secret - } + csrfCookieName: "radar" + secrets: { + unsafe_csrf_cookie_secret + } # -- Service configuration service: - type: ClusterIP - # -- The load balancer IP - loadBalancerIP: "" - port: 80 - # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) - name: http + type: ClusterIP + # -- The load balancer IP + loadBalancerIP: "" + port: 80 + # -- The service port name. Useful to set a custom service port name if it must follow a scheme (e.g. Istio) + name: http secret: - # -- switch to false to prevent creating the secret - enabled: true - # -- Provide custom name of existing secret, or custom name of secret to be created - nameOverride: "" - # nameOverride: "myCustomSecret" - # -- Annotations to be added to secret. Annotations are added only when secret is being created. Existing secret will not be modified. - secretAnnotations: - # Create the secret before installation, and only then. This saves the secret from regenerating during an upgrade - # pre-upgrade is needed to upgrade from 0.7.0 to newer. Can be deleted afterwards. - helm.sh/hook-weight: "0" - helm.sh/hook: "pre-install, pre-upgrade" - helm.sh/hook-delete-policy: "before-hook-creation" - helm.sh/resource-policy: "keep" - # -- switch to false to prevent checksum annotations being maintained and propogated to the pods - hashSumEnabled: true + # -- switch to false to prevent creating the secret + enabled: true + # -- Provide custom name of existing secret, or custom name of secret to be created + nameOverride: "" + # nameOverride: "myCustomSecret" + # -- Annotations to be added to secret. Annotations are added only when secret is being created. Existing secret will not be modified. + secretAnnotations: + # Create the secret before installation, and only then. This saves the secret from regenerating during an upgrade + # pre-upgrade is needed to upgrade from 0.7.0 to newer. Can be deleted afterwards. + helm.sh/hook-weight: "0" + helm.sh/hook: "pre-install, pre-upgrade" + helm.sh/hook-delete-policy: "before-hook-creation" + helm.sh/resource-policy: "keep" + # -- switch to false to prevent checksum annotations being maintained and propogated to the pods + hashSumEnabled: true # -- Ingress configration ingress: - enabled: false - className: "" - annotations: - {} + enabled: false + className: "" + annotations: + { } # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [ ] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local # -- Container level security context securityContext: - capabilities: - drop: - - ALL - readOnlyRootFilesystem: false - runAsNonRoot: true - runAsUser: 10000 - runAsGroup: 10000 - allowPrivilegeEscalation: false - privileged: false - seccompProfile: - type: RuntimeDefault - seLinuxOptions: - level: "s0:c123,c456" + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 10000 + runAsGroup: 10000 + allowPrivilegeEscalation: false + privileged: false + seccompProfile: + type: RuntimeDefault + seLinuxOptions: + level: "s0:c123,c456" # -- Pod level security context podSecurityContext: - fsGroupChangePolicy: "OnRootMismatch" - runAsNonRoot: true - runAsUser: 10000 - fsGroup: 10000 - runAsGroup: 10000 - seccompProfile: - type: RuntimeDefault - sysctls: [] - supplementalGroups: [] + fsGroupChangePolicy: "OnRootMismatch" + runAsNonRoot: true + runAsUser: 10000 + fsGroup: 10000 + runAsGroup: 10000 + seccompProfile: + type: RuntimeDefault + sysctls: [ ] + supplementalGroups: [ ] # -- Deployment configuration deployment: - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - - # -- Array of extra envs to be passed to the deployment. Kubernetes format is expected - # - name: FOO - # value: BAR - extraEnv: [] - # -- If you want to mount external volume - # For example, mount a secret containing Certificate root CA to verify database - # TLS connection. - extraVolumes: [] - # - name: my-volume - # secret: - # secretName: my-secret - extraVolumeMounts: [] - # - name: my-volume - # mountPath: /etc/secrets/my-secret - # readOnly: true - - # -- Node labels for pod assignment. - nodeSelector: {} - # If you do want to specify node labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - # foo: bar - - # -- Configure node tolerations. - tolerations: [] - - # -- Configure pod topologySpreadConstraints. - topologySpreadConstraints: [] - # - maxSkew: 1 - # topologyKey: topology.kubernetes.io/zone - # whenUnsatisfiable: DoNotSchedule - # labelSelector: - # matchLabels: - # app.kubernetes.io/name: kratos-selfservice-ui-node - # app.kubernetes.io/instance: kratos-selfservice-ui-node - - # -- Configure pod dnsConfig. - dnsConfig: {} - # options: - # - name: "ndots" - # value: "1" - - labels: {} - # If you do want to specify additional labels, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'labels:'. - # e.g. type: app - - annotations: {} - # If you do want to specify annotations, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. - # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" - - # https://github.com/kubernetes/kubernetes/issues/57601 - automountServiceAccountToken: false - -affinity: {} + resources: { } + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # -- Array of extra envs to be passed to the deployment. Kubernetes format is expected + # - name: FOO + # value: BAR + extraEnv: [ ] + # -- If you want to mount external volume + # For example, mount a secret containing Certificate root CA to verify database + # TLS connection. + extraVolumes: [ ] + # - name: my-volume + # secret: + # secretName: my-secret + extraVolumeMounts: [ ] + # - name: my-volume + # mountPath: /etc/secrets/my-secret + # readOnly: true + + # -- Node labels for pod assignment. + nodeSelector: { } + # If you do want to specify node labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + # foo: bar + + # -- Configure node tolerations. + tolerations: [ ] + + # -- Configure pod topologySpreadConstraints. + topologySpreadConstraints: [ ] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: kratos-selfservice-ui-node + # app.kubernetes.io/instance: kratos-selfservice-ui-node + + # -- Configure pod dnsConfig. + dnsConfig: { } + # options: + # - name: "ndots" + # value: "1" + + labels: { } + # If you do want to specify additional labels, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'labels:'. + # e.g. type: app + + annotations: { } + # If you do want to specify annotations, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'annotations:'. + # e.g. sidecar.istio.io/rewriteAppHTTPProbers: "true" + + # https://github.com/kubernetes/kubernetes/issues/57601 + automountServiceAccountToken: false + +affinity: { } # -- Set this to ORY Kratos's Admin URL kratosAdminUrl: "http://kratos-admin" @@ -185,7 +185,7 @@ jwksUrl: "http://oathkeeper-api" projectName: "SecureApp" test: - # -- use a busybox image from another repository - busybox: - repository: busybox - tag: 1 + # -- use a busybox image from another repository + busybox: + repository: busybox + tag: 1 diff --git a/src/main/java/org/radarbase/management/ManagementPortalApp.kt b/src/main/java/org/radarbase/management/ManagementPortalApp.kt index 2200a96d2..0c3b93278 100644 --- a/src/main/java/org/radarbase/management/ManagementPortalApp.kt +++ b/src/main/java/org/radarbase/management/ManagementPortalApp.kt @@ -23,13 +23,17 @@ import javax.annotation.PostConstruct "org.radarbase.management.repository", "org.radarbase.management.service", "org.radarbase.management.security", - "org.radarbase.management.web" + "org.radarbase.management.web", ) @EnableAutoConfiguration @EnableConfigurationProperties( - LiquibaseProperties::class, ApplicationProperties::class, ManagementPortalProperties::class + LiquibaseProperties::class, + ApplicationProperties::class, + ManagementPortalProperties::class, ) -class ManagementPortalApp(private val env: Environment) { +class ManagementPortalApp( + private val env: Environment, +) { /** * Initializes ManagementPortal. * @@ -45,20 +49,20 @@ class ManagementPortalApp(private val env: Environment) { */ @PostConstruct fun initApplication() { - if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) - && env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) && + env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) ) { log.error( - "You have misconfigured your application! It should not run " - + "with both the 'dev' and 'prod' profiles at the same time." + "You have misconfigured your application! It should not run " + + "with both the 'dev' and 'prod' profiles at the same time.", ) } - if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) - && env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_CLOUD)) + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) && + env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_CLOUD)) ) { log.error( - "You have misconfigured your application! It should not" - + "run with both the 'dev' and 'cloud' profiles at the same time." + "You have misconfigured your application! It should not" + + "run with both the 'dev' and 'cloud' profiles at the same time.", ) } } @@ -85,20 +89,20 @@ class ManagementPortalApp(private val env: Environment) { log.info( """ - ----------------------------------------------------- - ${'\t'}Application '{}' is running! Access URLs: - ${'\t'}Local: ${'\t'}${'\t'}{}://localhost:{} - ${'\t'}External: ${'\t'}{}://{}:{} - ${'\t'}Profile(s): ${'\t'}{} - ----------------------------------------------------- - """.trimIndent(), + ----------------------------------------------------- + ${'\t'}Application '{}' is running! Access URLs: + ${'\t'}Local: ${'\t'}${'\t'}{}://localhost:{} + ${'\t'}External: ${'\t'}{}://{}:{} + ${'\t'}Profile(s): ${'\t'}{} + ----------------------------------------------------- + """.trimIndent(), env.getProperty("spring.application.name"), protocol, env.getProperty("server.port"), protocol, InetAddress.getLocalHost().hostAddress, env.getProperty("server.port"), - env.activeProfiles + env.activeProfiles, ) } } diff --git a/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.java b/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.java deleted file mode 100644 index 3e912af1e..000000000 --- a/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.radarbase.management.aop.logging; - -import org.springframework.core.env.Profiles; -import tech.jhipster.config.JHipsterConstants; - -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.env.Environment; - -import java.util.Arrays; - -/** - * Aspect for logging execution of service and repository Spring components. - * - *

By default, it only runs with the "dev" profile.

- */ -@Aspect -public class LoggingAspect { - private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class); - - private final Environment env; - - public LoggingAspect(Environment env) { - this.env = env; - } - - /** - * Pointcut that matches all repositories, services and Web REST endpoints. - */ - @Pointcut("within(org.radarbase.management.repository..*) || " - + "within(org.radarbase.management.service..*) || " - + "within(org.radarbase.management.web.rest..*)") - public void loggingPointcut() { - // Method is empty as this is just a Pointcut, the implementations are in the advices. - } - - /** - * Advice that logs methods throwing exceptions. - * - * @param joinPoint join point for advice - * @param e exception - */ - @AfterThrowing(pointcut = "loggingPointcut()", throwing = "e") - public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { - if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { - log.error("Exception in {}.{}() with cause = '{}' and exception = '{}'", - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), - e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e); - - } else { - log.error("Exception in {}.{}() with cause = {}", - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), - e.getCause() != null ? e.getCause() : "NULL"); - } - } - - /** - * Advice that logs when a method is entered and exited. - * - * @param joinPoint join point for advice - * @return result - * @throws Throwable throws IllegalArgumentException - */ - @Around("loggingPointcut()") - public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { - if (log.isDebugEnabled()) { - log.debug("Enter: {}.{}() with argument[s] = {}", - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); - } - try { - Object result = joinPoint.proceed(); - if (log.isDebugEnabled()) { - log.debug("Exit: {}.{}() with result = {}", - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), result); - } - return result; - } catch (IllegalArgumentException e) { - log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()), - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName()); - - throw e; - } - } -} diff --git a/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.kt b/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.kt new file mode 100644 index 000000000..199321d7e --- /dev/null +++ b/src/main/java/org/radarbase/management/aop/logging/LoggingAspect.kt @@ -0,0 +1,113 @@ +package org.radarbase.management.aop.logging + +import org.aspectj.lang.JoinPoint +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.AfterThrowing +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Pointcut +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.core.env.Environment +import org.springframework.core.env.Profiles +import tech.jhipster.config.JHipsterConstants + +/** + * Aspect for logging execution of service and repository Spring components. + * + * + * By default, it only runs with the "dev" profile. + */ +@Aspect +class LoggingAspect( + private val env: Environment, +) { + /** + * Pointcut that matches all repositories, services and Web REST endpoints. + */ + @Pointcut( + ( + "within(org.radarbase.management.repository..*) || " + + "within(org.radarbase.management.service..*) || " + + "within(org.radarbase.management.web.rest..*)" + ), + ) + fun loggingPointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Advice that logs methods throwing exceptions. + * + * @param joinPoint join point for advice + * @param e exception + */ + @AfterThrowing(pointcut = "loggingPointcut()", throwing = "e") + fun logAfterThrowing( + joinPoint: JoinPoint, + e: Throwable, + ) { + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { + log.error( + "Exception in {}.{}() with cause = '{}' and exception = '{}'", + joinPoint.signature.declaringTypeName, + joinPoint.signature.name, + if (e.cause != null) e.cause else "NULL", + e.message, + e, + ) + } else { + log.error( + "Exception in {}.{}() with cause = {}", + joinPoint.signature.declaringTypeName, + joinPoint.signature.name, + if (e.cause != null) e.cause else "NULL", + ) + } + } + + /** + * Advice that logs when a method is entered and exited. + * + * @param joinPoint join point for advice + * @return result + * @throws Throwable throws IllegalArgumentException + */ + @Around("loggingPointcut()") + @Throws(Throwable::class) + fun logAround(joinPoint: ProceedingJoinPoint): Any { + if (log.isDebugEnabled) { + log.debug( + "Enter: {}.{}() with argument[s] = {}", + joinPoint.signature.declaringTypeName, + joinPoint.signature.name, + joinPoint.args.contentToString(), + ) + } + try { + val result = joinPoint.proceed() + if (log.isDebugEnabled) { + log.debug( + "Exit: {}.{}() with result = {}", + joinPoint.signature.declaringTypeName, + joinPoint.signature.name, + result, + ) + } + return result + } catch (e: IllegalArgumentException) { + log.error( + "Illegal argument: {} in {}.{}()", + joinPoint.args.contentToString(), + joinPoint.signature.declaringTypeName, + joinPoint.signature.name, + ) + + throw e + } + } + + companion object { + private val log: Logger = LoggerFactory.getLogger(LoggingAspect::class.java) + } +} diff --git a/src/main/java/org/radarbase/management/config/AsyncConfiguration.kt b/src/main/java/org/radarbase/management/config/AsyncConfiguration.kt index 2f89550b0..dbac49a1a 100644 --- a/src/main/java/org/radarbase/management/config/AsyncConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/AsyncConfiguration.kt @@ -17,7 +17,8 @@ import tech.jhipster.config.JHipsterProperties @EnableAsync @EnableScheduling class AsyncConfiguration( - @Autowired private val jHipsterProperties: JHipsterProperties) : AsyncConfigurer { + @Autowired private val jHipsterProperties: JHipsterProperties, +) : AsyncConfigurer { @Bean(name = ["taskExecutor"]) override fun getAsyncExecutor(): ExceptionHandlingAsyncTaskExecutor { log.debug("Creating Async Task Executor") @@ -29,9 +30,8 @@ class AsyncConfiguration( return ExceptionHandlingAsyncTaskExecutor(executor) } - override fun getAsyncUncaughtExceptionHandler(): AsyncUncaughtExceptionHandler { - return SimpleAsyncUncaughtExceptionHandler() - } + override fun getAsyncUncaughtExceptionHandler(): AsyncUncaughtExceptionHandler = + SimpleAsyncUncaughtExceptionHandler() companion object { private val log = LoggerFactory.getLogger(AsyncConfiguration::class.java) diff --git a/src/main/java/org/radarbase/management/config/AuthorizationConfiguration.kt b/src/main/java/org/radarbase/management/config/AuthorizationConfiguration.kt index c175df23c..339133d54 100644 --- a/src/main/java/org/radarbase/management/config/AuthorizationConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/AuthorizationConfiguration.kt @@ -14,12 +14,15 @@ class AuthorizationConfiguration( private val projectRepository: ProjectRepository, ) { @Bean - fun authorizationOracle(): AuthorizationOracle = MPAuthorizationOracle( - object : EntityRelationService { - override suspend fun findOrganizationOfProject(project: String): String? = withContext(Dispatchers.IO) { - projectRepository.findOneWithEagerRelationshipsByName(project) - ?.organizationName - } - } - ) + fun authorizationOracle(): AuthorizationOracle = + MPAuthorizationOracle( + object : EntityRelationService { + override suspend fun findOrganizationOfProject(project: String): String? = + withContext(Dispatchers.IO) { + projectRepository + .findOneWithEagerRelationshipsByName(project) + ?.organizationName + } + }, + ) } diff --git a/src/main/java/org/radarbase/management/config/CacheConfiguration.kt b/src/main/java/org/radarbase/management/config/CacheConfiguration.kt index e254d296c..ebaca1246 100644 --- a/src/main/java/org/radarbase/management/config/CacheConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/CacheConfiguration.kt @@ -30,6 +30,7 @@ import javax.annotation.PreDestroy class CacheConfiguration { @Autowired private val env: Environment? = null + @PreDestroy fun destroy() { log.info("Closing Cache Manager") @@ -40,7 +41,7 @@ class CacheConfiguration { fun HazelcastInstance.cacheManager(): CacheManager { log.debug("Starting HazelcastCacheManager") return HazelcastCacheManager( - this + this, ) } @@ -64,15 +65,18 @@ class CacheConfiguration { networkConfig.join.eurekaConfig.setEnabled(false) networkConfig.join.kubernetesConfig.setEnabled(false) } - val attributeConfig = AttributeConfig() - .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) - .setExtractorClassName(Hazelcast4PrincipalNameExtractor::class.java.getName()) - config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) - .addAttributeConfig(attributeConfig).addIndexConfig( + val attributeConfig = + AttributeConfig() + .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setExtractorClassName(Hazelcast4PrincipalNameExtractor::class.java.getName()) + config + .getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) + .addAttributeConfig(attributeConfig) + .addIndexConfig( IndexConfig( IndexType.HASH, - Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE - ) + Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, + ), ) config.mapConfigs["default"] = initializeDefaultMapConfig() config.mapConfigs["org.radarbase.management.domain.*"] = initializeDomainMapConfig(jHipsterProperties) @@ -84,25 +88,28 @@ class CacheConfiguration { /* Number of backups. If 1 is set as the backup-count for example, then all entries of the map will be copied to another JVM for - fail-safety. Valid numbers are 0 (no backup), 1, 2, 3. */mapConfig.setBackupCount(0) + fail-safety. Valid numbers are 0 (no backup), 1, 2, 3. */ + mapConfig.setBackupCount(0) /* Valid values are: NONE (no eviction), LRU (Least Recently Used), LFU (Least Frequently Used). - NONE is the default. */mapConfig.evictionConfig.setEvictionPolicy(EvictionPolicy.LRU) + NONE is the default. */ + mapConfig.evictionConfig.setEvictionPolicy(EvictionPolicy.LRU) /* Maximum size of the map. When max size is reached, map is evicted based on the policy defined. Any integer between 0 and Integer.MAX_VALUE. 0 means - Integer.MAX_VALUE. Default is 0. */mapConfig.evictionConfig.setMaxSizePolicy(MaxSizePolicy.USED_HEAP_SIZE) + Integer.MAX_VALUE. Default is 0. */ + mapConfig.evictionConfig.setMaxSizePolicy(MaxSizePolicy.USED_HEAP_SIZE) return mapConfig } private fun initializeDomainMapConfig(jHipsterProperties: JHipsterProperties): MapConfig { val mapConfig = MapConfig() mapConfig.setTimeToLiveSeconds( - jHipsterProperties.cache.hazelcast.timeToLiveSeconds + jHipsterProperties.cache.hazelcast.timeToLiveSeconds, ) return mapConfig } diff --git a/src/main/java/org/radarbase/management/config/DatabaseConfiguration.kt b/src/main/java/org/radarbase/management/config/DatabaseConfiguration.kt index 8c301a5a2..adc6b39b3 100644 --- a/src/main/java/org/radarbase/management/config/DatabaseConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/DatabaseConfiguration.kt @@ -19,19 +19,19 @@ import javax.sql.DataSource @Configuration @EnableJpaRepositories( basePackages = ["org.radarbase.management.repository"], - repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean::class + repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean::class, ) @EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") @EnableTransactionManagement class DatabaseConfiguration { @Autowired private val env: Environment? = null + @Bean fun liquibase( dataSource: DataSource, - liquibaseProperties: LiquibaseProperties + liquibaseProperties: LiquibaseProperties, ): SpringLiquibase { - // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start // asynchronously val liquibase = SpringLiquibase() @@ -50,9 +50,7 @@ class DatabaseConfiguration { } @Bean - fun hibernate5Module(): Hibernate5Module { - return Hibernate5Module() - } + fun hibernate5Module(): Hibernate5Module = Hibernate5Module() companion object { private val log = LoggerFactory.getLogger(DatabaseConfiguration::class.java) diff --git a/src/main/java/org/radarbase/management/config/DateTimeFormatConfiguration.kt b/src/main/java/org/radarbase/management/config/DateTimeFormatConfiguration.kt index 16292ac6b..9afe8c413 100644 --- a/src/main/java/org/radarbase/management/config/DateTimeFormatConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/DateTimeFormatConfiguration.kt @@ -13,7 +13,9 @@ import javax.annotation.Nonnull @Configuration class DateTimeFormatConfiguration : WebMvcConfigurer { - override fun addFormatters(@Nonnull registry: FormatterRegistry) { + override fun addFormatters( + @Nonnull registry: FormatterRegistry, + ) { val registrar = DateTimeFormatterRegistrar() registrar.setUseIsoFormat(true) registrar.setDateTimeFormatter( @@ -41,7 +43,7 @@ class DateTimeFormatConfiguration : WebMvcConfigurer { .parseDefaulting(ChronoField.NANO_OF_SECOND, ChronoField.NANO_OF_SECOND.range().minimum) .toFormatter() .withZone(ZoneId.of("UTC")) - .withResolverStyle(ResolverStyle.LENIENT) + .withResolverStyle(ResolverStyle.LENIENT), ) registrar.registerFormatters(registry) } diff --git a/src/main/java/org/radarbase/management/config/LocaleConfiguration.kt b/src/main/java/org/radarbase/management/config/LocaleConfiguration.kt index 99ac5080b..6331e719f 100644 --- a/src/main/java/org/radarbase/management/config/LocaleConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/LocaleConfiguration.kt @@ -11,7 +11,9 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor import tech.jhipster.config.locale.AngularCookieLocaleResolver @Configuration -class LocaleConfiguration : WebMvcConfigurer, EnvironmentAware { +class LocaleConfiguration : + WebMvcConfigurer, + EnvironmentAware { override fun setEnvironment(environment: Environment) { // unused } diff --git a/src/main/java/org/radarbase/management/config/LoggingAspectConfiguration.kt b/src/main/java/org/radarbase/management/config/LoggingAspectConfiguration.kt index 8e42853cf..b43a4c046 100644 --- a/src/main/java/org/radarbase/management/config/LoggingAspectConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/LoggingAspectConfiguration.kt @@ -13,7 +13,5 @@ import tech.jhipster.config.JHipsterConstants class LoggingAspectConfiguration { @Bean @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) - fun loggingAspect(env: Environment?): LoggingAspect { - return LoggingAspect(env!!) - } + fun loggingAspect(env: Environment?): LoggingAspect = LoggingAspect(env!!) } diff --git a/src/main/java/org/radarbase/management/config/LoggingConfiguration.kt b/src/main/java/org/radarbase/management/config/LoggingConfiguration.kt index 31094d692..267b5458d 100644 --- a/src/main/java/org/radarbase/management/config/LoggingConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/LoggingConfiguration.kt @@ -13,27 +13,30 @@ class LoggingConfiguration( @Value("\${spring.application.name}") appName: String, @Value("\${server.port}") serverPort: String, jHipsterProperties: JHipsterProperties, - mapper: ObjectMapper + mapper: ObjectMapper, ) { /** Logging configuration for JHipster. */ init { - val context = LoggerFactory.getILoggerFactory() as LoggerContext - val map: MutableMap = buildMap { - put("app_name", appName) - put("app_port", serverPort) - } as MutableMap + if (LoggerFactory.getILoggerFactory() is LoggerContext) { + val context = LoggerFactory.getILoggerFactory() as LoggerContext + val map: MutableMap = + buildMap { + put("app_name", appName) + put("app_port", serverPort) + } as MutableMap - val customFields = mapper.writeValueAsString(map) - val loggingProperties = jHipsterProperties.logging - val logstashProperties = loggingProperties.logstash - if (loggingProperties.isUseJsonFormat) { - LoggingUtils.addJsonConsoleAppender(context, customFields) - } - if (logstashProperties.isEnabled) { - LoggingUtils.addLogstashTcpSocketAppender(context, customFields, logstashProperties) - } - if (loggingProperties.isUseJsonFormat || logstashProperties.isEnabled) { - LoggingUtils.addContextListener(context, customFields, loggingProperties) + val customFields = mapper.writeValueAsString(map) + val loggingProperties = jHipsterProperties.logging + val logstashProperties = loggingProperties.logstash + if (loggingProperties.isUseJsonFormat) { + LoggingUtils.addJsonConsoleAppender(context, customFields) + } + if (logstashProperties.isEnabled) { + LoggingUtils.addLogstashTcpSocketAppender(context, customFields, logstashProperties) + } + if (loggingProperties.isUseJsonFormat || logstashProperties.isEnabled) { + LoggingUtils.addContextListener(context, customFields, loggingProperties) + } } } } diff --git a/src/main/java/org/radarbase/management/config/ManagementPortalProperties.java b/src/main/java/org/radarbase/management/config/ManagementPortalProperties.java deleted file mode 100644 index 520bd1d69..000000000 --- a/src/main/java/org/radarbase/management/config/ManagementPortalProperties.java +++ /dev/null @@ -1,362 +0,0 @@ -package org.radarbase.management.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.List; - -/** - * Created by nivethika on 3-10-17. - */ -@ConfigurationProperties(prefix = "managementportal", ignoreUnknownFields = false) -public class ManagementPortalProperties { - - private final IdentityServer identityServer = new IdentityServer(); - - private final Mail mail = new Mail(); - - private final Frontend frontend = new Frontend(); - - private Oauth oauth = new Oauth(); - - private final Common common = new Common(); - - private final CatalogueServer catalogueServer = new CatalogueServer(); - - private final Account account = new Account(); - - private final SiteSettings siteSettings = new SiteSettings(); - - public ManagementPortalProperties.Frontend getFrontend() { - return frontend; - } - - public IdentityServer getIdentityServer() { - return identityServer; - } - - public ManagementPortalProperties.Mail getMail() { - return mail; - } - - public ManagementPortalProperties.Oauth getOauth() { - return oauth; - } - - public void setOauth(ManagementPortalProperties.Oauth oauth) { - this.oauth = oauth; - } - - public CatalogueServer getCatalogueServer() { - return catalogueServer; - } - - public Common getCommon() { - return common; - } - - public Account getAccount() { - return account; - } - - public SiteSettings getSiteSettings() { - return siteSettings; - } - - public static class Account { - private boolean enableExposeToken = false; - - public boolean getEnableExposeToken() { - return enableExposeToken; - } - - public void setEnableExposeToken(boolean enableExposeToken) { - this.enableExposeToken = enableExposeToken; - } - } - - public static class Common { - - private String baseUrl = ""; - - private String managementPortalBaseUrl = ""; - - private String privacyPolicyUrl = ""; - - private String adminPassword = ""; - - private Integer activationKeyTimeoutInSeconds = 24 * 60 * 60; // 1 day - - public String getBaseUrl() { - return baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - public String getPrivacyPolicyUrl() { - return privacyPolicyUrl; - } - - public void setPrivacyPolicyUrl(String privacyPolicyUrl) { - this.privacyPolicyUrl = privacyPolicyUrl; - } - - public String getAdminPassword() { - return adminPassword; - } - - public void setAdminPassword(String adminPassword) { - this.adminPassword = adminPassword; - } - - public String getManagementPortalBaseUrl() { - return managementPortalBaseUrl; - } - - public void setManagementPortalBaseUrl(String managementPortalBaseUrl) { - this.managementPortalBaseUrl = managementPortalBaseUrl; - } - - public Integer getActivationKeyTimeoutInSeconds() { - return activationKeyTimeoutInSeconds; - } - - public void setActivationKeyTimeoutInSeconds(Integer activationKeyTimeoutInSeconds) { - this.activationKeyTimeoutInSeconds = activationKeyTimeoutInSeconds; - } - } - - public static class Mail { - - private String from = ""; - - public String getFrom() { - return from; - } - - public void setFrom(String from) { - this.from = from; - } - - } - - public static class Frontend { - - private String clientId = ""; - - private String clientSecret = ""; - - private Integer accessTokenValiditySeconds = 4 * 60 * 60; - - private Integer refreshTokenValiditySeconds = 72 * 60 * 60; - - private Integer sessionTimeout = 24 * 60 * 60; // a day - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public Integer getSessionTimeout() { - return sessionTimeout; - } - - public void setSessionTimeout(Integer sessionTimeout) { - this.sessionTimeout = sessionTimeout; - } - - public Integer getAccessTokenValiditySeconds() { - return accessTokenValiditySeconds; - } - - public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) { - this.accessTokenValiditySeconds = accessTokenValiditySeconds; - } - - public Integer getRefreshTokenValiditySeconds() { - return refreshTokenValiditySeconds; - } - - public void setRefreshTokenValiditySeconds(Integer refreshTokenValiditySeconds) { - this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; - } - } - - public static class Oauth { - - private Boolean requireAal2 = false; - - private String clientsFile; - - private String signingKeyAlias; - - private List checkingKeyAliases; - - private String keyStorePassword; - - private String metaTokenTimeout; - - private String persistentMetaTokenTimeout; - - private Boolean enablePublicKeyVerifiers = false; - - public String getClientsFile() { - return clientsFile; - } - - public void setClientsFile(String clientsFile) { - this.clientsFile = clientsFile; - } - - public String getSigningKeyAlias() { - return signingKeyAlias; - } - - public void setSigningKeyAlias(String signingKeyAlias) { - this.signingKeyAlias = signingKeyAlias; - } - - public List getCheckingKeyAliases() { - return checkingKeyAliases; - } - - public void setCheckingKeyAliases(List checkingKeyAliases) { - this.checkingKeyAliases = checkingKeyAliases; - } - - public String getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getMetaTokenTimeout() { - return metaTokenTimeout; - } - - public void setMetaTokenTimeout(String metaTokenTimeout) { - this.metaTokenTimeout = metaTokenTimeout; - } - - public String getPersistentMetaTokenTimeout() { - return persistentMetaTokenTimeout; - } - - public void setPersistentMetaTokenTimeout(String persistentMetaTokenTimeout) { - this.persistentMetaTokenTimeout = persistentMetaTokenTimeout; - } - - public Boolean getEnablePublicKeyVerifiers() { - return enablePublicKeyVerifiers; - } - - public void setEnablePublicKeyVerifiers(Boolean enablePublicKeyVerifiers) { - this.enablePublicKeyVerifiers = enablePublicKeyVerifiers; - } - - public Boolean getRequireAal2() { - return requireAal2; - } - - public void setRequireAal2(Boolean requireAal2) { - this.requireAal2 = requireAal2; - } - } - - public static class IdentityServer { - private String serverUrl = null; - private String serverAdminUrl = null; - private String adminEmail = null; - private String loginUrl = null; - - public String getServerUrl() { - return serverUrl; - } - - public void setServerUrl(String serverUrl) { - this.serverUrl = serverUrl; - } - - public String publicUrl() { - return serverUrl; - } - - public String adminUrl() { - return serverAdminUrl; - } - - public String getAdminEmail() { - return adminEmail; - } - - public void setAdminEmail(String adminEmail) { - this.adminEmail = adminEmail; - } - - public String getServerAdminUrl() { - return serverAdminUrl; - } - - public void setServerAdminUrl(String serverAdminUrl) { - this.serverAdminUrl = serverAdminUrl; - } - - public String getLoginUrl() { - return loginUrl; - } - - public void setLoginUrl(String loginUrl) { - this.loginUrl = loginUrl; - } - } - - public static class CatalogueServer { - - private boolean enableAutoImport = false; - - private String serverUrl; - - public String getServerUrl() { - return serverUrl; - } - - public void setServerUrl(String serverUrl) { - this.serverUrl = serverUrl; - } - - public boolean isEnableAutoImport() { - return enableAutoImport; - } - - public void setEnableAutoImport(boolean enableAutoImport) { - this.enableAutoImport = enableAutoImport; - } - } - - public static class SiteSettings { - - private List hiddenSubjectFields; - - public void setHiddenSubjectFields(List hiddenSubjectFields) { - this.hiddenSubjectFields = hiddenSubjectFields; - } - - public List getHiddenSubjectFields() { - return hiddenSubjectFields; - } - } -} diff --git a/src/main/java/org/radarbase/management/config/ManagementPortalProperties.kt b/src/main/java/org/radarbase/management/config/ManagementPortalProperties.kt new file mode 100644 index 000000000..9371fd715 --- /dev/null +++ b/src/main/java/org/radarbase/management/config/ManagementPortalProperties.kt @@ -0,0 +1,96 @@ +package org.radarbase.management.config + +import org.springframework.boot.context.properties.ConfigurationProperties + +/** + * Created by nivethika on 3-10-17. + */ +@ConfigurationProperties(prefix = "managementportal", ignoreUnknownFields = false) +class ManagementPortalProperties { + val identityServer: IdentityServer = IdentityServer() + + val mail: Mail = Mail() + + val frontend: Frontend = Frontend() + + var oauth: Oauth = Oauth() + + val common: Common = Common() + + val catalogueServer: CatalogueServer = CatalogueServer() + + val account: Account = Account() + + val siteSettings: SiteSettings = SiteSettings() + + class Account { + var enableExposeToken: Boolean = false + } + + class Common { + var baseUrl: String = "" + + var managementPortalBaseUrl: String = "" + + var privacyPolicyUrl: String = "" + + var adminPassword: String = "" + + var activationKeyTimeoutInSeconds: Int = 24 * 60 * 60 // 1 day + } + + class Mail { + var from: String = "" + } + + class Frontend { + var clientId: String = "" + + var clientSecret: String = "" + + var accessTokenValiditySeconds: Int = 4 * 60 * 60 + + var refreshTokenValiditySeconds: Int = 72 * 60 * 60 + + var sessionTimeout: Int = 24 * 60 * 60 // a day + } + + class Oauth { + var requireAal2: Boolean = false + + var clientsFile: String? = null + + var signingKeyAlias: String? = null + + var checkingKeyAliases: List? = null + + var keyStorePassword: String? = null + + var metaTokenTimeout: String? = null + + var persistentMetaTokenTimeout: String? = null + + var enablePublicKeyVerifiers: Boolean = false + } + + class IdentityServer { + var serverUrl: String? = null + var serverAdminUrl: String? = null + var adminEmail: String? = null + var loginUrl: String? = null + + fun publicUrl(): String? = serverUrl + + fun adminUrl(): String? = serverAdminUrl + } + + class CatalogueServer { + var isEnableAutoImport: Boolean = false + + var serverUrl: String? = null + } + + class SiteSettings { + var hiddenSubjectFields: List? = null + } +} diff --git a/src/main/java/org/radarbase/management/config/ManagementPortalSecurityConfigLoader.kt b/src/main/java/org/radarbase/management/config/ManagementPortalSecurityConfigLoader.kt index d0cd9e348..5bb87e3c6 100644 --- a/src/main/java/org/radarbase/management/config/ManagementPortalSecurityConfigLoader.kt +++ b/src/main/java/org/radarbase/management/config/ManagementPortalSecurityConfigLoader.kt @@ -65,17 +65,19 @@ class ManagementPortalSecurityConfigLoader { @Transactional fun createAdminIdentity() { try { - if (!isAdminIdCreated && managementPortalProperties?.identityServer?.serverUrl != null && managementPortalProperties.identityServer.adminEmail != null) { + if (!isAdminIdCreated && + managementPortalProperties?.identityServer?.serverUrl != null && + managementPortalProperties.identityServer.adminEmail != null + ) { logger.info("Overriding admin email to ${managementPortalProperties.identityServer.adminEmail}") val dto: UserDTO = - runBlocking { userService!!.addAdminEmail(managementPortalProperties.identityServer.adminEmail) } + runBlocking { userService!!.addAdminEmail(managementPortalProperties.identityServer.adminEmail!!) } runBlocking { userService?.updateUser(dto) } isAdminIdCreated = true } else if (!isAdminIdCreated) { logger.warn("AdminEmail property is left empty, thus no admin identity could be created.") } - } - catch (e: Throwable){ + } catch (e: Throwable) { logger.error("could not update/create admin identity. This may result in an unstable state", e) } } @@ -94,15 +96,18 @@ class ManagementPortalSecurityConfigLoader { details.refreshTokenValiditySeconds = frontend.refreshTokenValiditySeconds details.setResourceIds( listOf( - "res_ManagementPortal", "res_appconfig", "res_upload", - "res_restAuthorizer" - ) + "res_ManagementPortal", + "res_appconfig", + "res_upload", + "res_restAuthorizer", + ), ) details.setAuthorizedGrantTypes( mutableListOf( - "password", "refresh_token", - "authorization_code" - ) + "password", + "refresh_token", + "authorization_code", + ), ) details.setAdditionalInformation(Collections.singletonMap("protected", true)) val allScopes = listOf(*scopes()) @@ -128,14 +133,17 @@ class ManagementPortalSecurityConfigLoader { // column order val columnOrder = getCsvFileColumnOrder(file) ?: return val mapper = CsvMapper() - val schema = mapper.schemaFor(CustomBaseClientDetails::class.java) - .withColumnReordering(true) - .sortedBy(*columnOrder) - .withColumnSeparator(SEPARATOR) - .withHeader() - val reader = mapper - .readerFor(CustomBaseClientDetails::class.java) - .with(schema) + val schema = + mapper + .schemaFor(CustomBaseClientDetails::class.java) + .withColumnReordering(true) + .sortedBy(*columnOrder) + .withColumnSeparator(SEPARATOR) + .withHeader() + val reader = + mapper + .readerFor(CustomBaseClientDetails::class.java) + .with(schema) try { Files.newInputStream(file).use { inputStream -> reader.readValues(inputStream).use { iterator -> @@ -167,8 +175,9 @@ class ManagementPortalSecurityConfigLoader { logger.info("OAuth client loaded: " + details.clientId) } catch (ex: Exception) { logger.error( - "Unable to load OAuth client " + details.clientId + ": " - + ex.message, ex + "Unable to load OAuth client " + details.clientId + ": " + + ex.message, + ex, ) } } @@ -176,7 +185,10 @@ class ManagementPortalSecurityConfigLoader { private fun getCsvFileColumnOrder(csvFile: Path): Array? { try { Files.newBufferedReader(csvFile).use { bufferedReader -> - return bufferedReader.readLine().split(SEPARATOR.toString().toRegex()).dropLastWhile { it.isEmpty() } + return bufferedReader + .readLine() + .split(SEPARATOR.toString().toRegex()) + .dropLastWhile { it.isEmpty() } .toTypedArray() } } catch (ex: Exception) { @@ -193,9 +205,8 @@ class ManagementPortalSecurityConfigLoader { private class CustomBaseClientDetails : BaseClientDetails() { @JsonProperty("additional_information") private var additionalInformation: Map = LinkedHashMap() - override fun getAdditionalInformation(): Map { - return additionalInformation - } + + override fun getAdditionalInformation(): Map = additionalInformation @JsonSetter("additional_information") fun setAdditionalInformation(additionalInformation: String) { @@ -205,12 +216,16 @@ class ManagementPortalSecurityConfigLoader { } val mapper = ObjectMapper() try { - this.additionalInformation = mapper.readValue>(additionalInformation, - object : TypeReference>() {}) + this.additionalInformation = + mapper.readValue>( + additionalInformation, + object : TypeReference>() {}, + ) } catch (ex: Exception) { logger.error( - "Unable to parse additional_information field for client " - + clientId + ": " + ex.message, ex + "Unable to parse additional_information field for client " + + clientId + ": " + ex.message, + ex, ) } } diff --git a/src/main/java/org/radarbase/management/config/OAuth2LoginUiWebConfig.kt b/src/main/java/org/radarbase/management/config/OAuth2LoginUiWebConfig.kt index fd88d3162..7b14fe2b9 100644 --- a/src/main/java/org/radarbase/management/config/OAuth2LoginUiWebConfig.kt +++ b/src/main/java/org/radarbase/management/config/OAuth2LoginUiWebConfig.kt @@ -44,15 +44,21 @@ import kotlin.collections.set @SessionAttributes("authorizationRequest") class OAuth2LoginUiWebConfig( @Autowired private val tokenEndPoint: TokenEndpoint, - @Autowired private val managementPortalProperties: ManagementPortalProperties + @Autowired private val managementPortalProperties: ManagementPortalProperties, ) { - @Autowired private val clientDetailsService: ClientDetailsService? = null @RequestMapping("/oauth2/authorize") - fun redirect_authorize(request: HttpServletRequest): String { - val returnString = URLEncoder.encode(request.requestURL.toString().replace("oauth2", "oauth") + "?" + request.parameterMap.map{ param -> param.key + "=" + param.value.first()}.joinToString("&"), "UTF-8") + fun redirectAuthorize(request: HttpServletRequest): String { + val returnString = + URLEncoder.encode( + request.requestURL.toString().replace( + "oauth2", + "oauth", + ) + "?" + request.parameterMap.map { param -> param.key + "=" + param.value.first() }.joinToString("&"), + "UTF-8", + ) val mpUrl = managementPortalProperties.common.baseUrl return "redirect:$mpUrl/kratos-ui/login?return_to=$returnString" } @@ -60,35 +66,44 @@ class OAuth2LoginUiWebConfig( @PostMapping( "/oauth2/token", consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], - produces = [MediaType.APPLICATION_FORM_URLENCODED_VALUE] + produces = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], ) - fun redirect_token(@RequestParam parameters: Map, request: HttpServletRequest, response: HttpServletResponse) { - var dispatcher: RequestDispatcher = request.servletContext.getRequestDispatcher("/oauth/token/") + fun redirectToken( + @RequestParam parameters: Map, + request: HttpServletRequest, + response: HttpServletResponse, + ) { + var dispatcher: RequestDispatcher = request.servletContext.getRequestDispatcher("/oauth/token/") dispatcher.forward(request, response) } - @PostMapping(value = ["/oauth/token"], - consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE] + @PostMapping( + value = ["/oauth/token"], + consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE], ) @Throws( - HttpRequestMethodNotSupportedException::class + HttpRequestMethodNotSupportedException::class, ) - fun postAccessToken(@RequestParam parameters: Map, principal: Principal?): - ResponseEntity { + fun postAccessToken( + @RequestParam parameters: Map, + principal: Principal?, + ): ResponseEntity? { if (principal !is Authentication) { throw InsufficientAuthenticationException( - "There is no client authentication. Try adding an appropriate authentication filter." + "There is no client authentication. Try adding an appropriate authentication filter.", ) } - val grant_type = parameters.get("grant_type") - logger.debug("Token request of grant type $grant_type received") + val grantType = parameters.get("grant_type") + logger.debug("Token request of grant type $grantType received") val clientId: String = parameters.get("client_id") ?: principal.name - var radarPrincipal = RadarPrincipal(clientId, principal) + val radarPrincipal = RadarPrincipal(clientId, principal) + val accessToken = + this.tokenEndPoint.postAccessToken(radarPrincipal, parameters)?.body + ?: throw RuntimeException("Token endpoint did not return a token") - val token2 = this.tokenEndPoint.postAccessToken(radarPrincipal, parameters) - return getResponse(token2.body) + return getResponse(accessToken) } fun getResponse(accessToken: OAuth2AccessToken): ResponseEntity { @@ -106,7 +121,10 @@ class OAuth2LoginUiWebConfig( * @return a ModelAndView to render the form */ @RequestMapping("/login") - fun getLogin(request: HttpServletRequest, response: HttpServletResponse?): ModelAndView { + fun getLogin( + request: HttpServletRequest, + response: HttpServletResponse?, + ): ModelAndView { val model = TreeMap() if (request.parameterMap.containsKey("error")) { model["loginError"] = true @@ -123,22 +141,28 @@ class OAuth2LoginUiWebConfig( @RequestMapping("/oauth/confirm_access") fun getAccessConfirmation( request: HttpServletRequest, - response: HttpServletResponse? + response: HttpServletResponse?, ): ModelAndView { val params = request.parameterMap - val authorizationParameters = Stream.of( - OAuth2Utils.CLIENT_ID, OAuth2Utils.REDIRECT_URI, OAuth2Utils.STATE, - OAuth2Utils.SCOPE, OAuth2Utils.RESPONSE_TYPE - ) - .filter { key: String -> params.containsKey(key) } - .collect(Collectors.toMap(Function.identity(), Function { p: String -> params[p]!![0] })) - val authorizationRequest = DefaultOAuth2RequestFactory( - clientDetailsService - ).createAuthorizationRequest(authorizationParameters) - val model = Collections.singletonMap( - "authorizationRequest", - authorizationRequest - ) + val authorizationParameters = + Stream + .of( + OAuth2Utils.CLIENT_ID, + OAuth2Utils.REDIRECT_URI, + OAuth2Utils.STATE, + OAuth2Utils.SCOPE, + OAuth2Utils.RESPONSE_TYPE, + ).filter { key: String -> params.containsKey(key) } + .collect(Collectors.toMap(Function.identity(), Function { p: String -> params[p]!![0] })) + val authorizationRequest = + DefaultOAuth2RequestFactory( + clientDetailsService, + ).createAuthorizationRequest(authorizationParameters) + val model = + Collections.singletonMap( + "authorizationRequest", + authorizationRequest, + ) return ModelAndView("authorize", model) } @@ -154,8 +178,9 @@ class OAuth2LoginUiWebConfig( // The error summary may contain malicious user input, // it needs to be escaped to prevent XSS val errorParams: MutableMap = HashMap() - errorParams["date"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) - .format(Date()) + errorParams["date"] = + SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) + .format(Date()) if (error is OAuth2Exception) { val oauthError = error errorParams["status"] = String.format("%d", oauthError.httpErrorCode) @@ -163,9 +188,10 @@ class OAuth2LoginUiWebConfig( errorParams["message"] = oauthError.message?.let { HtmlUtils.htmlEscape(it) } ?: "No error message found" // transform the additionalInfo map to a comma seperated list of key: value pairs if (oauthError.additionalInformation != null) { - errorParams["additionalInfo"] = HtmlUtils.htmlEscape( - oauthError.additionalInformation.entries.joinToString(", ") { entry -> entry.key + ": " + entry.value } - ) + errorParams["additionalInfo"] = + HtmlUtils.htmlEscape( + oauthError.additionalInformation.entries.joinToString(", ") { entry -> entry.key + ": " + entry.value }, + ) } } // Copy non-empty entries to the model. Empty entries will not be present in the model, @@ -178,41 +204,32 @@ class OAuth2LoginUiWebConfig( return ModelAndView("error", model) } - private class RadarPrincipal(private val name: String, private val auth: Authentication) : Principal, Authentication { + private class RadarPrincipal( + private val name: String, + private val auth: Authentication, + ) : Principal, + Authentication { + override fun getName(): String = name - override fun getName(): String { - return name - } + override fun getAuthorities(): MutableCollection = auth.authorities - override fun getAuthorities(): MutableCollection { - return auth.authorities - } + override fun getCredentials(): Any = auth.credentials - override fun getCredentials(): Any { - return auth.credentials - } + override fun getDetails(): Any = auth.details - override fun getDetails(): Any { - return auth.details - } + override fun getPrincipal(): Any = this - override fun getPrincipal(): Any { - return this - } - - override fun isAuthenticated(): Boolean { - return auth.isAuthenticated - } + override fun isAuthenticated(): Boolean = auth.isAuthenticated - override fun setAuthenticated(isAuthenticated: Boolean) { - auth.isAuthenticated = isAuthenticated + override fun setAuthenticated(isAuthenticated: Boolean) { + auth.isAuthenticated = isAuthenticated + } } - } - companion object { - private val logger = LoggerFactory.getLogger( - OAuth2LoginUiWebConfig::class.java - ) + private val logger = + LoggerFactory.getLogger( + OAuth2LoginUiWebConfig::class.java, + ) } } diff --git a/src/main/java/org/radarbase/management/config/OAuth2ServerConfiguration.kt b/src/main/java/org/radarbase/management/config/OAuth2ServerConfiguration.kt index 2d1238e2a..0cbd99e66 100644 --- a/src/main/java/org/radarbase/management/config/OAuth2ServerConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/OAuth2ServerConfiguration.kt @@ -1,7 +1,5 @@ package org.radarbase.management.config -import java.util.* -import javax.sql.DataSource import org.radarbase.auth.authorization.RoleAuthority import org.radarbase.management.repository.UserRepository import org.radarbase.management.security.ClaimsTokenEnhancer @@ -49,36 +47,40 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancerChain import org.springframework.security.oauth2.provider.token.TokenStore import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.logout.LogoutSuccessHandler +import java.util.* +import javax.sql.DataSource @Configuration class OAuth2ServerConfiguration( @Autowired private val dataSource: DataSource, - @Autowired private val passwordEncoder: PasswordEncoder + @Autowired private val passwordEncoder: PasswordEncoder, ) { - @Configuration @Order(-20) protected class LoginConfig( @Autowired private val authenticationManager: AuthenticationManager, - @Autowired private val jwtAuthenticationFilter: JwtAuthenticationFilter + @Autowired private val jwtAuthenticationFilter: JwtAuthenticationFilter, ) : WebSecurityConfigurerAdapter() { - @Throws(Exception::class) override fun configure(http: HttpSecurity) { http - .formLogin().loginPage("/login").permitAll() + .formLogin() + .loginPage("/login") + .permitAll() .and() .authorizeRequests() - .antMatchers("/oauth/token").permitAll() + .antMatchers("/oauth/token") + .permitAll() .and() .addFilterAfter( jwtAuthenticationFilter, - UsernamePasswordAuthenticationFilter::class.java - ) - .requestMatchers() + UsernamePasswordAuthenticationFilter::class.java, + ).requestMatchers() .antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access") .and() - .authorizeRequests().anyRequest().authenticated() + .authorizeRequests() + .anyRequest() + .authenticated() } @Throws(Exception::class) @@ -91,17 +93,16 @@ class OAuth2ServerConfiguration( class JwtAuthenticationFilterConfiguration( @Autowired private val authenticationManager: AuthenticationManager, @Autowired private val userRepository: UserRepository, - @Autowired private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler + @Autowired private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler, ) { @Bean - fun jwtAuthenticationFilter(): JwtAuthenticationFilter { - return JwtAuthenticationFilter( + fun jwtAuthenticationFilter(): JwtAuthenticationFilter = + JwtAuthenticationFilter( keyStoreHandler.tokenValidator, authenticationManager, userRepository, - true + true, ) - } } @Bean @@ -119,14 +120,14 @@ class OAuth2ServerConfiguration( @Autowired private val http401UnauthorizedEntryPoint: Http401UnauthorizedEntryPoint, @Autowired private val logoutSuccessHandler: LogoutSuccessHandler, @Autowired private val authenticationManager: AuthenticationManager, - @Autowired private val userRepository: UserRepository + @Autowired private val userRepository: UserRepository, ) : ResourceServerConfigurerAdapter() { - - fun jwtAuthenticationFilter(): JwtAuthenticationFilter { - return JwtAuthenticationFilter( - keyStoreHandler.tokenValidator, authenticationManager, userRepository - ) - .skipUrlPattern(HttpMethod.GET, "/management/health") + fun jwtAuthenticationFilter(): JwtAuthenticationFilter = + JwtAuthenticationFilter( + keyStoreHandler.tokenValidator, + authenticationManager, + userRepository, + ).skipUrlPattern(HttpMethod.GET, "/management/health") .skipUrlPattern(HttpMethod.POST, "/oauth/token") .skipUrlPattern(HttpMethod.GET, "/api/meta-token/*") .skipUrlPattern(HttpMethod.GET, "/api/public/projects") @@ -138,7 +139,6 @@ class OAuth2ServerConfiguration( .skipUrlPattern(HttpMethod.GET, "/css/**") .skipUrlPattern(HttpMethod.GET, "/js/**") .skipUrlPattern(HttpMethod.GET, "/radar-baseRR.png") - } @Throws(Exception::class) override fun configure(http: HttpSecurity) { @@ -148,12 +148,13 @@ class OAuth2ServerConfiguration( .and() .addFilterBefore( jwtAuthenticationFilter(), - UsernamePasswordAuthenticationFilter::class.java - ) - .authorizeRequests() - .antMatchers("/oauth/**").permitAll() + UsernamePasswordAuthenticationFilter::class.java, + ).authorizeRequests() + .antMatchers("/oauth/**") + .permitAll() .and() - .logout().invalidateHttpSession(true) + .logout() + .invalidateHttpSession(true) .logoutUrl("/api/logout") .logoutSuccessHandler(logoutSuccessHandler) .and() @@ -166,32 +167,41 @@ class OAuth2ServerConfiguration( .and() .addFilterBefore( jwtAuthenticationFilter(), - UsernamePasswordAuthenticationFilter::class.java - ) - .authorizeRequests() - .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() + UsernamePasswordAuthenticationFilter::class.java, + ).authorizeRequests() + .antMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() .antMatchers("/api/register") .hasAnyAuthority(RoleAuthority.SYS_ADMIN_AUTHORITY) - .antMatchers("/api/profile-info").permitAll() - .antMatchers("/api/sitesettings").permitAll() - .antMatchers("/api/public/projects").permitAll() - .antMatchers("/api/logout-url").permitAll() + .antMatchers("/api/profile-info") + .permitAll() + .antMatchers("/api/sitesettings") + .permitAll() + .antMatchers("/api/public/projects") + .permitAll() + .antMatchers("/api/logout-url") + .permitAll() .antMatchers("/api/**") .authenticated() // Allow management/health endpoint to all to allow kubernetes to be able to // detect the health of the service - .antMatchers("/oauth/token").permitAll() - .antMatchers("/management/health").permitAll() + .antMatchers("/oauth/token") + .permitAll() + .antMatchers("/management/health") + .permitAll() .antMatchers("/management/**") .hasAnyAuthority(RoleAuthority.SYS_ADMIN_AUTHORITY) - .antMatchers("/v2/api-docs/**").permitAll() - .antMatchers("/swagger-resources/configuration/ui").permitAll() + .antMatchers("/v2/api-docs/**") + .permitAll() + .antMatchers("/swagger-resources/configuration/ui") + .permitAll() .antMatchers("/swagger-ui/index.html") .hasAnyAuthority(RoleAuthority.SYS_ADMIN_AUTHORITY) } @Throws(Exception::class) override fun configure(resources: ResourceServerSecurityConfigurer) { - resources.resourceId("res_ManagementPortal") + resources + .resourceId("res_ManagementPortal") .tokenStore(tokenStore) .eventPublisher(CustomEventPublisher()) } @@ -212,33 +222,25 @@ class OAuth2ServerConfiguration( @Autowired @Qualifier("authenticationManagerBean") private val authenticationManager: AuthenticationManager, @Autowired private val dataSource: DataSource, @Autowired private val jdbcClientDetailsService: JdbcClientDetailsService, - @Autowired private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler + @Autowired private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler, ) : AuthorizationServerConfigurerAdapter() { - @Bean - protected fun authorizationCodeServices(): AuthorizationCodeServices { - return JdbcAuthorizationCodeServices(dataSource) - } + protected fun authorizationCodeServices(): AuthorizationCodeServices = JdbcAuthorizationCodeServices(dataSource) @Bean - fun approvalStore(): ApprovalStore { - return if (jpaProperties.database == Database.POSTGRESQL) { + fun approvalStore(): ApprovalStore = + if (jpaProperties.database == Database.POSTGRESQL) { PostgresApprovalStore(dataSource) } else { // to have compatibility for other databases including H2 JdbcApprovalStore(dataSource) } - } @Bean - fun tokenEnhancer(): TokenEnhancer { - return ClaimsTokenEnhancer() - } + fun tokenEnhancer(): TokenEnhancer = ClaimsTokenEnhancer() @Bean - fun tokenStore(): TokenStore { - return ManagementPortalJwtTokenStore(accessTokenConverter()) - } + fun tokenStore(): TokenStore = ManagementPortalJwtTokenStore(accessTokenConverter()) @Bean fun accessTokenConverter(): ManagementPortalJwtAccessTokenConverter { @@ -246,7 +248,7 @@ class OAuth2ServerConfiguration( return ManagementPortalJwtAccessTokenConverter( keyStoreHandler.algorithmForSigning, keyStoreHandler.verifiers, - keyStoreHandler.refreshTokenVerifiers + keyStoreHandler.refreshTokenVerifiers, ) } @@ -263,7 +265,7 @@ class OAuth2ServerConfiguration( override fun configure(endpoints: AuthorizationServerEndpointsConfigurer) { val tokenEnhancerChain = TokenEnhancerChain() tokenEnhancerChain.setTokenEnhancers( - listOf(tokenEnhancer(), accessTokenConverter()) + listOf(tokenEnhancer(), accessTokenConverter()), ) endpoints .authorizationCodeServices(authorizationCodeServices()) @@ -275,7 +277,8 @@ class OAuth2ServerConfiguration( } override fun configure(oauthServer: AuthorizationServerSecurityConfigurer) { - oauthServer.allowFormAuthenticationForClients() + oauthServer + .allowFormAuthenticationForClients() .checkTokenAccess("isAuthenticated()") .tokenKeyAccess("permitAll()") .passwordEncoder(BCryptPasswordEncoder()) diff --git a/src/main/java/org/radarbase/management/config/OpenApiConfiguration.kt b/src/main/java/org/radarbase/management/config/OpenApiConfiguration.kt index 78c988cc5..09fecc592 100644 --- a/src/main/java/org/radarbase/management/config/OpenApiConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/OpenApiConfiguration.kt @@ -24,36 +24,34 @@ import tech.jhipster.config.JHipsterConstants @Profile(JHipsterConstants.SPRING_PROFILE_API_DOCS) class OpenApiConfiguration { @Bean - fun customOpenAPI(): OpenAPI { - return OpenAPI() + fun customOpenAPI(): OpenAPI = + OpenAPI() .components( Components() .addSecuritySchemes( - "oauth2Login", SecurityScheme() + "oauth2Login", + SecurityScheme() .type(SecurityScheme.Type.OAUTH2) .flows( OAuthFlows() .authorizationCode( OAuthFlow() .authorizationUrl("/oauth/authorize") - .tokenUrl("/oauth/token") - ) - .clientCredentials( + .tokenUrl("/oauth/token"), + ).clientCredentials( OAuthFlow() - .tokenUrl("/oauth/token") - ) - ) - ) - ) - .info( + .tokenUrl("/oauth/token"), + ), + ), + ), + ).info( Info() .title("ManagementPortal API") .description("ManagementPortal for RADAR-base") .license( License() .name("Apache 2.0") - .url("https://radar-base.org") - ) + .url("https://radar-base.org"), + ), ) - } } diff --git a/src/main/java/org/radarbase/management/config/package-info.kt b/src/main/java/org/radarbase/management/config/PackageInfo.kt similarity index 64% rename from src/main/java/org/radarbase/management/config/package-info.kt rename to src/main/java/org/radarbase/management/config/PackageInfo.kt index dd8ac5d6b..0eb99948a 100644 --- a/src/main/java/org/radarbase/management/config/package-info.kt +++ b/src/main/java/org/radarbase/management/config/PackageInfo.kt @@ -1,4 +1,6 @@ /** * Spring Framework configuration files. */ +@file:Suppress("ktlint:standard:no-empty-file") + package org.radarbase.management.config diff --git a/src/main/java/org/radarbase/management/config/RadarTokenConfiguration.kt b/src/main/java/org/radarbase/management/config/RadarTokenConfiguration.kt index deb2e5484..eccd684f0 100644 --- a/src/main/java/org/radarbase/management/config/RadarTokenConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/RadarTokenConfiguration.kt @@ -10,10 +10,10 @@ import org.springframework.context.annotation.ScopedProxyMode import javax.servlet.http.HttpServletRequest @Configuration -class RadarTokenConfiguration @Autowired constructor(private val radarTokenLoader: RadarTokenLoader) { +class RadarTokenConfiguration( + @Autowired private val radarTokenLoader: RadarTokenLoader, +) { @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) @Bean - fun radarToken(request: HttpServletRequest?): RadarToken? { - return radarTokenLoader.loadToken(request!!) - } + fun radarToken(request: HttpServletRequest?): RadarToken? = radarTokenLoader.loadToken(request!!) } diff --git a/src/main/java/org/radarbase/management/config/SecurityConfiguration.kt b/src/main/java/org/radarbase/management/config/SecurityConfiguration.kt index 311c1badb..3969410bf 100644 --- a/src/main/java/org/radarbase/management/config/SecurityConfiguration.kt +++ b/src/main/java/org/radarbase/management/config/SecurityConfiguration.kt @@ -24,15 +24,15 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl import tech.jhipster.security.AjaxLogoutSuccessHandler import javax.annotation.PostConstruct +/** Security configuration constructor. */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -class SecurityConfiguration -/** Security configuration constructor. */ @Autowired constructor( - private val authenticationManagerBuilder: AuthenticationManagerBuilder, - private val userDetailsService: UserDetailsService, - private val applicationEventPublisher: ApplicationEventPublisher, - private val passwordEncoder: PasswordEncoder +class SecurityConfiguration( + @Autowired private val authenticationManagerBuilder: AuthenticationManagerBuilder, + @Autowired private val userDetailsService: UserDetailsService, + @Autowired private val applicationEventPublisher: ApplicationEventPublisher, + @Autowired private val passwordEncoder: PasswordEncoder, ) : WebSecurityConfigurerAdapter() { @PostConstruct fun init() { @@ -43,7 +43,7 @@ class SecurityConfiguration .and() .authenticationProvider(RadarAuthenticationProvider()) .authenticationEventPublisher( - DefaultAuthenticationEventPublisher(applicationEventPublisher) + DefaultAuthenticationEventPublisher(applicationEventPublisher), ) } catch (e: Exception) { throw BeanInitializationException("Security configuration failed", e) @@ -51,17 +51,14 @@ class SecurityConfiguration } @Bean - fun logoutSuccessHandler(): LogoutSuccessHandler { - return AjaxLogoutSuccessHandler() - } + fun logoutSuccessHandler(): LogoutSuccessHandler = AjaxLogoutSuccessHandler() @Bean - fun http401UnauthorizedEntryPoint(): Http401UnauthorizedEntryPoint { - return Http401UnauthorizedEntryPoint() - } + fun http401UnauthorizedEntryPoint(): Http401UnauthorizedEntryPoint = Http401UnauthorizedEntryPoint() override fun configure(web: WebSecurity) { - web.ignoring() + web + .ignoring() .antMatchers("/") .antMatchers("/*.{js,ico,css,html}") .antMatchers(HttpMethod.OPTIONS, "/**") @@ -88,7 +85,8 @@ class SecurityConfiguration @Throws(Exception::class) public override fun configure(http: HttpSecurity) { http - .httpBasic().realmName("ManagementPortal") + .httpBasic() + .realmName("ManagementPortal") .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) @@ -96,12 +94,8 @@ class SecurityConfiguration @Bean @Throws(Exception::class) - override fun authenticationManagerBean(): AuthenticationManager { - return super.authenticationManagerBean() - } + override fun authenticationManagerBean(): AuthenticationManager = super.authenticationManagerBean() @Bean - fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension { - return SecurityEvaluationContextExtension() - } + fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension = SecurityEvaluationContextExtension() } diff --git a/src/main/java/org/radarbase/management/config/SourceTypeLoader.kt b/src/main/java/org/radarbase/management/config/SourceTypeLoader.kt index e456f9e95..433794e89 100644 --- a/src/main/java/org/radarbase/management/config/SourceTypeLoader.kt +++ b/src/main/java/org/radarbase/management/config/SourceTypeLoader.kt @@ -22,17 +22,21 @@ class SourceTypeLoader : CommandLineRunner { @Autowired private val managementPortalProperties: ManagementPortalProperties? = null + override fun run(vararg args: String) { if (!managementPortalProperties!!.catalogueServer.isEnableAutoImport) { log.info("Auto source-type import is disabled") return } - val catalogServerUrl = managementPortalProperties.catalogueServer.serverUrl + val catalogServerUrl = + managementPortalProperties.catalogueServer.serverUrl + ?: throw IllegalStateException("Catalog server URL is not set") try { val restTemplate = RestTemplate() log.debug("Requesting source-types from catalog server...") - val catalogues = restTemplate - .getForEntity(catalogServerUrl, SourceTypeResponse::class.java) + val catalogues = + restTemplate + .getForEntity(catalogServerUrl, SourceTypeResponse::class.java) val catalogueDto = catalogues.body if (catalogueDto == null) { log.warn("Catalog Service {} returned empty response", catalogServerUrl) @@ -46,20 +50,26 @@ class SourceTypeLoader : CommandLineRunner { sourceTypeService!!.saveSourceTypesFromCatalogServer(catalogSourceTypes) } catch (e: RestClientException) { log.warn( - "Cannot fetch source types from Catalog Service at {}: {}", catalogServerUrl, - e.toString() + "Cannot fetch source types from Catalog Service at {}: {}", + catalogServerUrl, + e.toString(), ) } catch (exe: RuntimeException) { log.warn( - "An error has occurred during auto import of source-types: {}", exe - .message + "An error has occurred during auto import of source-types: {}", + exe + .message, ) } } companion object { private val log = LoggerFactory.getLogger(SourceTypeLoader::class.java) - private fun addNonNull(collection: MutableCollection, toAdd: Collection?) { + + private fun addNonNull( + collection: MutableCollection, + toAdd: Collection?, + ) { if (toAdd != null && !toAdd.isEmpty()) { collection.addAll(toAdd) } diff --git a/src/main/java/org/radarbase/management/config/WebConfigurer.kt b/src/main/java/org/radarbase/management/config/WebConfigurer.kt index 1ff446223..923392406 100644 --- a/src/main/java/org/radarbase/management/config/WebConfigurer.kt +++ b/src/main/java/org/radarbase/management/config/WebConfigurer.kt @@ -25,22 +25,26 @@ import javax.servlet.ServletContext * Configuration of web application with Servlet 3.0 APIs. */ @Configuration -class WebConfigurer : ServletContextInitializer, WebServerFactoryCustomizer { +class WebConfigurer : + ServletContextInitializer, + WebServerFactoryCustomizer { @Autowired private val env: Environment? = null @Autowired private val jHipsterProperties: JHipsterProperties? = null + override fun onStartup(servletContext: ServletContext) { if (env!!.activeProfiles.size != 0) { log.info( "Web application configuration, using profiles: {}", - Arrays.asList(*env.activeProfiles) + Arrays.asList(*env.activeProfiles), ) } if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_PRODUCTION))) { - val disps = EnumSet - .of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC) + val disps = + EnumSet + .of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC) initCachingHttpHeadersFilter(servletContext, disps) } log.info("Web application fully configured") @@ -74,12 +78,19 @@ class WebConfigurer : ServletContextInitializer, WebServerFactoryCustomizer + disps: EnumSet, ) { log.debug("Registering Caching HTTP Headers Filter") - val cachingHttpHeadersFilter = servletContext.addFilter( - "cachingHttpHeadersFilter", - CachingHttpHeadersFilter(jHipsterProperties) - ) + val cachingHttpHeadersFilter = + servletContext.addFilter( + "cachingHttpHeadersFilter", + CachingHttpHeadersFilter(jHipsterProperties), + ) cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*") cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*") cachingHttpHeadersFilter.setAsyncSupported(true) } @Bean - fun passwordEncoder(): PasswordEncoder { - return BCryptPasswordEncoder() - } + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() companion object { private val log = LoggerFactory.getLogger(WebConfigurer::class.java) diff --git a/src/main/java/org/radarbase/management/config/audit/AuditEventConverter.kt b/src/main/java/org/radarbase/management/config/audit/AuditEventConverter.kt index d2f742049..29bf6f6cb 100644 --- a/src/main/java/org/radarbase/management/config/audit/AuditEventConverter.kt +++ b/src/main/java/org/radarbase/management/config/audit/AuditEventConverter.kt @@ -14,9 +14,7 @@ class AuditEventConverter { * @param persistentAuditEvents the list to convert * @return the converted list. */ - fun convertToAuditEvent( - persistentAuditEvents: Iterable? - ): List { + fun convertToAuditEvent(persistentAuditEvents: Iterable?): List { if (persistentAuditEvents == null) { return emptyList() } @@ -34,12 +32,15 @@ class AuditEventConverter { * @return the converted list. */ fun convertToAuditEvent(persistentAuditEvent: PersistentAuditEvent): AuditEvent { - val instant = persistentAuditEvent.auditEventDate?.atZone(ZoneId.systemDefault()) - ?.toInstant() + val instant = + persistentAuditEvent.auditEventDate + ?.atZone(ZoneId.systemDefault()) + ?.toInstant() return AuditEvent( - instant, persistentAuditEvent.principal, + instant, + persistentAuditEvent.principal, persistentAuditEvent.auditEventType, - convertDataToObjects(persistentAuditEvent.data) + convertDataToObjects(persistentAuditEvent.data), ) } @@ -71,7 +72,6 @@ class AuditEventConverter { val results: MutableMap = HashMap() if (data != null) { for ((key, value) in data) { - // Extract the data that will be saved. if (value is WebAuthenticationDetails && value.sessionId != null) { results["sessionId"] = value.sessionId diff --git a/src/main/java/org/radarbase/management/config/audit/CustomRevisionListener.kt b/src/main/java/org/radarbase/management/config/audit/CustomRevisionListener.kt index c2279fc22..5d12ee9c1 100644 --- a/src/main/java/org/radarbase/management/config/audit/CustomRevisionListener.kt +++ b/src/main/java/org/radarbase/management/config/audit/CustomRevisionListener.kt @@ -12,10 +12,13 @@ import org.springframework.stereotype.Component class CustomRevisionListener : RevisionListener { @Autowired private val springSecurityAuditorAware: SpringSecurityAuditorAware? = null + override fun newRevision(revisionEntity: Any) { AutowireHelper.autowire(this, springSecurityAuditorAware) val entity = revisionEntity as CustomRevisionEntity - entity.auditor = springSecurityAuditorAware!!.currentAuditor - .orElse(Constants.SYSTEM_ACCOUNT) + entity.auditor = + springSecurityAuditorAware!! + .currentAuditor + .orElse(Constants.SYSTEM_ACCOUNT) } } diff --git a/src/main/java/org/radarbase/management/domain/Authority.kt b/src/main/java/org/radarbase/management/domain/Authority.kt index 7a67aae0a..89bc21b5b 100644 --- a/src/main/java/org/radarbase/management/domain/Authority.kt +++ b/src/main/java/org/radarbase/management/domain/Authority.kt @@ -21,11 +21,14 @@ import javax.validation.constraints.Size @Audited @Table(name = "radar_authority") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -data class Authority(@JvmField - @Id - @Column(length = 50) - @NotNull @Size(min = 0, max = 50) @Pattern(regexp = Constants.ENTITY_ID_REGEX) var name: String? = null) : Serializable { - +data class Authority( + @JvmField + @Id + @Column(length = 50) + @NotNull + @Size(min = 0, max = 50) + @Pattern(regexp = Constants.ENTITY_ID_REGEX) var name: String? = null, +) : Serializable { constructor(role: RoleAuthority) : this(role.authority) override fun equals(other: Any?): Boolean { @@ -38,22 +41,18 @@ data class Authority(@JvmField val authority = other as Authority return if (name == null || authority.name == null) { false - } else name == authority.name + } else { + name == authority.name + } } - override fun hashCode(): Int { - return if (name != null) name.hashCode() else 0 - } + override fun hashCode(): Int = if (name != null) name.hashCode() else 0 - override fun toString(): String { - return name.toString() - } + override fun toString(): String = name.toString() companion object { private const val serialVersionUID = 1L } - fun asString() : String? { - return name - } + fun asString(): String? = name } diff --git a/src/main/java/org/radarbase/management/domain/Group.kt b/src/main/java/org/radarbase/management/domain/Group.kt index 71d2810e3..8921fd81c 100644 --- a/src/main/java/org/radarbase/management/domain/Group.kt +++ b/src/main/java/org/radarbase/management/domain/Group.kt @@ -32,7 +32,9 @@ import javax.validation.constraints.Size @Entity @Table(name = "radar_group") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -class Group : AbstractEntity(), Serializable { +class Group : + AbstractEntity(), + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") @@ -40,7 +42,10 @@ class Group : AbstractEntity(), Serializable { @JvmField @Column(name = "name", length = 50, nullable = false) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) @Size(min = 1, max = 50) var name: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + @Size(min = 1, max = 50) + var name: String? = null @JvmField @JsonIgnore @@ -48,6 +53,7 @@ class Group : AbstractEntity(), Serializable { @JoinColumn(name = "project_id", nullable = false) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) var project: Project? = null + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -58,20 +64,21 @@ class Group : AbstractEntity(), Serializable { val group = other as Group return if (group.id == null || id == null) { false - } else id == group.id + } else { + id == group.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("Group{" - + "id=" + id + ", " - + "name=" + name + ", " - + "project='" + project?.projectName + "', " - + "}") - } + override fun toString(): String = + ( + "Group{" + + "id=" + id + ", " + + "name=" + name + ", " + + "project='" + project?.projectName + "', " + + "}" + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/MetaToken.kt b/src/main/java/org/radarbase/management/domain/MetaToken.kt index 8cc4e0e45..2b4d66991 100644 --- a/src/main/java/org/radarbase/management/domain/MetaToken.kt +++ b/src/main/java/org/radarbase/management/domain/MetaToken.kt @@ -27,7 +27,7 @@ import javax.validation.constraints.Pattern @Table(name = "radar_meta_token") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) class MetaToken : AbstractEntity() { @Id @@ -36,7 +36,9 @@ class MetaToken : AbstractEntity() { override var id: Long? = null @Column(name = "token_name", nullable = false, unique = true) - @NotNull @Pattern(regexp = Constants.TOKEN_NAME_REGEX) var tokenName: String? = null + @NotNull + @Pattern(regexp = Constants.TOKEN_NAME_REGEX) + var tokenName: String? = null private set @Column(name = "fetched", nullable = false) @@ -82,9 +84,7 @@ class MetaToken : AbstractEntity() { return this } - fun isFetched(): Boolean { - return fetched!! - } + fun isFetched(): Boolean = fetched!! fun fetched(fetched: Boolean): MetaToken { this.fetched = fetched @@ -117,27 +117,30 @@ class MetaToken : AbstractEntity() { return false } val metaToken = other as MetaToken - return id == metaToken.id && tokenName == metaToken.tokenName && fetched == metaToken.fetched && expiryDate == metaToken.expiryDate && clientId == metaToken.clientId && subject == metaToken.subject && persistent == metaToken.persistent + return id == metaToken.id && + tokenName == metaToken.tokenName && + fetched == metaToken.fetched && + expiryDate == metaToken.expiryDate && + clientId == metaToken.clientId && + subject == metaToken.subject && + persistent == metaToken.persistent } - override fun hashCode(): Int { - return Objects.hash(id, tokenName, fetched, expiryDate, subject, clientId, persistent) - } + override fun hashCode(): Int = Objects.hash(id, tokenName, fetched, expiryDate, subject, clientId, persistent) - override fun toString(): String { - return ("MetaToken{" + "id=" + id - + ", tokenName='" + tokenName - + ", fetched=" + fetched - + ", expiryDate=" + expiryDate - + ", subject=" + subject - + ", clientId=" + clientId - + ", persistent=" + persistent - + '}') - } + override fun toString(): String = + ( + "MetaToken{" + "id=" + id + + ", tokenName='" + tokenName + + ", fetched=" + fetched + + ", expiryDate=" + expiryDate + + ", subject=" + subject + + ", clientId=" + clientId + + ", persistent=" + persistent + + '}' + ) - fun isPersistent(): Boolean { - return persistent!! - } + fun isPersistent(): Boolean = persistent!! fun persistent(persistent: Boolean): MetaToken { this.persistent = persistent @@ -145,16 +148,75 @@ class MetaToken : AbstractEntity() { } companion object { - private val ID_CHARS = charArrayOf( - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', - 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', - 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '-', '.' - ) - - //https://math.stackexchange.com/questions/889538/ + private val ID_CHARS = + charArrayOf( + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '-', + '.', + ) + + // https://math.stackexchange.com/questions/889538/ // probability-of-collision-with-randomly-generated-id // Current length of tokenName is 8 for short-lived tokens, and double that // for persistent tokens. diff --git a/src/main/java/org/radarbase/management/domain/Organization.kt b/src/main/java/org/radarbase/management/domain/Organization.kt index d0aa47a37..25b4bb7d0 100644 --- a/src/main/java/org/radarbase/management/domain/Organization.kt +++ b/src/main/java/org/radarbase/management/domain/Organization.kt @@ -26,7 +26,7 @@ import javax.validation.constraints.Pattern @Table(name = "radar_organization") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) class Organization : AbstractEntity() { @Id @@ -36,15 +36,19 @@ class Organization : AbstractEntity() { @JvmField @Column(name = "name", nullable = false, unique = true) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var name: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var name: String? = null @JvmField @Column(name = "description", nullable = false) - @NotNull var description: String? = null + @NotNull + var description: String? = null @JvmField @Column(name = "location", nullable = false) - @NotNull var location: String? = null + @NotNull + var location: String? = null @JvmField @OneToMany(mappedBy = "organization") @@ -60,21 +64,22 @@ class Organization : AbstractEntity() { val org = other as Organization return if (org.id == null || id == null) { false - } else id == org.id + } else { + id == org.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("Organization{" - + "id=" + id - + ", name='" + name + "'" - + ", description='" + description + "'" - + ", location='" + location + "'" - + "}") - } + override fun toString(): String = + ( + "Organization{" + + "id=" + id + + ", name='" + name + "'" + + ", description='" + description + "'" + + ", location='" + location + "'" + + "}" + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/package-info.kt b/src/main/java/org/radarbase/management/domain/PackageInfo.kt similarity index 60% rename from src/main/java/org/radarbase/management/domain/package-info.kt rename to src/main/java/org/radarbase/management/domain/PackageInfo.kt index 302bbf388..da46eccda 100644 --- a/src/main/java/org/radarbase/management/domain/package-info.kt +++ b/src/main/java/org/radarbase/management/domain/PackageInfo.kt @@ -1,5 +1,6 @@ /** * JPA domain objects. */ -package org.radarbase.management.domain +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.domain diff --git a/src/main/java/org/radarbase/management/domain/PersistentAuditEvent.kt b/src/main/java/org/radarbase/management/domain/PersistentAuditEvent.kt index 7f58cfab1..fc77eb90f 100644 --- a/src/main/java/org/radarbase/management/domain/PersistentAuditEvent.kt +++ b/src/main/java/org/radarbase/management/domain/PersistentAuditEvent.kt @@ -34,7 +34,8 @@ class PersistentAuditEvent : Serializable { @JvmField @Column(nullable = false) - @NotNull var principal: String? = null + @NotNull + var principal: String? = null @JvmField @Column(name = "event_date") diff --git a/src/main/java/org/radarbase/management/domain/Project.kt b/src/main/java/org/radarbase/management/domain/Project.kt index 7beb687fa..4584c153d 100644 --- a/src/main/java/org/radarbase/management/domain/Project.kt +++ b/src/main/java/org/radarbase/management/domain/Project.kt @@ -46,34 +46,35 @@ import javax.validation.constraints.Pattern @Table(name = "project") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) @DynamicInsert -class Project : AbstractEntity(), Serializable { +class Project : + AbstractEntity(), + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") override var id: Long? = null @Column(name = "project_name", nullable = false, unique = true) - @NotNull @Pattern(regexp = "^[_'.@A-Za-z0-9- ]*$") var projectName: String? = null + @NotNull + @Pattern(regexp = "^[_'.@A-Za-z0-9- ]*$") + var projectName: String? = null @Column(name = "description", nullable = false) - @NotNull var description: String? = null + @NotNull + var description: String? = null // Defaults to organization name, but if that is not set then we can use the organizationName @Column(name = "jhi_organization") var organizationName: String? = null get() { - if (organization?.name != null) + if (organization?.name != null) { field = organization?.name + } return field } - // needed because the @JVMField annotation cannot be added when a custom getter/setter is set - set(value) { - field = value - } - @JvmField @ManyToOne(fetch = FetchType.EAGER) @@ -82,7 +83,8 @@ class Project : AbstractEntity(), Serializable { @JvmField @Column(name = "location", nullable = false) - @NotNull var location: String? = null + @NotNull + var location: String? = null @JvmField @Column(name = "start_date") @@ -110,7 +112,7 @@ class Project : AbstractEntity(), Serializable { @JoinTable( name = "project_source_type", joinColumns = [JoinColumn(name = "projects_id", referencedColumnName = "id")], - inverseJoinColumns = [JoinColumn(name = "source_types_id", referencedColumnName = "id")] + inverseJoinColumns = [JoinColumn(name = "source_types_id", referencedColumnName = "id")], ) var sourceTypes: Set = HashSet() @@ -128,10 +130,11 @@ class Project : AbstractEntity(), Serializable { mappedBy = "project", fetch = FetchType.LAZY, orphanRemoval = true, - cascade = [javax.persistence.CascadeType.REMOVE, javax.persistence.CascadeType.REFRESH, javax.persistence.CascadeType.DETACH] + cascade = [javax.persistence.CascadeType.REMOVE, javax.persistence.CascadeType.REFRESH, javax.persistence.CascadeType.DETACH], ) @OrderBy("name ASC") var groups: MutableSet = HashSet() + fun projectName(projectName: String?): Project { this.projectName = projectName return this @@ -187,25 +190,26 @@ class Project : AbstractEntity(), Serializable { val project = other as Project return if (project.id == null || id == null) { false - } else id == project.id - } - - override fun hashCode(): Int { - return Objects.hashCode(id) + } else { + id == project.id + } } - override fun toString(): String { - return ("Project{" - + "id=" + id - + ", projectName='" + projectName + "'" - + ", description='" + description + "'" - + ", organization='" + organizationName + "'" - + ", location='" + location + "'" - + ", startDate='" + startDate + "'" - + ", projectStatus='" + projectStatus + "'" - + ", endDate='" + endDate + "'" - + "}") - } + override fun hashCode(): Int = Objects.hashCode(id) + + override fun toString(): String = + ( + "Project{" + + "id=" + id + + ", projectName='" + projectName + "'" + + ", description='" + description + "'" + + ", organization='" + organizationName + "'" + + ", location='" + location + "'" + + ", startDate='" + startDate + "'" + + ", projectStatus='" + projectStatus + "'" + + ", endDate='" + endDate + "'" + + "}" + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/Role.kt b/src/main/java/org/radarbase/management/domain/Role.kt index 010af8aca..edd5aba94 100644 --- a/src/main/java/org/radarbase/management/domain/Role.kt +++ b/src/main/java/org/radarbase/management/domain/Role.kt @@ -39,9 +39,11 @@ import javax.persistence.Table @Table(name = "radar_role") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) -class Role : AbstractEntity, Serializable { +class Role : + AbstractEntity, + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") @@ -110,19 +112,18 @@ class Role : AbstractEntity, Serializable { return authority == role.authority && project == role.project && organization == role.organization } - override fun hashCode(): Int { - return Objects.hash(authority, project, organization) - } - - override fun toString(): String { - return ("Role{" - + "id=" + id + ", " - + "organization='" + (if (organization == null) "null" else organization?.name) - + "', " - + "project='" + (if (project == null) "null" else project?.projectName) + "', " - + "authority='" + authority?.name + "', " - + "}") - } + override fun hashCode(): Int = Objects.hash(authority, project, organization) + + override fun toString(): String = + ( + "Role{" + + "id=" + id + ", " + + "organization='" + (if (organization == null) "null" else organization?.name) + + "', " + + "project='" + (if (project == null) "null" else project?.projectName) + "', " + + "authority='" + authority?.name + "', " + + "}" + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/Source.kt b/src/main/java/org/radarbase/management/domain/Source.kt index 770d5233a..ad61005a6 100644 --- a/src/main/java/org/radarbase/management/domain/Source.kt +++ b/src/main/java/org/radarbase/management/domain/Source.kt @@ -36,9 +36,11 @@ import javax.validation.constraints.Pattern @Table(name = "radar_source") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) -class Source : AbstractEntity, Serializable { +class Source : + AbstractEntity, + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") @@ -47,21 +49,26 @@ class Source : AbstractEntity, Serializable { // pass @JvmField @Column(name = "source_id", nullable = false, unique = true) - @NotNull var sourceId: UUID? = null + @NotNull + var sourceId: UUID? = null @JvmField @Column(name = "source_name", nullable = false, unique = true) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var sourceName: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var sourceName: String? = null @JvmField @Column(name = "expected_source_name") var expectedSourceName: String? = null @Column(name = "assigned", nullable = false) - @NotNull var assigned: Boolean? = false + @NotNull + var assigned: Boolean? = false @Column(name = "deleted", nullable = false) - @NotNull var deleted: Boolean = false + @NotNull + var deleted: Boolean = false @JvmField @ManyToOne(fetch = FetchType.EAGER) @@ -118,12 +125,15 @@ class Source : AbstractEntity, Serializable { sourceId = UUID.randomUUID() } if (sourceName == null) { - sourceName = java.lang.String.join( - "-", sourceType?.model, - sourceId.toString().substring(0, 8) - ) + sourceName = + java.lang.String.join( + "-", + sourceType?.model, + sourceId.toString().substring(0, 8), + ) } } + fun sourceType(sourceType: SourceType?): Source { this.sourceType = sourceType return this @@ -154,23 +164,24 @@ class Source : AbstractEntity, Serializable { val source = other as Source return if (source.id == null || id == null) { false - } else id == source.id && sourceId == source.sourceId - } - - override fun hashCode(): Int { - return Objects.hash(id, sourceId) + } else { + id == source.id && sourceId == source.sourceId + } } - override fun toString(): String { - return ("Source{" - + "id=" + id - + ", sourceId='" + sourceId + '\'' - + ", sourceName='" + sourceName + '\'' - + ", assigned=" + assigned - + ", sourceType=" + sourceType - + ", project=" + project - + '}') - } + override fun hashCode(): Int = Objects.hash(id, sourceId) + + override fun toString(): String = + ( + "Source{" + + "id=" + id + + ", sourceId='" + sourceId + '\'' + + ", sourceName='" + sourceName + '\'' + + ", assigned=" + assigned + + ", sourceType=" + sourceType + + ", project=" + project + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/SourceData.kt b/src/main/java/org/radarbase/management/domain/SourceData.kt index 7ff379940..d92c9fa66 100644 --- a/src/main/java/org/radarbase/management/domain/SourceData.kt +++ b/src/main/java/org/radarbase/management/domain/SourceData.kt @@ -29,30 +29,35 @@ import javax.validation.constraints.Pattern @Table(name = "source_data") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) -class SourceData : AbstractEntity(), Serializable { +class SourceData : + AbstractEntity(), + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") override var id: Long? = null - //SourceData type e.g. ACCELEROMETER, TEMPERATURE. + // SourceData type e.g. ACCELEROMETER, TEMPERATURE. @JvmField @Column(name = "source_data_type", nullable = false) - @NotNull var sourceDataType: String? = null + @NotNull + var sourceDataType: String? = null // this will be the unique human readable identifier of @JvmField @Column(name = "source_data_name", nullable = false, unique = true) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var sourceDataName: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var sourceDataName: String? = null - //Default data frequency + // Default data frequency @JvmField @Column(name = "frequency") var frequency: String? = null - //Measurement unit. + // Measurement unit. @JvmField @Column(name = "unit") var unit: String? = null @@ -92,6 +97,7 @@ class SourceData : AbstractEntity(), Serializable { @ManyToOne(fetch = FetchType.LAZY) @JsonIgnoreProperties("sourceData") // avoids infinite recursion in JSON serialization var sourceType: SourceType? = null + fun sourceDataType(sourceDataType: String?): SourceData { this.sourceDataType = sourceDataType return this @@ -147,23 +153,24 @@ class SourceData : AbstractEntity(), Serializable { val sourceData = other as SourceData return if (sourceData.id == null || id == null) { false - } else id == sourceData.id - } - - override fun hashCode(): Int { - return Objects.hashCode(id) + } else { + id == sourceData.id + } } - override fun toString(): String { - return ("SourceData{" + "id=" + id + ", sourceDataType='" + sourceDataType + '\'' - + ", frequency='" - + frequency + '\'' + ", unit='" + unit + '\'' + ", processingState=" - + processingState - + ", dataClass='" + dataClass + '\'' + ", keySchema='" + keySchema + '\'' - + ", valueSchema='" + valueSchema + '\'' + ", topic='" + topic + '\'' - + ", provider='" - + provider + '\'' + ", enabled=" + enabled + '}') - } + override fun hashCode(): Int = Objects.hashCode(id) + + override fun toString(): String = + ( + "SourceData{" + "id=" + id + ", sourceDataType='" + sourceDataType + '\'' + + ", frequency='" + + frequency + '\'' + ", unit='" + unit + '\'' + ", processingState=" + + processingState + + ", dataClass='" + dataClass + '\'' + ", keySchema='" + keySchema + '\'' + + ", valueSchema='" + valueSchema + '\'' + ", topic='" + topic + '\'' + + ", provider='" + + provider + '\'' + ", enabled=" + enabled + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/SourceType.kt b/src/main/java/org/radarbase/management/domain/SourceType.kt index baf41d505..55590e61c 100644 --- a/src/main/java/org/radarbase/management/domain/SourceType.kt +++ b/src/main/java/org/radarbase/management/domain/SourceType.kt @@ -34,9 +34,11 @@ import javax.validation.constraints.Pattern @Table(name = "source_type") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) -class SourceType : AbstractEntity(), Serializable { +class SourceType : + AbstractEntity(), + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") @@ -44,7 +46,9 @@ class SourceType : AbstractEntity(), Serializable { @JvmField @Column(name = "producer") - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var producer: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var producer: String? = null @JvmField @Column(name = "name") @@ -64,26 +68,33 @@ class SourceType : AbstractEntity(), Serializable { @JvmField @Column(name = "model", nullable = false) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var model: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var model: String? = null @JvmField @Column(name = "catalog_version", nullable = false) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) var catalogVersion: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + var catalogVersion: String? = null @JvmField @Column(name = "source_type_scope", nullable = false) - @NotNull var sourceTypeScope: String? = null + @NotNull + var sourceTypeScope: String? = null @JvmField @Column(name = "dynamic_registration", nullable = false) - @NotNull var canRegisterDynamically: Boolean? = false + @NotNull + var canRegisterDynamically: Boolean? = false @JvmField @JsonSetter(nulls = Nulls.AS_EMPTY) @OneToMany(mappedBy = "sourceType", orphanRemoval = true, fetch = FetchType.LAZY) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Cascade( - CascadeType.DELETE, CascadeType.SAVE_UPDATE + CascadeType.DELETE, + CascadeType.SAVE_UPDATE, ) var sourceData: Set = HashSet() @@ -92,6 +103,7 @@ class SourceType : AbstractEntity(), Serializable { @JsonIgnore @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) var projects: Set = HashSet() + fun producer(producer: String?): SourceType { this.producer = producer return this @@ -132,30 +144,49 @@ class SourceType : AbstractEntity(), Serializable { val sourceType = other as SourceType return if (sourceType.id == null || id == null) { false - } else id == sourceType.id && producer == sourceType.producer && model == sourceType.model && catalogVersion == sourceType.catalogVersion && canRegisterDynamically == sourceType.canRegisterDynamically && sourceTypeScope == sourceType.sourceTypeScope && name == sourceType.name && description == sourceType.description && appProvider == sourceType.appProvider && assessmentType == sourceType.assessmentType + } else { + id == sourceType.id && + producer == sourceType.producer && + model == sourceType.model && + catalogVersion == sourceType.catalogVersion && + canRegisterDynamically == sourceType.canRegisterDynamically && + sourceTypeScope == sourceType.sourceTypeScope && + name == sourceType.name && + description == sourceType.description && + appProvider == sourceType.appProvider && + assessmentType == sourceType.assessmentType + } } - override fun hashCode(): Int { - return Objects.hash( - id, model, producer, catalogVersion, canRegisterDynamically, - sourceTypeScope, name, description, appProvider, assessmentType + override fun hashCode(): Int = + Objects.hash( + id, + model, + producer, + catalogVersion, + canRegisterDynamically, + sourceTypeScope, + name, + description, + appProvider, + assessmentType, ) - } - override fun toString(): String { - return ("SourceType{" - + "id=" + id - + ", producer='" + producer + '\'' - + ", model='" + model + '\'' - + ", catalogVersion='" + catalogVersion + '\'' - + ", sourceTypeScope=" + sourceTypeScope - + ", canRegisterDynamically=" + canRegisterDynamically - + ", name='" + name + '\'' - + ", description=" + description - + ", appProvider=" + appProvider - + ", assessmentType=" + assessmentType - + '}') - } + override fun toString(): String = + ( + "SourceType{" + + "id=" + id + + ", producer='" + producer + '\'' + + ", model='" + model + '\'' + + ", catalogVersion='" + catalogVersion + '\'' + + ", sourceTypeScope=" + sourceTypeScope + + ", canRegisterDynamically=" + canRegisterDynamically + + ", name='" + name + '\'' + + ", description=" + description + + ", appProvider=" + appProvider + + ", assessmentType=" + assessmentType + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/Subject.kt b/src/main/java/org/radarbase/management/domain/Subject.kt index 1d84aa5c4..3003f2fb2 100644 --- a/src/main/java/org/radarbase/management/domain/Subject.kt +++ b/src/main/java/org/radarbase/management/domain/Subject.kt @@ -42,163 +42,168 @@ import javax.validation.constraints.NotNull @Table(name = "subject") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) class Subject( @Id @GeneratedValue( strategy = GenerationType.SEQUENCE, - generator = "sequenceGenerator" + generator = "sequenceGenerator", ) @SequenceGenerator( name = "sequenceGenerator", initialValue = 1000, - sequenceName = "hibernate_sequence" - ) override var id: Long? = null -) : AbstractEntity(), Serializable { - - @JvmField - @Column(name = "external_link") - var externalLink: String? = null - - @JvmField - @Column(name = "external_id") - var externalId: String? = null - - @Column(name = "removed", nullable = false) - @NotNull var removed: Boolean = false - - @JvmField - @OneToOne - @JoinColumn(unique = true, name = "user_id") - @Cascade(CascadeType.ALL) - var user: User? = null - - @JvmField - @OneToMany(mappedBy = "subject", fetch = FetchType.LAZY) - @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) - @Cascade( - CascadeType.SAVE_UPDATE - ) - var sources: MutableSet = HashSet() - - @JvmField - @JsonSetter(nulls = Nulls.AS_EMPTY) - @ElementCollection(fetch = FetchType.EAGER) - @MapKeyColumn(name = "attribute_key") - @Column(name = "attribute_value") - @CollectionTable(name = "subject_metadata", joinColumns = [JoinColumn(name = "id")]) - @Cascade( - CascadeType.ALL - ) - @BatchSize(size = 50) - var attributes: Map = HashMap() - - @OneToMany(mappedBy = "subject", orphanRemoval = true, fetch = FetchType.LAZY) - @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) - @JsonIgnore - val metaTokens: Set = HashSet() - - @JvmField - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "group_id") - @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) - var group: Group? = null - - @JvmField - @Column(name = "date_of_birth") - var dateOfBirth: LocalDate? = null - - @JvmField - @Column(name = "enrollment_date") - var enrollmentDate: ZonedDateTime? = null - - @JvmField - @Column(name = "person_name") - var personName: String? = null - - val activeProject: Project? - /** - * Gets the active project of subject. - * - * - * There can be only one role with PARTICIPANT authority - * and the project that is related to that role is the active role. - * - * @return [Project] currently active project of subject. - */ - get() = user?.roles - ?.firstOrNull { r -> r.authority?.name == RoleAuthority.PARTICIPANT.authority } - ?.project - - val associatedProject: Project? - /** - * Get the active project of a subject, and otherwise the - * inactive project. - * @return the project a subject belongs to, if any. - */ - get() { - val user = user ?: return null - return user.roles - ?.filter { r -> PARTICIPANT_TYPES.contains(r.authority?.name) } - ?.sortedBy { it.authority?.name }?.firstOrNull() - .let { obj: Role? -> obj?.project } - + sequenceName = "hibernate_sequence", + ) override var id: Long? = null, +) : AbstractEntity(), + Serializable { + @JvmField + @Column(name = "external_link") + var externalLink: String? = null + + @JvmField + @Column(name = "external_id") + var externalId: String? = null + + @Column(name = "removed", nullable = false) + @NotNull + var removed: Boolean = false + + @JvmField + @OneToOne + @JoinColumn(unique = true, name = "user_id") + @Cascade(CascadeType.ALL) + var user: User? = null + + @JvmField + @OneToMany(mappedBy = "subject", fetch = FetchType.LAZY) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @Cascade( + CascadeType.SAVE_UPDATE, + ) + var sources: MutableSet = HashSet() + + @JvmField + @JsonSetter(nulls = Nulls.AS_EMPTY) + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "attribute_key") + @Column(name = "attribute_value") + @CollectionTable(name = "subject_metadata", joinColumns = [JoinColumn(name = "id")]) + @Cascade( + CascadeType.ALL, + ) + @BatchSize(size = 50) + var attributes: Map = HashMap() + + @OneToMany(mappedBy = "subject", orphanRemoval = true, fetch = FetchType.LAZY) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @JsonIgnore + val metaTokens: Set = HashSet() + + @JvmField + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "group_id") + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) + var group: Group? = null + + @JvmField + @Column(name = "date_of_birth") + var dateOfBirth: LocalDate? = null + + @JvmField + @Column(name = "enrollment_date") + var enrollmentDate: ZonedDateTime? = null + + @JvmField + @Column(name = "person_name") + var personName: String? = null + + val activeProject: Project? + /** + * Gets the active project of subject. + * + * + * There can be only one role with PARTICIPANT authority + * and the project that is related to that role is the active role. + * + * @return [Project] currently active project of subject. + */ + get() = + user + ?.roles + ?.firstOrNull { r -> r.authority?.name == RoleAuthority.PARTICIPANT.authority } + ?.project + + val associatedProject: Project? + /** + * Get the active project of a subject, and otherwise the + * inactive project. + * @return the project a subject belongs to, if any. + */ + get() { + val user = user ?: return null + return user.roles + .filter { r -> PARTICIPANT_TYPES.contains(r.authority?.name) } + .sortedBy { it.authority?.name } + ?.firstOrNull() + .let { obj: Role? -> obj?.project } + } + + fun externalLink(externalLink: String?): Subject { + this.externalLink = externalLink + return this } - fun externalLink(externalLink: String?): Subject { - this.externalLink = externalLink - return this - } - - fun externalId(enternalId: String?): Subject { - externalId = enternalId - return this - } - - fun user(usr: User?): Subject { - user = usr - return this - } - - fun sources(sources: MutableSet): Subject { - this.sources = sources - return this - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true + fun externalId(enternalId: String?): Subject { + externalId = enternalId + return this } - if (other == null || javaClass != other.javaClass) { - return false + + fun user(usr: User?): Subject { + user = usr + return this } - val subject = other as Subject - return if (subject.id == null || id == null) { - false - } else id == subject.id - } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + fun sources(sources: MutableSet): Subject { + this.sources = sources + return this + } - override fun toString(): String { - return ("Subject{" - + "id=" + id - + ", externalLink='" + externalLink + '\'' - + ", externalId='" + externalId + '\'' - + ", removed=" + removed - + ", user=" + user - + ", sources=" + sources - + ", attributes=" + attributes - + ", group=" + group - + "}") - } + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val subject = other as Subject + return if (subject.id == null || id == null) { + false + } else { + id == subject.id + } + } - companion object { - private const val serialVersionUID = 1L - private val PARTICIPANT_TYPES = mutableSetOf( - RoleAuthority.PARTICIPANT.authority, - RoleAuthority.INACTIVE_PARTICIPANT.authority - ) + override fun hashCode(): Int = Objects.hashCode(id) + + override fun toString(): String = + ( + "Subject{" + + "id=" + id + + ", externalLink='" + externalLink + '\'' + + ", externalId='" + externalId + '\'' + + ", removed=" + removed + + ", user=" + user + + ", sources=" + sources + + ", attributes=" + attributes + + ", group=" + group + + "}" + ) + + companion object { + private const val serialVersionUID = 1L + private val PARTICIPANT_TYPES = + mutableSetOf( + RoleAuthority.PARTICIPANT.authority, + RoleAuthority.INACTIVE_PARTICIPANT.authority, + ) + } } -} diff --git a/src/main/java/org/radarbase/management/domain/User.kt b/src/main/java/org/radarbase/management/domain/User.kt index cdcab2dfe..0e178bcff 100644 --- a/src/main/java/org/radarbase/management/domain/User.kt +++ b/src/main/java/org/radarbase/management/domain/User.kt @@ -38,51 +38,66 @@ import javax.validation.constraints.Size @Table(name = "radar_user") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @EntityListeners( - AbstractEntityListener::class + AbstractEntityListener::class, ) -class User : AbstractEntity(), Serializable { +class User : + AbstractEntity(), + Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator", initialValue = 1000, sequenceName = "hibernate_sequence") override var id: Long? = null @Column(length = 50, unique = true, nullable = false) - @NotNull @Pattern(regexp = Constants.ENTITY_ID_REGEX) @Size(min = 1, max = 50) var login: String? = null + @NotNull + @Pattern(regexp = Constants.ENTITY_ID_REGEX) + @Size(min = 1, max = 50) + var login: String? = null private set @JvmField @JsonIgnore @Column(name = "password_hash", length = 60) - @NotNull @Size(min = 60, max = 60) var password: String? = null + @NotNull + @Size(min = 60, max = 60) + var password: String? = null @JvmField @Column(name = "first_name", length = 50) - @Size(max = 50) var firstName: String? = null + @Size(max = 50) + var firstName: String? = null @JvmField @Column(name = "last_name", length = 50) - @Size(max = 50) var lastName: String? = null + @Size(max = 50) + var lastName: String? = null @JvmField @Column(length = 100, unique = true, nullable = true) - @Email @Size(min = 5, max = 100) var email: String? = null + @Email + @Size(min = 5, max = 100) + var email: String? = null @JvmField @Column(nullable = false) - @NotNull var activated: Boolean = false + @NotNull + var activated: Boolean = false @JvmField @Column(name = "lang_key", length = 5) - @Size(min = 2, max = 5) var langKey: String? = null + @Size(min = 2, max = 5) + var langKey: String? = null @JvmField @Column(name = "activation_key", length = 20) @JsonIgnore - @Size(max = 20) var activationKey: String? = null + @Size(max = 20) + var activationKey: String? = null @JvmField @Column(name = "reset_key", length = 20) - @Size(max = 20) var resetKey: String? = null + @Size(max = 20) + var resetKey: String? = null @JvmField @Column(name = "reset_date") @@ -104,16 +119,16 @@ class User : AbstractEntity(), Serializable { @JoinTable( name = "role_users", joinColumns = [JoinColumn(name = "users_id", referencedColumnName = "id")], - inverseJoinColumns = [JoinColumn(name = "roles_id", referencedColumnName = "id")] + inverseJoinColumns = [JoinColumn(name = "roles_id", referencedColumnName = "id")], ) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @BatchSize(size = 20) @Cascade( - CascadeType.SAVE_UPDATE + CascadeType.SAVE_UPDATE, ) var roles: MutableSet = HashSet() - //Lowercase the login before saving it in database + // Lowercase the login before saving it in database fun setLogin(login: String?) { this.login = login?.lowercase() } @@ -129,21 +144,20 @@ class User : AbstractEntity(), Serializable { return login == user.login } - override fun hashCode(): Int { - return login.hashCode() - } - - override fun toString(): String { - return ("User{" - + "login='" + login + '\'' - + ", firstName='" + firstName + '\'' - + ", lastName='" + lastName + '\'' - + ", email='" + email + '\'' - + ", activated='" + activated + '\'' - + ", langKey='" + langKey + '\'' - + ", activationKey='" + activationKey + '\'' - + "}") - } + override fun hashCode(): Int = login.hashCode() + + override fun toString(): String = + ( + "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}" + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/domain/audit/CustomRevisionEntity.kt b/src/main/java/org/radarbase/management/domain/audit/CustomRevisionEntity.kt index 573d2ea7e..bd7b52b26 100644 --- a/src/main/java/org/radarbase/management/domain/audit/CustomRevisionEntity.kt +++ b/src/main/java/org/radarbase/management/domain/audit/CustomRevisionEntity.kt @@ -34,7 +34,7 @@ class CustomRevisionEntity : Serializable { name = "revisionGenerator", initialValue = 2, allocationSize = 50, - sequenceName = "sequence_revision" + sequenceName = "sequence_revision", ) @RevisionNumber var id = 0 @@ -43,6 +43,7 @@ class CustomRevisionEntity : Serializable { @Temporal(TemporalType.TIMESTAMP) @RevisionTimestamp var timestamp: Date? = null + @JvmField var auditor: String? = null @@ -51,10 +52,11 @@ class CustomRevisionEntity : Serializable { @JoinTable(name = "REVCHANGES", joinColumns = [JoinColumn(name = "REV")]) @Column(name = "ENTITYNAME") @Fetch( - FetchMode.JOIN + FetchMode.JOIN, ) @ModifiedEntityNames var modifiedEntityNames: Set? = null + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -62,21 +64,23 @@ class CustomRevisionEntity : Serializable { if (other !is CustomRevisionEntity) { return false } - return id == other.id && timestamp == other.timestamp && auditor == other.auditor && modifiedEntityNames == other.modifiedEntityNames + return id == other.id && + timestamp == other.timestamp && + auditor == other.auditor && + modifiedEntityNames == other.modifiedEntityNames } - override fun hashCode(): Int { - return Objects.hash(id, timestamp, auditor, modifiedEntityNames) - } + override fun hashCode(): Int = Objects.hash(id, timestamp, auditor, modifiedEntityNames) - override fun toString(): String { - return ("CustomRevisionEntity{" - + "id=" + id - + ", timestamp=" + timestamp - + ", auditor='" + auditor + '\'' - + ", modifiedEntityNames=" + modifiedEntityNames - + '}') - } + override fun toString(): String = + ( + "CustomRevisionEntity{" + + "id=" + id + + ", timestamp=" + timestamp + + ", auditor='" + auditor + '\'' + + ", modifiedEntityNames=" + modifiedEntityNames + + '}' + ) companion object { private const val serialVersionUID = 8530213963961662300L diff --git a/src/main/java/org/radarbase/management/domain/audit/CustomRevisionMetadata.kt b/src/main/java/org/radarbase/management/domain/audit/CustomRevisionMetadata.kt index 42853ec24..73cf6356c 100644 --- a/src/main/java/org/radarbase/management/domain/audit/CustomRevisionMetadata.kt +++ b/src/main/java/org/radarbase/management/domain/audit/CustomRevisionMetadata.kt @@ -5,7 +5,9 @@ import org.springframework.util.Assert import java.time.Instant import java.util.* -class CustomRevisionMetadata(entity: CustomRevisionEntity) : RevisionMetadata { +class CustomRevisionMetadata( + entity: CustomRevisionEntity, +) : RevisionMetadata { private val entity: CustomRevisionEntity /** @@ -22,19 +24,14 @@ class CustomRevisionMetadata(entity: CustomRevisionEntity) : RevisionMetadata { - return Optional.of(entity.id) - } + override fun getRevisionNumber(): Optional = Optional.of(entity.id) - override fun getRevisionInstant(): Optional { - return Optional.ofNullable(entity.timestamp).map { ts: Date -> ts.toInstant() } - } + override fun getRevisionInstant(): Optional = + Optional.ofNullable(entity.timestamp).map { ts: Date -> ts.toInstant() } /* * (non-Javadoc) * @see org.springframework.data.history.RevisionMetadata#getDelegate() */ - override fun getDelegate(): T { - return entity as T - } + override fun getDelegate(): T = entity as T } diff --git a/src/main/java/org/radarbase/management/domain/audit/EntityAuditInfo.kt b/src/main/java/org/radarbase/management/domain/audit/EntityAuditInfo.kt index 520ce9675..f9699b658 100644 --- a/src/main/java/org/radarbase/management/domain/audit/EntityAuditInfo.kt +++ b/src/main/java/org/radarbase/management/domain/audit/EntityAuditInfo.kt @@ -44,19 +44,21 @@ class EntityAuditInfo { return false } val that = other as EntityAuditInfo - return createdAt == that.createdAt && createdBy == that.createdBy && lastModifiedAt == that.lastModifiedAt && lastModifiedBy == that.lastModifiedBy + return createdAt == that.createdAt && + createdBy == that.createdBy && + lastModifiedAt == that.lastModifiedAt && + lastModifiedBy == that.lastModifiedBy } - override fun hashCode(): Int { - return if (lastModifiedAt != null) lastModifiedAt.hashCode() else 0 - } + override fun hashCode(): Int = if (lastModifiedAt != null) lastModifiedAt.hashCode() else 0 - override fun toString(): String { - return ("EntityAuditInfo{" - + "createdAt=" + createdAt - + ", createdBy='" + createdBy + '\'' - + ", lastModifiedAt=" + lastModifiedAt - + ", lastModifiedBy='" + lastModifiedBy + '\'' - + '}') - } + override fun toString(): String = + ( + "EntityAuditInfo{" + + "createdAt=" + createdAt + + ", createdBy='" + createdBy + '\'' + + ", lastModifiedAt=" + lastModifiedAt + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/domain/enumeration/ProjectStatus.kt b/src/main/java/org/radarbase/management/domain/enumeration/ProjectStatus.kt index 568e9a8f0..2d435bf3b 100644 --- a/src/main/java/org/radarbase/management/domain/enumeration/ProjectStatus.kt +++ b/src/main/java/org/radarbase/management/domain/enumeration/ProjectStatus.kt @@ -6,5 +6,5 @@ package org.radarbase.management.domain.enumeration enum class ProjectStatus { PLANNING, ONGOING, - ENDED + ENDED, } diff --git a/src/main/java/org/radarbase/management/domain/enumeration/Role.kt b/src/main/java/org/radarbase/management/domain/enumeration/Role.kt index 58b1cc2b8..a6d33dd82 100644 --- a/src/main/java/org/radarbase/management/domain/enumeration/Role.kt +++ b/src/main/java/org/radarbase/management/domain/enumeration/Role.kt @@ -10,5 +10,5 @@ enum class Role { ROLE_PROJECT_OWNER, ROLE_AFFILIATE, ROLE_ANALYST, - ROLE_PARTICIPANT + ROLE_PARTICIPANT, } diff --git a/src/main/java/org/radarbase/management/domain/support/AbstractEntityListener.kt b/src/main/java/org/radarbase/management/domain/support/AbstractEntityListener.kt index affea0bbd..7c22dd660 100644 --- a/src/main/java/org/radarbase/management/domain/support/AbstractEntityListener.kt +++ b/src/main/java/org/radarbase/management/domain/support/AbstractEntityListener.kt @@ -30,9 +30,13 @@ class AbstractEntityListener { fun publishPersistEvent(entity: AbstractEntity) { AutowireHelper.autowire(this, springSecurityAuditorAware) log.info( - TEMPLATE, ENTITY_CREATED, springSecurityAuditorAware!!.currentAuditor + TEMPLATE, + ENTITY_CREATED, + springSecurityAuditorAware!! + .currentAuditor .orElse(Constants.SYSTEM_ACCOUNT), - entity.javaClass.getName(), entity.toString() + entity.javaClass.getName(), + entity.toString(), ) } @@ -45,9 +49,13 @@ class AbstractEntityListener { fun publishUpdateEvent(entity: AbstractEntity) { AutowireHelper.autowire(this, springSecurityAuditorAware) log.info( - TEMPLATE, ENTITY_UPDATED, springSecurityAuditorAware!!.currentAuditor + TEMPLATE, + ENTITY_UPDATED, + springSecurityAuditorAware!! + .currentAuditor .orElse(Constants.SYSTEM_ACCOUNT), - entity.javaClass.getName(), entity.toString() + entity.javaClass.getName(), + entity.toString(), ) } @@ -60,9 +68,13 @@ class AbstractEntityListener { fun publishRemoveEvent(entity: AbstractEntity) { AutowireHelper.autowire(this, springSecurityAuditorAware) log.info( - TEMPLATE, ENTITY_REMOVED, springSecurityAuditorAware!!.currentAuditor + TEMPLATE, + ENTITY_REMOVED, + springSecurityAuditorAware!! + .currentAuditor .orElse(Constants.SYSTEM_ACCOUNT), - entity.javaClass.getName(), entity.toString() + entity.javaClass.getName(), + entity.toString(), ) } @@ -71,12 +83,11 @@ class AbstractEntityListener { * and use it to populate the created and last modified fields. * * @param entity the entity that was loaded. - */ - /* - @PostLoad - public void populateAuditMetaData(AbstractEntity entity) { - }*/ + @PostLoad + public void populateAuditMetaData(AbstractEntity entity) { + + }*/ companion object { const val ENTITY_CREATED = "ENTITY_CREATED" const val ENTITY_UPDATED = "ENTITY_UPDATED" diff --git a/src/main/java/org/radarbase/management/domain/support/AutowireHelper.kt b/src/main/java/org/radarbase/management/domain/support/AutowireHelper.kt index 22efc363c..365e692fb 100644 --- a/src/main/java/org/radarbase/management/domain/support/AutowireHelper.kt +++ b/src/main/java/org/radarbase/management/domain/support/AutowireHelper.kt @@ -31,7 +31,10 @@ class AutowireHelper private constructor() : ApplicationContextAware { * @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified * {#classToAutowire} */ - fun autowire(classToAutowire: Any, vararg beansToAutowireInClass: Any?) { + fun autowire( + classToAutowire: Any, + vararg beansToAutowireInClass: Any?, + ) { for (bean in beansToAutowireInClass) { if (bean == null) { applicationContext!!.autowireCapableBeanFactory.autowireBean(classToAutowire) diff --git a/src/main/java/org/radarbase/management/filters/CustomHttpServletRequest.kt b/src/main/java/org/radarbase/management/filters/CustomHttpServletRequest.kt index 45c423b13..9dd28bd6e 100644 --- a/src/main/java/org/radarbase/management/filters/CustomHttpServletRequest.kt +++ b/src/main/java/org/radarbase/management/filters/CustomHttpServletRequest.kt @@ -3,15 +3,15 @@ package org.radarbase.management.filters import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequestWrapper -class CustomHttpServletRequest /** * Create a new instance with the given request and additional parameters. * * @param request the request * @param additionalParams the additional parameters - */( + */ +class CustomHttpServletRequest( private val request: HttpServletRequest, - private val additionalParams: Map> + private val additionalParams: Map>, ) : HttpServletRequestWrapper(request) { override fun getParameterMap(): Map> { val map = request.parameterMap diff --git a/src/main/java/org/radarbase/management/hibernate/CaseSensitivePhysicalNamingStrategy.kt b/src/main/java/org/radarbase/management/hibernate/CaseSensitivePhysicalNamingStrategy.kt index f410fd901..adb0c9b57 100644 --- a/src/main/java/org/radarbase/management/hibernate/CaseSensitivePhysicalNamingStrategy.kt +++ b/src/main/java/org/radarbase/management/hibernate/CaseSensitivePhysicalNamingStrategy.kt @@ -4,7 +4,5 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy class CaseSensitivePhysicalNamingStrategy : SpringPhysicalNamingStrategy() { - override fun isCaseInsensitive(jdbcEnvironment: JdbcEnvironment): Boolean { - return false - } + override fun isCaseInsensitive(jdbcEnvironment: JdbcEnvironment): Boolean = false } diff --git a/src/main/java/org/radarbase/management/repository/AuthorityRepository.kt b/src/main/java/org/radarbase/management/repository/AuthorityRepository.kt index a34d17d0c..d30ca4703 100644 --- a/src/main/java/org/radarbase/management/repository/AuthorityRepository.kt +++ b/src/main/java/org/radarbase/management/repository/AuthorityRepository.kt @@ -11,7 +11,11 @@ import org.springframework.data.repository.query.Param * Spring Data JPA repository for the Authority entity. */ @RepositoryDefinition(domainClass = Authority::class, idClass = String::class) -interface AuthorityRepository : JpaRepository, RevisionRepository { +interface AuthorityRepository : + JpaRepository, + RevisionRepository { @Query("select authority from Authority authority where authority.name = :authorityName") - fun findByAuthorityName(@Param("authorityName") authorityName: String): Authority? + fun findByAuthorityName( + @Param("authorityName") authorityName: String, + ): Authority? } diff --git a/src/main/java/org/radarbase/management/repository/CustomAuditEventRepository.kt b/src/main/java/org/radarbase/management/repository/CustomAuditEventRepository.kt index ce5bfe4f5..c148d547e 100644 --- a/src/main/java/org/radarbase/management/repository/CustomAuditEventRepository.kt +++ b/src/main/java/org/radarbase/management/repository/CustomAuditEventRepository.kt @@ -21,31 +21,37 @@ import java.time.ZoneId @Repository class CustomAuditEventRepository( @Autowired private val auditEventConverter: AuditEventConverter, - @Autowired private val persistenceAuditEventRepository: PersistenceAuditEventRepository + @Autowired private val persistenceAuditEventRepository: PersistenceAuditEventRepository, ) : AuditEventRepository { - - override fun find(principal: String, after: Instant, type: String): List { - val persistentAuditEvents: Iterable = persistenceAuditEventRepository - .findByPrincipalAndAuditEventDateAfterAndAuditEventType( - principal, - LocalDateTime.from(after), type - ) + override fun find( + principal: String, + after: Instant, + type: String, + ): List { + val persistentAuditEvents: Iterable = + persistenceAuditEventRepository + .findByPrincipalAndAuditEventDateAfterAndAuditEventType( + principal, + LocalDateTime.from(after), + type, + ) return auditEventConverter.convertToAuditEvent(persistentAuditEvents) } @Transactional(propagation = Propagation.REQUIRES_NEW) override fun add(event: AuditEvent) { val eventType = event.type - if (AUTHORIZATION_FAILURE != eventType - && Constants.ANONYMOUS_USER != event.principal + if (AUTHORIZATION_FAILURE != eventType && + Constants.ANONYMOUS_USER != event.principal ) { val persistentAuditEvent = PersistentAuditEvent() persistentAuditEvent.principal = event.principal persistentAuditEvent.auditEventType = eventType - persistentAuditEvent.auditEventDate = LocalDateTime.ofInstant( - event.timestamp, - ZoneId.systemDefault() - ) + persistentAuditEvent.auditEventDate = + LocalDateTime.ofInstant( + event.timestamp, + ZoneId.systemDefault(), + ) persistentAuditEvent.data = auditEventConverter.convertDataToStrings(event.data) persistenceAuditEventRepository.save(persistentAuditEvent) } @@ -58,7 +64,7 @@ class CustomAuditEventRepository( event.principal, errorType, event.data["message"], - event.data["details"] + event.data["details"], ) } } diff --git a/src/main/java/org/radarbase/management/repository/GroupRepository.kt b/src/main/java/org/radarbase/management/repository/GroupRepository.kt index cd704bf5c..8c0572cf5 100644 --- a/src/main/java/org/radarbase/management/repository/GroupRepository.kt +++ b/src/main/java/org/radarbase/management/repository/GroupRepository.kt @@ -16,25 +16,27 @@ import org.springframework.data.repository.history.RevisionRepository import org.springframework.data.repository.query.Param @RepositoryDefinition(domainClass = Group::class, idClass = Long::class) -interface GroupRepository : JpaRepository, RevisionRepository { +interface GroupRepository : + JpaRepository, + RevisionRepository { @Query( - "SELECT group FROM Group group " - + "WHERE group.project.id = :project_id " - + "AND group.name = :group_name" + "SELECT group FROM Group group " + + "WHERE group.project.id = :project_id " + + "AND group.name = :group_name", ) fun findByProjectIdAndName( @Param("project_id") id: Long?, - @Param("group_name") groupName: String? + @Param("group_name") groupName: String?, ): Group? @Query( - "SELECT group FROM Group group " - + "LEFT JOIN Project project on group.project = project " - + "WHERE group.project.projectName = :project_name " - + "AND group.name = :group_name" + "SELECT group FROM Group group " + + "LEFT JOIN Project project on group.project = project " + + "WHERE group.project.projectName = :project_name " + + "AND group.name = :group_name", ) fun findByProjectNameAndName( @Param("project_name") projectName: String?, - @Param("group_name") groupName: String? + @Param("group_name") groupName: String?, ): Group? } diff --git a/src/main/java/org/radarbase/management/repository/MetaTokenRepository.kt b/src/main/java/org/radarbase/management/repository/MetaTokenRepository.kt index c954fecd8..6745582c2 100644 --- a/src/main/java/org/radarbase/management/repository/MetaTokenRepository.kt +++ b/src/main/java/org/radarbase/management/repository/MetaTokenRepository.kt @@ -10,13 +10,17 @@ import java.time.Instant /** * Spring Data JPA repository for the MetaToken entity. */ -interface MetaTokenRepository : JpaRepository, RevisionRepository { +interface MetaTokenRepository : + JpaRepository, + RevisionRepository { fun findOneByTokenName(tokenName: String?): MetaToken? @Query( - "select metaToken from MetaToken metaToken " - + "where (metaToken.fetched = true and metaToken.persistent = false)" - + " or metaToken.expiryDate < :time" + "select metaToken from MetaToken metaToken " + + "where (metaToken.fetched = true and metaToken.persistent = false)" + + " or metaToken.expiryDate < :time", ) - fun findAllByFetchedOrExpired(@Param("time") time: Instant?): List + fun findAllByFetchedOrExpired( + @Param("time") time: Instant?, + ): List } diff --git a/src/main/java/org/radarbase/management/repository/OrganizationRepository.kt b/src/main/java/org/radarbase/management/repository/OrganizationRepository.kt index d471e710a..8906a6eea 100644 --- a/src/main/java/org/radarbase/management/repository/OrganizationRepository.kt +++ b/src/main/java/org/radarbase/management/repository/OrganizationRepository.kt @@ -11,18 +11,22 @@ import org.springframework.data.repository.query.Param * Spring Data JPA repository for the Organization entity. */ @RepositoryDefinition(domainClass = Organization::class, idClass = Long::class) -interface OrganizationRepository : JpaRepository, RevisionRepository { +interface OrganizationRepository : + JpaRepository, + RevisionRepository { @Query( - "select org from Organization org " - + "where org.name = :name" + "select org from Organization org " + + "where org.name = :name", ) - fun findOneByName(@Param("name") name: String): Organization? + fun findOneByName( + @Param("name") name: String, + ): Organization? @Query( - "select distinct org from Organization org left join fetch org.projects project " - + "where project.projectName in (:projectNames)" + "select distinct org from Organization org left join fetch org.projects project " + + "where project.projectName in (:projectNames)", ) fun findAllByProjectNames( - @Param("projectNames") projectNames: Collection + @Param("projectNames") projectNames: Collection, ): List } diff --git a/src/main/java/org/radarbase/management/repository/package-info.kt b/src/main/java/org/radarbase/management/repository/PackageInfo.kt similarity index 64% rename from src/main/java/org/radarbase/management/repository/package-info.kt rename to src/main/java/org/radarbase/management/repository/PackageInfo.kt index 3dcb8c57a..ae4ba3cd0 100644 --- a/src/main/java/org/radarbase/management/repository/package-info.kt +++ b/src/main/java/org/radarbase/management/repository/PackageInfo.kt @@ -1,5 +1,6 @@ /** * Spring Data JPA repositories. */ -package org.radarbase.management.repository +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.repository diff --git a/src/main/java/org/radarbase/management/repository/PersistenceAuditEventRepository.kt b/src/main/java/org/radarbase/management/repository/PersistenceAuditEventRepository.kt index 00b4bff3a..866d2b50f 100644 --- a/src/main/java/org/radarbase/management/repository/PersistenceAuditEventRepository.kt +++ b/src/main/java/org/radarbase/management/repository/PersistenceAuditEventRepository.kt @@ -11,18 +11,23 @@ import java.time.LocalDateTime */ interface PersistenceAuditEventRepository : JpaRepository { fun findByPrincipal(principal: String?): List? + fun findByAuditEventDateAfter(after: LocalDateTime?): List? + fun findByPrincipalAndAuditEventDateAfter( principal: String?, - after: LocalDateTime? + after: LocalDateTime?, ): List? fun findByPrincipalAndAuditEventDateAfterAndAuditEventType( - principle: String?, after: LocalDateTime?, type: String? + principle: String?, + after: LocalDateTime?, + type: String?, ): List fun findAllByAuditEventDateBetween( fromDate: LocalDateTime?, - toDate: LocalDateTime?, pageable: Pageable? + toDate: LocalDateTime?, + pageable: Pageable?, ): Page } diff --git a/src/main/java/org/radarbase/management/repository/ProjectRepository.kt b/src/main/java/org/radarbase/management/repository/ProjectRepository.kt index fdcf4f1d4..cac102638 100644 --- a/src/main/java/org/radarbase/management/repository/ProjectRepository.kt +++ b/src/main/java/org/radarbase/management/repository/ProjectRepository.kt @@ -15,86 +15,103 @@ import org.springframework.data.repository.query.Param */ @Suppress("unused") @RepositoryDefinition(domainClass = Project::class, idClass = Long::class) -interface ProjectRepository : JpaRepository, RevisionRepository { +interface ProjectRepository : + JpaRepository, + RevisionRepository { @Query( - value = "select distinct project from Project project " - + "left join fetch project.sourceTypes", - countQuery = "select distinct count(project) from Project project" + value = + "select distinct project from Project project " + + "left join fetch project.sourceTypes", + countQuery = "select distinct count(project) from Project project", ) fun findAllWithEagerRelationships(pageable: Pageable): Page @Query( - value = "select distinct project from Project project " - + "left join fetch project.sourceTypes " - + "WHERE project.projectName in (:projectNames) " - + "OR project.organizationName in (:organizationNames)", - countQuery = "select distinct count(project) from Project project " - + "WHERE project.projectName in (:projectNames) " - + "OR project.organizationName in (:organizationNames)" + value = + "select distinct project from Project project " + + "left join fetch project.sourceTypes " + + "WHERE project.projectName in (:projectNames) " + + "OR project.organizationName in (:organizationNames)", + countQuery = + "select distinct count(project) from Project project " + + "WHERE project.projectName in (:projectNames) " + + "OR project.organizationName in (:organizationNames)", ) fun findAllWithEagerRelationshipsInOrganizationsOrProjects( pageable: Pageable?, @Param("organizationNames") organizationNames: Collection, - @Param("projectNames") projectNames: Collection + @Param("projectNames") projectNames: Collection, ): Page @Query( - "select project from Project project " - + "WHERE project.organizationName = :organization_name" + "select project from Project project " + + "WHERE project.organizationName = :organization_name", ) fun findAllByOrganizationName( - @Param("organization_name") organizationName: String + @Param("organization_name") organizationName: String, ): List @Query( - "select project from Project project " - + "left join fetch project.sourceTypes s " - + "left join fetch project.groups " - + "left join fetch project.organization " - + "where project.id = :id" + "select project from Project project " + + "left join fetch project.sourceTypes s " + + "left join fetch project.groups " + + "left join fetch project.organization " + + "where project.id = :id", ) - fun findOneWithEagerRelationships(@Param("id") id: Long?): Project? + fun findOneWithEagerRelationships( + @Param("id") id: Long?, + ): Project? @Query( - "select project from Project project " - + "left join fetch project.organization " - + "where project.id = :id" + "select project from Project project " + + "left join fetch project.organization " + + "where project.id = :id", ) - fun findByIdWithOrganization(@Param("id") id: Long?): Project? + fun findByIdWithOrganization( + @Param("id") id: Long?, + ): Project? @Query( - "select project from Project project " - + "left join fetch project.sourceTypes " - + "left join fetch project.groups " - + "left join fetch project.organization " - + "where project.projectName = :name" + "select project from Project project " + + "left join fetch project.sourceTypes " + + "left join fetch project.groups " + + "left join fetch project.organization " + + "where project.projectName = :name", ) - fun findOneWithEagerRelationshipsByName(@Param("name") name: String?): Project? + fun findOneWithEagerRelationshipsByName( + @Param("name") name: String?, + ): Project? @Query( - "select project.id from Project project " - + "where project.projectName =:name" + "select project.id from Project project " + + "where project.projectName =:name", ) - fun findProjectIdByName(@Param("name") name: String): Long? + fun findProjectIdByName( + @Param("name") name: String, + ): Long? @Query( - "select project from Project project " - + "left join fetch project.groups " - + "where project.projectName = :name" + "select project from Project project " + + "left join fetch project.groups " + + "where project.projectName = :name", ) - fun findOneWithGroupsByName(@Param("name") name: String): Project? + fun findOneWithGroupsByName( + @Param("name") name: String, + ): Project? @Query("select project.sourceTypes from Project project WHERE project.id = :id") - fun findSourceTypesByProjectId(@Param("id") id: Long): List + fun findSourceTypesByProjectId( + @Param("id") id: Long, + ): List @Query( - "select distinct sourceType from Project project " - + "left join project.sourceTypes sourceType " - + "where project.id =:id " - + "and sourceType.id = :sourceTypeId " + "select distinct sourceType from Project project " + + "left join project.sourceTypes sourceType " + + "where project.id =:id " + + "and sourceType.id = :sourceTypeId ", ) fun findSourceTypeByProjectIdAndSourceTypeId( @Param("id") id: Long?, - @Param("sourceTypeId") sourceTypeId: Long? + @Param("sourceTypeId") sourceTypeId: Long?, ): SourceType? } diff --git a/src/main/java/org/radarbase/management/repository/RoleRepository.kt b/src/main/java/org/radarbase/management/repository/RoleRepository.kt index 7b6a47f35..bde4b4bbd 100644 --- a/src/main/java/org/radarbase/management/repository/RoleRepository.kt +++ b/src/main/java/org/radarbase/management/repository/RoleRepository.kt @@ -12,49 +12,55 @@ import org.springframework.data.repository.query.Param */ @Suppress("unused") @RepositoryDefinition(domainClass = Role::class, idClass = Long::class) -interface RoleRepository : JpaRepository, RevisionRepository { +interface RoleRepository : + JpaRepository, + RevisionRepository { @Query( - "select role from Role role inner join role.authority authority" - + " where authority.name = :authorityName" + "select role from Role role inner join role.authority authority" + + " where authority.name = :authorityName", ) - fun findRolesByAuthorityName(@Param("authorityName") authorityName: String?): List + fun findRolesByAuthorityName( + @Param("authorityName") authorityName: String?, + ): List @Query("select distinct role from Role role left join fetch role.authority") fun findAllWithEagerRelationships(): List @Query( - "select role from Role role join role.authority " - + "where role.organization.id = :organizationId " - + "and role.authority.name = :authorityName" + "select role from Role role join role.authority " + + "where role.organization.id = :organizationId " + + "and role.authority.name = :authorityName", ) fun findOneByOrganizationIdAndAuthorityName( @Param("organizationId") organizationId: Long?, - @Param("authorityName") authorityName: String? + @Param("authorityName") authorityName: String?, ): Role? @Query( - "select role from Role role join role.authority " - + "where role.project.id = :projectId " - + "and role.authority.name = :authorityName" + "select role from Role role join role.authority " + + "where role.project.id = :projectId " + + "and role.authority.name = :authorityName", ) fun findOneByProjectIdAndAuthorityName( @Param("projectId") projectId: Long?, - @Param("authorityName") authorityName: String? + @Param("authorityName") authorityName: String?, ): Role? @Query( - "select role from Role role join role.authority join role.project " - + "where role.project.projectName = :projectName " - + "and role.authority.name = :authorityName" + "select role from Role role join role.authority join role.project " + + "where role.project.projectName = :projectName " + + "and role.authority.name = :authorityName", ) fun findOneByProjectNameAndAuthorityName( @Param("projectName") projectName: String?, - @Param("authorityName") authorityName: String? + @Param("authorityName") authorityName: String?, ): Role? @Query( - "select role from Role role left join fetch role.authority " - + "where role.project.projectName = :projectName" + "select role from Role role left join fetch role.authority " + + "where role.project.projectName = :projectName", ) - fun findAllRolesByProjectName(@Param("projectName") projectName: String): List + fun findAllRolesByProjectName( + @Param("projectName") projectName: String, + ): List } diff --git a/src/main/java/org/radarbase/management/repository/SourceDataRepository.kt b/src/main/java/org/radarbase/management/repository/SourceDataRepository.kt index 83d3079b4..a9e48b5a3 100644 --- a/src/main/java/org/radarbase/management/repository/SourceDataRepository.kt +++ b/src/main/java/org/radarbase/management/repository/SourceDataRepository.kt @@ -10,6 +10,8 @@ import org.springframework.data.repository.history.RevisionRepository */ @Suppress("unused") @RepositoryDefinition(domainClass = SourceData::class, idClass = Long::class) -interface SourceDataRepository : JpaRepository, RevisionRepository { +interface SourceDataRepository : + JpaRepository, + RevisionRepository { fun findOneBySourceDataName(sourceDataName: String?): SourceData? } diff --git a/src/main/java/org/radarbase/management/repository/SourceRepository.kt b/src/main/java/org/radarbase/management/repository/SourceRepository.kt index fa23af0b9..03af0f42b 100644 --- a/src/main/java/org/radarbase/management/repository/SourceRepository.kt +++ b/src/main/java/org/radarbase/management/repository/SourceRepository.kt @@ -14,52 +14,76 @@ import java.util.* * Spring Data JPA repository for the Source entity. */ @RepositoryDefinition(domainClass = Source::class, idClass = Long::class) -interface SourceRepository : JpaRepository, RevisionRepository { +interface SourceRepository : + JpaRepository, + RevisionRepository { @Query( - value = "select source from Source source " - + "WHERE source.deleted = false", countQuery = "select count(source) from Source source " - + "WHERE source.deleted = false" + value = + "select source from Source source " + + "WHERE source.deleted = false", + countQuery = + "select count(source) from Source source " + + "WHERE source.deleted = false", ) override fun findAll(pageable: Pageable): Page @Query( - value = "select source from Source source " - + "WHERE source.deleted = false " - + "AND source.project.id = :projectId", countQuery = "select count(source) from Source source " - + "WHERE source.deleted = false " - + "AND source.project.id = :projectId" + value = + "select source from Source source " + + "WHERE source.deleted = false " + + "AND source.project.id = :projectId", + countQuery = + "select count(source) from Source source " + + "WHERE source.deleted = false " + + "AND source.project.id = :projectId", ) - fun findAllSourcesByProjectId(pageable: Pageable, @Param("projectId") projectId: Long): Page + fun findAllSourcesByProjectId( + pageable: Pageable, + @Param("projectId") projectId: Long, + ): Page @Query( - value = "select source from Source source " - + "WHERE source.deleted = false " - + "AND source.project.id = :projectId " - + "AND source.assigned = :assigned", countQuery = "select count(source) from Source source " - + "WHERE source.deleted = false " - + "AND source.project.id = :projectId " - + "AND source.assigned = :assigned" + value = + "select source from Source source " + + "WHERE source.deleted = false " + + "AND source.project.id = :projectId " + + "AND source.assigned = :assigned", + countQuery = + "select count(source) from Source source " + + "WHERE source.deleted = false " + + "AND source.project.id = :projectId " + + "AND source.assigned = :assigned", ) fun findAllSourcesByProjectIdAndAssigned( @Param("projectId") projectId: Long?, - @Param("assigned") assigned: Boolean? + @Param("assigned") assigned: Boolean?, ): List @Query( - value = "select source from Source source " - + "WHERE source.deleted = false " - + "AND source.sourceId = :sourceId", countQuery = "select count(source) from Source source " - + "WHERE source.deleted = false " - + "AND source.sourceId = :sourceId" + value = + "select source from Source source " + + "WHERE source.deleted = false " + + "AND source.sourceId = :sourceId", + countQuery = + "select count(source) from Source source " + + "WHERE source.deleted = false " + + "AND source.sourceId = :sourceId", ) - fun findOneBySourceId(@Param("sourceId") sourceId: UUID?): Source? + fun findOneBySourceId( + @Param("sourceId") sourceId: UUID?, + ): Source? @Query( - value = "select source from Source source " - + "WHERE source.deleted = false " - + "AND source.sourceName = :sourceName", countQuery = "select count(source) from Source source " - + "WHERE source.deleted = false " - + "AND source.sourceName = :sourceName" + value = + "select source from Source source " + + "WHERE source.deleted = false " + + "AND source.sourceName = :sourceName", + countQuery = + "select count(source) from Source source " + + "WHERE source.deleted = false " + + "AND source.sourceName = :sourceName", ) - fun findOneBySourceName(@Param("sourceName") sourceName: String?): Source? + fun findOneBySourceName( + @Param("sourceName") sourceName: String?, + ): Source? } diff --git a/src/main/java/org/radarbase/management/repository/SourceTypeRepository.kt b/src/main/java/org/radarbase/management/repository/SourceTypeRepository.kt index 69686686f..38aa2bd5e 100644 --- a/src/main/java/org/radarbase/management/repository/SourceTypeRepository.kt +++ b/src/main/java/org/radarbase/management/repository/SourceTypeRepository.kt @@ -13,57 +13,65 @@ import org.springframework.data.repository.query.Param */ @Suppress("unused") @RepositoryDefinition(domainClass = SourceType::class, idClass = Long::class) -interface SourceTypeRepository : JpaRepository, RevisionRepository { +interface SourceTypeRepository : + JpaRepository, + RevisionRepository { @Query( - "select distinct sourceType from SourceType sourceType left join fetch sourceType" - + ".sourceData" + "select distinct sourceType from SourceType sourceType left join fetch sourceType" + + ".sourceData", ) fun findAllWithEagerRelationships(): List @Query( - "select case when count(sourceType) > 0 then true else false end " - + "from SourceType sourceType " - + "where sourceType.producer = :producer " - + "and sourceType.model = :model " - + "and sourceType.catalogVersion = :version" + "select case when count(sourceType) > 0 then true else false end " + + "from SourceType sourceType " + + "where sourceType.producer = :producer " + + "and sourceType.model = :model " + + "and sourceType.catalogVersion = :version", ) fun hasOneByProducerAndModelAndVersion( - @Param("producer") producer: String, @Param("model") model: String, - @Param("version") version: String + @Param("producer") producer: String, + @Param("model") model: String, + @Param("version") version: String, ): Boolean @Query( - "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " - + "where sourceType.producer = :producer " - + "and sourceType.model = :model " - + "and sourceType.catalogVersion = :version" + "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " + + "where sourceType.producer = :producer " + + "and sourceType.model = :model " + + "and sourceType.catalogVersion = :version", ) fun findOneWithEagerRelationshipsByProducerAndModelAndVersion( - @Param("producer") producer: String, @Param("model") model: String, - @Param("version") version: String + @Param("producer") producer: String, + @Param("model") model: String, + @Param("version") version: String, ): SourceType? @Query( - "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " - + "where sourceType.producer =:producer" + "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " + + "where sourceType.producer =:producer", ) - fun findWithEagerRelationshipsByProducer(@Param("producer") producer: String): List + fun findWithEagerRelationshipsByProducer( + @Param("producer") producer: String, + ): List @Query( - "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " - + "where sourceType.producer =:producer and sourceType.model =:model" + "select sourceType from SourceType sourceType left join fetch sourceType.sourceData " + + "where sourceType.producer =:producer and sourceType.model =:model", ) fun findWithEagerRelationshipsByProducerAndModel( - @Param("producer") producer: String, @Param("model") model: String + @Param("producer") producer: String, + @Param("model") model: String, ): List @Query( - "select distinct sourceType.projects from SourceType sourceType left join sourceType" - + ".projects where sourceType.producer =:producer and sourceType.model =:model " - + "and sourceType.catalogVersion = :version" + "select distinct sourceType.projects from SourceType sourceType left join sourceType" + + ".projects where sourceType.producer =:producer and sourceType.model =:model " + + "and sourceType.catalogVersion = :version", ) fun findProjectsBySourceType( @Param("producer") producer: String, - @Param("model") model: String, @Param("version") version: String + @Param("model") model: String, + @Param("version") version: String, ): List } diff --git a/src/main/java/org/radarbase/management/repository/SubjectRepository.kt b/src/main/java/org/radarbase/management/repository/SubjectRepository.kt index 15c6ee95f..19474f27d 100644 --- a/src/main/java/org/radarbase/management/repository/SubjectRepository.kt +++ b/src/main/java/org/radarbase/management/repository/SubjectRepository.kt @@ -18,111 +18,130 @@ import java.util.* */ @Suppress("unused") @RepositoryDefinition(domainClass = Subject::class, idClass = Long::class) -interface SubjectRepository : JpaRepository, RevisionRepository, +interface SubjectRepository : + JpaRepository, + RevisionRepository, JpaSpecificationExecutor { @Query( - "SELECT count(*) from Subject subject " - + "WHERE subject.group.id = :group_id" + "SELECT count(*) from Subject subject " + + "WHERE subject.group.id = :group_id", ) - fun countByGroupId(@Param("group_id") groupId: Long?): Long + fun countByGroupId( + @Param("group_id") groupId: Long?, + ): Long @Query( - value = "select distinct subject from Subject subject left join fetch subject.sources " - + "left join fetch subject.user user " - + "join user.roles roles where roles.project.projectName = :projectName and roles" - + ".authority.name in :authorities", countQuery = "select distinct count(subject) from Subject subject " - + "left join subject.user user left join user.roles roles " - + "where roles.project.projectName = :projectName and roles" - + ".authority.name in :authorities" + value = + "select distinct subject from Subject subject left join fetch subject.sources " + + "left join fetch subject.user user " + + "join user.roles roles where roles.project.projectName = :projectName and roles" + + ".authority.name in :authorities", + countQuery = + "select distinct count(subject) from Subject subject " + + "left join subject.user user left join user.roles roles " + + "where roles.project.projectName = :projectName and roles" + + ".authority.name in :authorities", ) fun findAllByProjectNameAndAuthoritiesIn( pageable: Pageable?, @Param("projectName") projectName: String?, - @Param("authorities") authorities: List? + @Param("authorities") authorities: List?, ): Page? @Query( - "select subject from Subject subject " - + "left join fetch subject.sources " - + "WHERE subject.user.login = :login" + "select subject from Subject subject " + + "left join fetch subject.sources " + + "WHERE subject.user.login = :login", ) - fun findOneWithEagerBySubjectLogin(@Param("login") login: String?): Subject? + fun findOneWithEagerBySubjectLogin( + @Param("login") login: String?, + ): Subject? @Query( - "select subject from Subject subject " - + "WHERE subject.user.login in :logins" + "select subject from Subject subject " + + "WHERE subject.user.login in :logins", ) - fun findAllBySubjectLogins(@Param("logins") logins: List): List + fun findAllBySubjectLogins( + @Param("logins") logins: List, + ): List @Modifying @Query( - "UPDATE Subject subject " - + "SET subject.group.id = :groupId " - + "WHERE subject.id in :ids" + "UPDATE Subject subject " + + "SET subject.group.id = :groupId " + + "WHERE subject.id in :ids", ) fun setGroupIdByIds( @Param("groupId") groupId: Long, - @Param("ids") ids: List + @Param("ids") ids: List, ) @Modifying @Query( - "UPDATE Subject subject " - + "SET subject.group.id = null " - + "WHERE subject.id in :ids" + "UPDATE Subject subject " + + "SET subject.group.id = null " + + "WHERE subject.id in :ids", + ) + fun unsetGroupIdByIds( + @Param("ids") ids: List, ) - fun unsetGroupIdByIds(@Param("ids") ids: List) @Query("select subject.sources from Subject subject WHERE subject.id = :id") - fun findSourcesBySubjectId(@Param("id") id: Long): List + fun findSourcesBySubjectId( + @Param("id") id: Long, + ): List @Query("select subject.sources from Subject subject WHERE subject.user.login = :login") - fun findSourcesBySubjectLogin(@Param("login") login: String?): List + fun findSourcesBySubjectLogin( + @Param("login") login: String?, + ): List @Query( - "select distinct subject from Subject subject left join fetch subject.sources " - + "left join fetch subject.user user " - + "join user.roles roles where roles.project.projectName = :projectName " - + "and subject.externalId = :externalId" + "select distinct subject from Subject subject left join fetch subject.sources " + + "left join fetch subject.user user " + + "join user.roles roles where roles.project.projectName = :projectName " + + "and subject.externalId = :externalId", ) fun findOneByProjectNameAndExternalId( @Param("projectName") projectName: String?, - @Param("externalId") externalId: String? + @Param("externalId") externalId: String?, ): Subject? @Query( - "select distinct subject from Subject subject left join fetch subject.sources " - + "left join fetch subject.user user " - + "join user.roles roles where roles.project.projectName = :projectName " - + "and roles.authority.name in :authorities " - + "and subject.externalId = :externalId" + "select distinct subject from Subject subject left join fetch subject.sources " + + "left join fetch subject.user user " + + "join user.roles roles where roles.project.projectName = :projectName " + + "and roles.authority.name in :authorities " + + "and subject.externalId = :externalId", ) fun findOneByProjectNameAndExternalIdAndAuthoritiesIn( - @Param("projectName") projectName: String, @Param("externalId") externalId: String, - @Param("authorities") authorities: List + @Param("projectName") projectName: String, + @Param("externalId") externalId: String, + @Param("authorities") authorities: List, ): Subject? @Query( - "select subject.sources from Subject subject left join subject.sources sources " - + "join sources.sourceType sourceType " - + "where sourceType.producer = :producer " - + "and sourceType.model = :model " - + "and sourceType.catalogVersion =:version " - + "and subject.user.login = :login" + "select subject.sources from Subject subject left join subject.sources sources " + + "join sources.sourceType sourceType " + + "where sourceType.producer = :producer " + + "and sourceType.model = :model " + + "and sourceType.catalogVersion =:version " + + "and subject.user.login = :login", ) fun findSubjectSourcesBySourceType( @Param("login") login: String?, - @Param("producer") producer: String?, @Param("model") model: String?, - @Param("version") version: String? + @Param("producer") producer: String?, + @Param("model") model: String?, + @Param("version") version: String?, ): List? @Query( - "select distinct subject.sources from Subject subject left join subject.sources sources " - + "where sources.sourceId= :sourceId " - + "and subject.user.login = :login" + "select distinct subject.sources from Subject subject left join subject.sources sources " + + "where sources.sourceId= :sourceId " + + "and subject.user.login = :login", ) fun findSubjectSourcesBySourceId( @Param("login") login: String?, - @Param("sourceId") sourceId: UUID? + @Param("sourceId") sourceId: UUID?, ): Source? } diff --git a/src/main/java/org/radarbase/management/repository/UserRepository.kt b/src/main/java/org/radarbase/management/repository/UserRepository.kt index 3543e5495..6e929acf5 100644 --- a/src/main/java/org/radarbase/management/repository/UserRepository.kt +++ b/src/main/java/org/radarbase/management/repository/UserRepository.kt @@ -17,27 +17,36 @@ import org.springframework.stereotype.Component */ @RepositoryDefinition(domainClass = User::class, idClass = Long::class) @Component -interface UserRepository : JpaRepository, RevisionRepository, +interface UserRepository : + JpaRepository, + RevisionRepository, JpaSpecificationExecutor { fun findOneByActivationKey(activationKey: String): User? + fun findAllByActivated(activated: Boolean): List @Query( - "select user from User user " - + "left join fetch user.roles roles where " - + "roles.authority.name not in :authorities " - + "and user.activated= :activated" + "select user from User user " + + "left join fetch user.roles roles where " + + "roles.authority.name not in :authorities " + + "and user.activated= :activated", ) fun findAllByActivatedAndAuthoritiesNot( @Param("activated") activated: Boolean, - @Param("authorities") authorities: List + @Param("authorities") authorities: List, ): List fun findOneByResetKey(resetKey: String): User? + fun findOneByEmail(email: String): User? + fun findOneByLogin(login: String?): User? @EntityGraph(attributePaths = ["roles", "roles.authority.name"]) fun findOneWithRolesByLogin(login: String): User? - fun findAllByLoginNot(pageable: Pageable, login: String): Page + + fun findAllByLoginNot( + pageable: Pageable, + login: String, + ): Page } diff --git a/src/main/java/org/radarbase/management/repository/filters/PredicateBuilder.kt b/src/main/java/org/radarbase/management/repository/filters/PredicateBuilder.kt index dcda7bba6..2116fd434 100644 --- a/src/main/java/org/radarbase/management/repository/filters/PredicateBuilder.kt +++ b/src/main/java/org/radarbase/management/repository/filters/PredicateBuilder.kt @@ -18,7 +18,9 @@ import javax.persistence.criteria.Path import javax.persistence.criteria.Predicate import javax.persistence.criteria.Root -class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { +class PredicateBuilder( + val criteriaBuilder: CriteriaBuilder, +) { private val predicates: MutableList init { @@ -40,36 +42,37 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { /** * Build the predicates as an AND predicate. */ - fun toAndPredicate(): Predicate? { - return if (predicates.size == 1) { + fun toAndPredicate(): Predicate? = + if (predicates.size == 1) { predicates[0] } else if (predicates.isNotEmpty()) { criteriaBuilder.and(*predicates.toTypedArray()) } else { null } - } /** * Build the predicates as an AND predicate. */ - fun toOrPredicate(): Predicate? { - return if (predicates.size == 1) { + fun toOrPredicate(): Predicate? = + if (predicates.size == 1) { predicates[0] } else if (!predicates.isEmpty()) { criteriaBuilder.or(*predicates.toTypedArray()) } else { null } - } /** * Add an equal criteria to predicates if value is not null or empty. * @param path entity path * @param value value to compare with * @param type of field. - */ - fun equal(path: Supplier?>, value: T) { + */ + fun equal( + path: Supplier?>, + value: T, + ) { if (isValidValue(value)) { add(criteriaBuilder.equal(path.get(), value)) } @@ -80,8 +83,11 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { * @param path entity path * @param value value to compare with * @param type of field. - */ - fun equal(path: Expression?, value: T) { + */ + fun equal( + path: Expression?, + value: T, + ) { if (isValidValue(value)) { add(criteriaBuilder.equal(path, value)) } @@ -92,13 +98,16 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { * @param path entity path * @param value value to compare with */ - fun likeLower(path: Supplier?>, value: String) { + fun likeLower( + path: Supplier?>, + value: String, + ) { if (isValidValue(value)) { add( criteriaBuilder.like( criteriaBuilder.lower(path.get()), - "%" + value.trim { it <= ' ' }.lowercase() + "%" - ) + "%" + value.trim { it <= ' ' }.lowercase() + "%", + ), ) } } @@ -108,13 +117,16 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { * @param path entity path * @param value value to compare with */ - fun likeLower(path: Expression?, value: String?) { + fun likeLower( + path: Expression?, + value: String?, + ) { if (isValidValue(value)) { add( criteriaBuilder.like( criteriaBuilder.lower(path), - "%" + value!!.trim { it <= ' ' }.lowercase() + "%" - ) + "%" + value!!.trim { it <= ' ' }.lowercase() + "%", + ), ) } } @@ -126,8 +138,9 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { * @param attributeValue value to compare with using a like query. */ fun attributeLike( - root: Root<*>, attributeKey: String?, - attributeValue: String? + root: Root<*>, + attributeKey: String?, + attributeValue: String?, ) { if (isValidValue(attributeValue)) { val attributesJoin = root.joinMap("attributes", JoinType.LEFT) @@ -136,18 +149,24 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { criteriaBuilder.equal(attributesJoin.key(), attributeKey), criteriaBuilder.like( attributesJoin.value(), - "%$attributeValue%" - ) - ) + "%$attributeValue%", + ), + ), ) } } - fun `in`(expr: Expression<*>, other: Expression<*>?) { + fun `in`( + expr: Expression<*>, + other: Expression<*>?, + ) { add(expr.`in`(other)) } - fun `in`(expr: Expression<*>, other: Collection<*>?) { + fun `in`( + expr: Expression<*>, + other: Collection<*>?, + ) { add(expr.`in`(other)) } @@ -157,7 +176,8 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { * @param range range that should be matched. */ fun ?> range( - path: Path?, range: CriteriaRange? + path: Path?, + range: CriteriaRange?, ) { if (range == null || range.isEmpty) { return @@ -187,12 +207,12 @@ class PredicateBuilder(val criteriaBuilder: CriteriaBuilder) { } return if (value is String) { !value.isBlank() && value != "null" - } else true + } else { + true + } } - fun newBuilder(): PredicateBuilder { - return PredicateBuilder(criteriaBuilder) - } + fun newBuilder(): PredicateBuilder = PredicateBuilder(criteriaBuilder) val isEmpty: Boolean get() = predicates.isEmpty() diff --git a/src/main/java/org/radarbase/management/repository/filters/SubjectSpecification.kt b/src/main/java/org/radarbase/management/repository/filters/SubjectSpecification.kt index e53c7b0c8..abcf37923 100644 --- a/src/main/java/org/radarbase/management/repository/filters/SubjectSpecification.kt +++ b/src/main/java/org/radarbase/management/repository/filters/SubjectSpecification.kt @@ -23,8 +23,9 @@ import javax.persistence.criteria.Path import javax.persistence.criteria.Predicate import javax.persistence.criteria.Root - -class SubjectSpecification(criteria: SubjectCriteria) : Specification { +class SubjectSpecification( + criteria: SubjectCriteria, +) : Specification { private val dateOfBirth: LocalDateCriteriaRange? private val enrollmentDate: ZonedDateTimeCriteriaRange? private val groupId: Long? @@ -43,9 +44,10 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification * @param criteria criteria to use for the specification. */ init { - authority = criteria.authority - .map { obj: SubjectAuthority -> obj.name } - .toSet() + authority = + criteria.authority + .map { obj: SubjectAuthority -> obj.name } + .toSet() dateOfBirth = criteria.dateOfBirth enrollmentDate = criteria.enrollmentDate groupId = criteria.groupId @@ -56,21 +58,23 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification externalId = criteria.externalId subjectId = criteria.login sort = criteria.parsedSort - sortLastValues = if (last != null) { - sort - ?.mapNotNull { o: SubjectSortOrder -> getLastValue(o.sortBy) } - ?.toList() - } else { - null - } + sortLastValues = + if (last != null) { + sort + ?.mapNotNull { o: SubjectSortOrder -> getLastValue(o.sortBy) } + ?.toList() + } else { + null + } } override fun toPredicate( - root: Root, query: CriteriaQuery<*>, - builder: CriteriaBuilder + root: Root, + query: CriteriaQuery<*>, + builder: CriteriaBuilder, ): Predicate? { if (root == null || query == null || builder == null) { - return null; + return null } query.distinct(true) root.alias("subject") @@ -79,8 +83,9 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification val predicates = PredicateBuilder(builder) addRolePredicates(userJoin, predicates) predicates.attributeLike( - root, "Human-readable-identifier", - humanReadableIdentifier + root, + "Human-readable-identifier", + humanReadableIdentifier, ) predicates.likeLower(root.get("externalId"), externalId) predicates.equal(root.get("group"), groupId) @@ -93,13 +98,18 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification return predicates.toAndPredicate()!! } - private fun filterLastValues(root: Root, builder: CriteriaBuilder): Predicate? { - val lastPredicates = arrayOfNulls( - sort!!.size - ) - val paths: MutableList> = ArrayList( - sort.size - ) + private fun filterLastValues( + root: Root, + builder: CriteriaBuilder, + ): Predicate? { + val lastPredicates = + arrayOfNulls( + sort!!.size, + ) + val paths: MutableList> = + ArrayList( + sort.size, + ) for (order in sort) { paths.add(getPropertyPath(order.sortBy, root)) } @@ -110,16 +120,19 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification lastAndPredicates!![j] = builder.equal(paths[j], sortLastValues!![j]) } val order = sort[i] - val saveVal = sortLastValues - ?: throw BadRequestException( - "No last value given", - EntityName.SUBJECT, ErrorConstants.ERR_VALIDATION - ) - val currentSort: Predicate? = if (order.direction.isAscending) { - builder.greaterThan(paths[i], saveVal[i]) - } else { - builder.lessThan(paths[i], saveVal[i]) - } + val saveVal = + sortLastValues + ?: throw BadRequestException( + "No last value given", + EntityName.SUBJECT, + ErrorConstants.ERR_VALIDATION, + ) + val currentSort: Predicate? = + if (order.direction.isAscending) { + builder.greaterThan(paths[i], saveVal[i]) + } else { + builder.lessThan(paths[i], saveVal[i]) + } if (lastAndPredicates != null) { lastAndPredicates[i] = currentSort lastPredicates[i] = builder.and(*lastAndPredicates) @@ -135,8 +148,10 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification } private fun addContentPredicates( - predicates: PredicateBuilder, builder: CriteriaBuilder, - root: Root, queryResult: Class<*> + predicates: PredicateBuilder, + builder: CriteriaBuilder, + root: Root, + queryResult: Class<*>, ) { // Don't add content for count queries. if (Long::class.javaObjectType == queryResult) { @@ -150,29 +165,36 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification } private fun getLastValue(property: SubjectSortBy): String? { - val result = when (property) { - SubjectSortBy.ID -> last?.id - SubjectSortBy.USER_LOGIN -> last?.login - SubjectSortBy.EXTERNAL_ID -> last?.login - } + val result = + when (property) { + SubjectSortBy.ID -> last?.id + SubjectSortBy.USER_LOGIN -> last?.login + SubjectSortBy.EXTERNAL_ID -> last?.login + } if (property.isUnique && result == null) { throw BadRequestException( "No last value given for sort property $property", - EntityName.SUBJECT, ErrorConstants.ERR_VALIDATION + EntityName.SUBJECT, + ErrorConstants.ERR_VALIDATION, ) } return result } - private fun getPropertyPath(property: SubjectSortBy, root: Root): Path { - return when (property) { + private fun getPropertyPath( + property: SubjectSortBy, + root: Root, + ): Path = + when (property) { SubjectSortBy.ID -> root.get("id") SubjectSortBy.USER_LOGIN -> root.get("user").get("login") SubjectSortBy.EXTERNAL_ID -> root.get("externalId") } - } - private fun addRolePredicates(userJoin: Join, predicates: PredicateBuilder) { + private fun addRolePredicates( + userJoin: Join, + predicates: PredicateBuilder, + ) { val rolesJoin = userJoin.join("roles") rolesJoin.alias("roles") predicates.equal({ rolesJoin.get("project").get("projectName") }, projectName) @@ -183,7 +205,7 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification private fun getSortOrder( root: Root, - builder: CriteriaBuilder + builder: CriteriaBuilder, ): List? { return sort ?.map { order: SubjectSortOrder -> @@ -193,7 +215,6 @@ class SubjectSpecification(criteria: SubjectCriteria) : Specification } else { return listOf(builder.desc(path)) } - } - ?.toList() + }?.toList() } } diff --git a/src/main/java/org/radarbase/management/repository/filters/UserFilter.kt b/src/main/java/org/radarbase/management/repository/filters/UserFilter.kt index 4c6979ef4..cd598d3c2 100644 --- a/src/main/java/org/radarbase/management/repository/filters/UserFilter.kt +++ b/src/main/java/org/radarbase/management/repository/filters/UserFilter.kt @@ -25,9 +25,11 @@ class UserFilter : Specification { var organization: String? = null var authority: String? = null var isIncludeUpperLevels = false + override fun toPredicate( - root: Root, @Nonnull query: CriteriaQuery<*>, - @Nonnull builder: CriteriaBuilder + root: Root, + @Nonnull query: CriteriaQuery<*>, + @Nonnull builder: CriteriaBuilder, ): Predicate { val predicates = PredicateBuilder(builder) predicates.likeLower(root.get("login"), login) @@ -40,16 +42,20 @@ class UserFilter : Specification { } private fun filterRoles( - predicates: PredicateBuilder, roleJoin: Join, - query: CriteriaQuery<*> + predicates: PredicateBuilder, + roleJoin: Join, + query: CriteriaQuery<*>, ) { - var authoritiesFiltered = Stream.of(*RoleAuthority.values()) - .filter { r: RoleAuthority -> !r.isPersonal } + var authoritiesFiltered = + Stream + .of(*RoleAuthority.values()) + .filter { r: RoleAuthority -> !r.isPersonal } var allowNoRole = true if (predicates.isValidValue(authority)) { val authorityUpper = authority!!.uppercase() - authoritiesFiltered = authoritiesFiltered - .filter { r: RoleAuthority? -> r != null && r.authority.contains(authorityUpper) } + authoritiesFiltered = + authoritiesFiltered + .filter { r: RoleAuthority? -> r != null && r.authority.contains(authorityUpper) } allowNoRole = false } val authoritiesAllowed = authoritiesFiltered.toList() @@ -67,7 +73,7 @@ class UserFilter : Specification { roleJoin: Join, query: CriteriaQuery<*>, authoritiesAllowed: List, - allowNoRole: Boolean + allowNoRole: Boolean, ) { val authorityPredicates = predicates.newBuilder() var allowNoRoleInScope = allowNoRole @@ -76,26 +82,36 @@ class UserFilter : Specification { allowNoRoleInScope = false // Is project admin entitySubquery( - RoleAuthority.Scope.PROJECT, roleJoin, - query, authorityPredicates, authoritiesAllowed + RoleAuthority.Scope.PROJECT, + roleJoin, + query, + authorityPredicates, + authoritiesAllowed, ) { b: PredicateBuilder?, proj: From<*, *> -> b!!.likeLower(proj.get("projectName"), projectName) } // Is organization admin for organization above current project if (isIncludeUpperLevels) { entitySubquery( - RoleAuthority.Scope.ORGANIZATION, roleJoin, - query, authorityPredicates, authoritiesAllowed + RoleAuthority.Scope.ORGANIZATION, + roleJoin, + query, + authorityPredicates, + authoritiesAllowed, ) { b: PredicateBuilder?, org: From<*, *> -> b!!.likeLower( - org.join("projects").get("projectName"), projectName + org.join("projects").get("projectName"), + projectName, ) } } } else if (predicates.isValidValue(organization)) { allowNoRoleInScope = false entitySubquery( - RoleAuthority.Scope.ORGANIZATION, roleJoin, - query, authorityPredicates, authoritiesAllowed + RoleAuthority.Scope.ORGANIZATION, + roleJoin, + query, + authorityPredicates, + authoritiesAllowed, ) { b: PredicateBuilder?, org: From<*, *> -> b!!.likeLower(org.get("name"), organization) } } if (authorityPredicates.isEmpty) { @@ -104,8 +120,10 @@ class UserFilter : Specification { } else if (isIncludeUpperLevels) { // is sys admin addAllowedAuthorities( - authorityPredicates, roleJoin, authoritiesAllowed, - RoleAuthority.Scope.GLOBAL + authorityPredicates, + roleJoin, + authoritiesAllowed, + RoleAuthority.Scope.GLOBAL, ) } if (allowNoRoleInScope) { @@ -118,15 +136,16 @@ class UserFilter : Specification { predicates: PredicateBuilder?, roleJoin: Join, authorities: List, - scope: RoleAuthority.Scope? + scope: RoleAuthority.Scope?, ): Boolean { var authorityStream = authorities.stream() if (scope != null) { authorityStream = authorityStream.filter { r: RoleAuthority -> r.scope === scope } } - val authorityNames = authorityStream - .map(RoleAuthority::authority) - .toList() + val authorityNames = + authorityStream + .map(RoleAuthority::authority) + .toList() return if (!authorityNames.isEmpty()) { predicates!!.`in`(roleJoin.get("authority").get("name"), authorityNames) true @@ -142,7 +161,7 @@ class UserFilter : Specification { query: CriteriaQuery<*>, predicates: PredicateBuilder?, allowedRoles: List, - queryMatch: BiConsumer> + queryMatch: BiConsumer>, ) { val authorityPredicates = predicates!!.newBuilder() if (!addAllowedAuthorities(authorityPredicates, roleJoin, allowedRoles, scope)) { @@ -150,25 +169,28 @@ class UserFilter : Specification { } val subQuery = query.subquery(Long::class.java) subQuery.distinct(true) - val orgRoot = subQuery.from( - when (scope) { - RoleAuthority.Scope.PROJECT -> Project::class.java - RoleAuthority.Scope.ORGANIZATION -> Organization::class.java - else -> throw IllegalStateException("Unknown role scope $scope") - } as Class<*> - ) + val orgRoot = + subQuery.from( + when (scope) { + RoleAuthority.Scope.PROJECT -> Project::class.java + RoleAuthority.Scope.ORGANIZATION -> Organization::class.java + else -> throw IllegalStateException("Unknown role scope $scope") + } as Class<*>, + ) subQuery.select(orgRoot.get("id")) val subqueryPredicates = predicates.newBuilder() queryMatch.accept(subqueryPredicates, orgRoot) subQuery.where(subqueryPredicates.toAndPredicate()) authorityPredicates.`in`( - roleJoin.get( - when (scope) { - RoleAuthority.Scope.ORGANIZATION -> "organization" - RoleAuthority.Scope.PROJECT -> "project" - else -> throw IllegalStateException("Unknown role scope $scope") - } - ).get("id"), subQuery + roleJoin + .get( + when (scope) { + RoleAuthority.Scope.ORGANIZATION -> "organization" + RoleAuthority.Scope.PROJECT -> "project" + else -> throw IllegalStateException("Unknown role scope $scope") + }, + ).get("id"), + subQuery, ) predicates.add(authorityPredicates.toAndPredicate()) } diff --git a/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.java b/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.java deleted file mode 100644 index d645f0cba..000000000 --- a/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.radarbase.management.security; - -import org.radarbase.auth.authorization.AuthorizationOracle; -import org.radarbase.auth.authorization.Permission; -import org.radarbase.auth.authorization.RoleAuthority; -import org.radarbase.management.domain.Role; -import org.radarbase.management.domain.Source; -import org.radarbase.management.repository.SubjectRepository; -import org.radarbase.management.repository.UserRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.stereotype.Component; - -import java.security.Principal; -import java.time.Instant; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; - -import static org.radarbase.auth.jwt.JwtTokenVerifier.GRANT_TYPE_CLAIM; -import static org.radarbase.auth.jwt.JwtTokenVerifier.ROLES_CLAIM; -import static org.radarbase.auth.jwt.JwtTokenVerifier.SOURCES_CLAIM; - -@Component -public class ClaimsTokenEnhancer implements TokenEnhancer, InitializingBean { - private static final Logger logger = LoggerFactory.getLogger(ClaimsTokenEnhancer.class); - - @Autowired - private SubjectRepository subjectRepository; - - @Autowired - private UserRepository userRepository; - - @Autowired - private AuditEventRepository auditEventRepository; - - @Autowired - private AuthorizationOracle authorizationOracle; - - @Value("${spring.application.name}") - private String appName; - - private static final String GRANT_TOKEN_EVENT = "GRANT_ACCESS_TOKEN"; - - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, - OAuth2Authentication authentication) { - logger.debug("Enhancing token of authentication {}", authentication); - - Map additionalInfo = new HashMap<>(); - - String userName = authentication.getName(); - - if (authentication.getPrincipal() instanceof Principal - || authentication.getPrincipal() instanceof UserDetails) { - // add the 'sub' claim in accordance with JWT spec - additionalInfo.put("sub", userName); - - Optional.ofNullable(userRepository.findOneByLogin(userName)).ifPresent(user -> { - var roles = user.roles.stream().map(role -> { - var auth = role.authority.name; - return switch (role.getRole().getScope()) { - case GLOBAL -> auth; - case ORGANIZATION -> role.organization.name + ":" + auth; - case PROJECT -> role.project.getProjectName() + ":" + auth; - }; - }).toList(); - additionalInfo.put(ROLES_CLAIM, roles); - - // Do not grant scopes that cannot be given to a user. - Set currentScopes = accessToken.getScope(); - Set newScopes = currentScopes.stream().filter(scope -> { - Permission permission = Permission.ofScope(scope); - var roleAuthorities = user.roles.stream().map(Role::getRole) - .collect(Collectors.toCollection(() -> - EnumSet.noneOf(RoleAuthority.class))); - return authorizationOracle.mayBeGranted(roleAuthorities, permission); - }).collect(Collectors.toCollection(TreeSet::new)); - - if (!newScopes.equals(currentScopes)) { - ((DefaultOAuth2AccessToken) accessToken).setScope(newScopes); - } - }); - - List assignedSources = subjectRepository.findSourcesBySubjectLogin(userName); - - List sourceIds = assignedSources.stream().map(s -> - s.sourceId.toString()).toList(); - additionalInfo.put(SOURCES_CLAIM, sourceIds); - } - // add iat and iss optional JWT claims - additionalInfo.put("iat", Instant.now().getEpochSecond()); - additionalInfo.put("iss", appName); - additionalInfo.put(GRANT_TYPE_CLAIM, authentication.getOAuth2Request().getGrantType()); - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); - - // HACK: since all granted tokens need to pass here, we can use this point to create an - // audit event for a granted token, there is an open issue about oauth2 audit events in - // spring security but it has been inactive for a long time: - // https://github.com/spring-projects/spring-security-oauth/issues/223 - Map auditData = auditData(accessToken, authentication); - auditEventRepository.add(new AuditEvent(userName, GRANT_TOKEN_EVENT, auditData)); - logger.info("[{}] for {}: {}", GRANT_TOKEN_EVENT, userName, auditData); - - return accessToken; - } - - @Override - public void afterPropertiesSet() throws Exception { - // nothing to do for now - } - - private Map auditData(OAuth2AccessToken accessToken, - OAuth2Authentication authentication) { - Map result = new HashMap<>(); - result.put("tokenType", accessToken.getTokenType()); - result.put("scope", String.join(", ", accessToken.getScope())); - result.put("expiresIn", Integer.toString(accessToken.getExpiresIn())); - result.putAll(accessToken.getAdditionalInformation()); - OAuth2Request request = authentication.getOAuth2Request(); - result.put("clientId", request.getClientId()); - result.put("grantType", request.getGrantType()); - return result; - } -} diff --git a/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.kt b/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.kt new file mode 100644 index 000000000..54e12bd1c --- /dev/null +++ b/src/main/java/org/radarbase/management/security/ClaimsTokenEnhancer.kt @@ -0,0 +1,153 @@ +package org.radarbase.management.security + +import org.radarbase.auth.authorization.AuthorizationOracle +import org.radarbase.auth.authorization.Permission.Companion.ofScope +import org.radarbase.auth.authorization.RoleAuthority +import org.radarbase.auth.jwt.JwtTokenVerifier +import org.radarbase.management.domain.Role +import org.radarbase.management.domain.User +import org.radarbase.management.repository.SubjectRepository +import org.radarbase.management.repository.UserRepository +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.InitializingBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.actuate.audit.AuditEvent +import org.springframework.boot.actuate.audit.AuditEventRepository +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken +import org.springframework.security.oauth2.common.OAuth2AccessToken +import org.springframework.security.oauth2.provider.OAuth2Authentication +import org.springframework.security.oauth2.provider.token.TokenEnhancer +import org.springframework.stereotype.Component +import java.security.Principal +import java.time.Instant +import java.util.* +import java.util.stream.Collectors + +@Component +class ClaimsTokenEnhancer : + TokenEnhancer, + InitializingBean { + @Autowired + private val subjectRepository: SubjectRepository? = null + + @Autowired + private val userRepository: UserRepository? = null + + @Autowired + private val auditEventRepository: AuditEventRepository? = null + + @Autowired + private val authorizationOracle: AuthorizationOracle? = null + + @Value("\${spring.application.name}") + private val appName: String? = null + + override fun enhance( + accessToken: OAuth2AccessToken, + authentication: OAuth2Authentication, + ): OAuth2AccessToken { + logger.debug("Enhancing token of authentication {}", authentication) + + val additionalInfo: MutableMap = HashMap() + + val userName = authentication.name + + if (authentication.principal is Principal || + authentication.principal is UserDetails + ) { + // add the 'sub' claim in accordance with JWT spec + additionalInfo["sub"] = userName + + Optional.ofNullable(userRepository!!.findOneByLogin(userName)).ifPresent { user: User -> + val roles = + user.roles.map { + val auth = it.authority!!.name + when (it.role!!.scope) { + RoleAuthority.Scope.GLOBAL -> auth + RoleAuthority.Scope.ORGANIZATION -> it.organization!!.name + ":" + auth + RoleAuthority.Scope.PROJECT -> it.project!!.projectName + ":" + auth + } + } + additionalInfo[JwtTokenVerifier.ROLES_CLAIM] = roles + + // Do not grant scopes that cannot be given to a user. + val currentScopes = accessToken.scope + val roleAuthorities = + user.roles + .stream() + .map(Role::role) + .collect( + Collectors.toCollection { + EnumSet.noneOf( + RoleAuthority::class.java, + ) + }, + ) + val newScopes: Set = + if (authorizationOracle == null) { + emptySet() + } else { + currentScopes + .filter { + val permission = ofScope(it) + with(authorizationOracle) { + roleAuthorities.mayBeGranted(permission) + } + }.toSet() + } + if (newScopes != currentScopes) { + (accessToken as DefaultOAuth2AccessToken).scope = newScopes + } + } + + val assignedSources = subjectRepository!!.findSourcesBySubjectLogin(userName) + + val sourceIds = assignedSources.map { it.sourceId.toString() } + additionalInfo[JwtTokenVerifier.SOURCES_CLAIM] = sourceIds + } + // add iat and iss optional JWT claims + additionalInfo["iat"] = Instant.now().epochSecond + additionalInfo["iss"] = appName + additionalInfo[JwtTokenVerifier.GRANT_TYPE_CLAIM] = authentication.oAuth2Request.grantType + (accessToken as DefaultOAuth2AccessToken).additionalInformation = additionalInfo + + // HACK: since all granted tokens need to pass here, we can use this point to create an + // audit event for a granted token, there is an open issue about oauth2 audit events in + // spring security but it has been inactive for a long time: + // https://github.com/spring-projects/spring-security-oauth/issues/223 + val auditData = auditData(accessToken, authentication) + auditEventRepository!!.add(AuditEvent(userName, GRANT_TOKEN_EVENT, auditData)) + logger.info("[{}] for {}: {}", GRANT_TOKEN_EVENT, userName, auditData) + + return accessToken + } + + @Throws(Exception::class) + override fun afterPropertiesSet() { + // nothing to do for now + } + + private fun auditData( + accessToken: OAuth2AccessToken, + authentication: OAuth2Authentication, + ): Map { + val result: MutableMap = HashMap() + result["tokenType"] = accessToken.tokenType + result["scope"] = java.lang.String.join(", ", accessToken.scope) + result["expiresIn"] = accessToken.expiresIn.toString() + result.putAll(accessToken.additionalInformation) + val request = authentication.oAuth2Request + result["clientId"] = request.clientId + result["grantType"] = request.grantType + return result + } + + companion object { + private val logger: Logger = LoggerFactory.getLogger(ClaimsTokenEnhancer::class.java) + + private const val GRANT_TOKEN_EVENT = "GRANT_ACCESS_TOKEN" + } +} diff --git a/src/main/java/org/radarbase/management/security/Constants.kt b/src/main/java/org/radarbase/management/security/Constants.kt index f0ffc5e54..546905393 100644 --- a/src/main/java/org/radarbase/management/security/Constants.kt +++ b/src/main/java/org/radarbase/management/security/Constants.kt @@ -4,7 +4,7 @@ package org.radarbase.management.security * Application constants. */ object Constants { - //Regex for acceptable logins + // Regex for acceptable logins const val ENTITY_ID_REGEX = "^[_'.@A-Za-z0-9- ]*$" const val TOKEN_NAME_REGEX = "^[A-Za-z0-9.-]*$" const val SYSTEM_ACCOUNT = "system" diff --git a/src/main/java/org/radarbase/management/security/DomainUserDetailsService.kt b/src/main/java/org/radarbase/management/security/DomainUserDetailsService.kt index b3fc07064..ef343493f 100644 --- a/src/main/java/org/radarbase/management/security/DomainUserDetailsService.kt +++ b/src/main/java/org/radarbase/management/security/DomainUserDetailsService.kt @@ -15,28 +15,29 @@ import org.springframework.transaction.annotation.Transactional */ @Component("userDetailsService") class DomainUserDetailsService( - private val userRepository: UserRepository + private val userRepository: UserRepository, ) : UserDetailsService { @Transactional override fun loadUserByUsername(login: String): UserDetails { log.debug("Authenticating {}", login) val lowercaseLogin = login.lowercase() - val user = userRepository.findOneWithRolesByLogin(lowercaseLogin) - ?: throw UsernameNotFoundException( - "User $lowercaseLogin was not found in the database" + val user = + userRepository.findOneWithRolesByLogin(lowercaseLogin) + ?: throw UsernameNotFoundException( + "User $lowercaseLogin was not found in the database", ) if (!user.activated) { throw UserNotActivatedException( - "User " + lowercaseLogin - + " was not activated" + "User " + lowercaseLogin + + " was not activated", ) } val grantedAuthorities = - user.authorities!!.map { authority -> SimpleGrantedAuthority(authority) } + user.authorities.map { authority -> SimpleGrantedAuthority(authority) } return User( lowercaseLogin, user.password, - grantedAuthorities + grantedAuthorities, ) } diff --git a/src/main/java/org/radarbase/management/security/Http401UnauthorizedEntryPoint.kt b/src/main/java/org/radarbase/management/security/Http401UnauthorizedEntryPoint.kt index dbeeea979..67b923c89 100644 --- a/src/main/java/org/radarbase/management/security/Http401UnauthorizedEntryPoint.kt +++ b/src/main/java/org/radarbase/management/security/Http401UnauthorizedEntryPoint.kt @@ -34,8 +34,9 @@ class Http401UnauthorizedEntryPoint : AuthenticationEntryPoint { */ @Throws(IOException::class) override fun commence( - request: HttpServletRequest, response: HttpServletResponse, - arg2: AuthenticationException + request: HttpServletRequest, + response: HttpServletResponse, + arg2: AuthenticationException, ) { log.debug("Pre-authenticated entry point called. Rejecting access") response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied") diff --git a/src/main/java/org/radarbase/management/security/JwtAuthenticationFilter.kt b/src/main/java/org/radarbase/management/security/JwtAuthenticationFilter.kt index f667c33f8..3d2077412 100644 --- a/src/main/java/org/radarbase/management/security/JwtAuthenticationFilter.kt +++ b/src/main/java/org/radarbase/management/security/JwtAuthenticationFilter.kt @@ -1,14 +1,5 @@ package org.radarbase.management.security -import io.ktor.http.* -import java.io.IOException -import java.time.Instant -import javax.annotation.Nonnull -import javax.servlet.FilterChain -import javax.servlet.ServletException -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse -import javax.servlet.http.HttpSession import org.radarbase.auth.authentication.TokenValidator import org.radarbase.auth.authorization.AuthorityReference import org.radarbase.auth.authorization.RoleAuthority @@ -28,7 +19,14 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication import org.springframework.security.web.util.matcher.AntPathRequestMatcher import org.springframework.web.cors.CorsUtils import org.springframework.web.filter.OncePerRequestFilter - +import java.io.IOException +import java.time.Instant +import javax.annotation.Nonnull +import javax.servlet.FilterChain +import javax.servlet.ServletException +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import javax.servlet.http.HttpSession /** * Authentication filter using given validator. @@ -37,189 +35,203 @@ import org.springframework.web.filter.OncePerRequestFilter * @param userRepository user repository to retrieve user details from. * @param isOptional do not fail if no authentication is provided */ -class JwtAuthenticationFilter @JvmOverloads constructor( - private val validator: TokenValidator, - private val authenticationManager: AuthenticationManager, - private val userRepository: UserRepository, - private val isOptional: Boolean = false -) : OncePerRequestFilter() { - private val ignoreUrls: MutableList = mutableListOf() - - /** - * Do not use JWT authentication for given paths and HTTP method. - * @param method HTTP method - * @param antPatterns Ant wildcard pattern - * @return the current filter - */ - fun skipUrlPattern(method: HttpMethod, vararg antPatterns: String?): JwtAuthenticationFilter { - for (pattern in antPatterns) { - ignoreUrls.add(AntPathRequestMatcher(pattern, method.name)) - } - return this - } +class JwtAuthenticationFilter + @JvmOverloads + constructor( + private val validator: TokenValidator, + private val authenticationManager: AuthenticationManager, + private val userRepository: UserRepository, + private val isOptional: Boolean = false, + ) : OncePerRequestFilter() { + private val ignoreUrls: MutableList = mutableListOf() - @Throws(IOException::class, ServletException::class) - override fun doFilterInternal( - httpRequest: HttpServletRequest, - httpResponse: HttpServletResponse, - chain: FilterChain, - ) { - if (CorsUtils.isPreFlightRequest(httpRequest)) { - Companion.logger.debug("Skipping JWT check for preflight request") - chain.doFilter(httpRequest, httpResponse) - return + /** + * Do not use JWT authentication for given paths and HTTP method. + * @param method HTTP method + * @param antPatterns Ant wildcard pattern + * @return the current filter + */ + fun skipUrlPattern( + method: HttpMethod, + vararg antPatterns: String?, + ): JwtAuthenticationFilter { + for (pattern in antPatterns) { + ignoreUrls.add(AntPathRequestMatcher(pattern, method.name)) + } + return this } - val existingAuthentication = SecurityContextHolder.getContext().authentication - - if (existingAuthentication.isAnonymous || existingAuthentication is OAuth2Authentication) { - val session = httpRequest.getSession(false) - val stringToken = tokenFromHeader(httpRequest) - var token: RadarToken? = null - var exMessage = "No token provided" - token = session?.radarToken - ?.takeIf { Instant.now() < it.expiresAt } - if (token != null) { - Companion.logger.debug("Using token from session") - } - else if (stringToken != null) { - try { - token = validator.validateBlocking(stringToken) - Companion.logger.debug("Using token from header") - } catch (ex: TokenValidationException) { - ex.message?.let { exMessage = it } - Companion.logger.info("Failed to validate token from header: {}", exMessage) - } - } - if (!validateToken(token, httpRequest, httpResponse, session, exMessage)) { + @Throws(IOException::class, ServletException::class) + override fun doFilterInternal( + httpRequest: HttpServletRequest, + httpResponse: HttpServletResponse, + chain: FilterChain, + ) { + if (CorsUtils.isPreFlightRequest(httpRequest)) { + Companion.logger.debug("Skipping JWT check for preflight request") + chain.doFilter(httpRequest, httpResponse) return } - } - chain.doFilter(httpRequest, httpResponse) - } - override fun shouldNotFilter(@Nonnull httpRequest: HttpServletRequest): Boolean { - val shouldNotFilterUrl = ignoreUrls.find { it.matches(httpRequest) } - return if (shouldNotFilterUrl != null) { - Companion.logger.debug("Skipping JWT check for {} request", shouldNotFilterUrl) - true - } else { - false + val existingAuthentication = SecurityContextHolder.getContext().authentication + + if (existingAuthentication.isAnonymous || existingAuthentication is OAuth2Authentication) { + val session = httpRequest.getSession(false) + val stringToken = tokenFromHeader(httpRequest) + var token: RadarToken? = null + var exMessage = "No token provided" + token = + session + ?.radarToken + ?.takeIf { Instant.now() < it.expiresAt } + if (token != null) { + Companion.logger.debug("Using token from session") + } else if (stringToken != null) { + try { + token = validator.validateBlocking(stringToken) + Companion.logger.debug("Using token from header") + } catch (ex: TokenValidationException) { + ex.message?.let { exMessage = it } + Companion.logger.info("Failed to validate token from header: {}", exMessage) + } + } + if (!validateToken(token, httpRequest, httpResponse, session, exMessage)) { + return + } + } + chain.doFilter(httpRequest, httpResponse) } - } - private fun tokenFromHeader(httpRequest: HttpServletRequest): String? { - return httpRequest.getHeader(HttpHeaders.AUTHORIZATION) - ?.takeIf { it.startsWith(AUTHORIZATION_BEARER_HEADER) } - ?.removePrefix(AUTHORIZATION_BEARER_HEADER) - ?.trim { it <= ' ' } - ?: parseCookies(httpRequest.getHeader(HttpHeaders.COOKIE)).find { it.name == "ory_kratos_session" } - ?.value - } - - @Throws(IOException::class) - private fun validateToken( - token: RadarToken?, - httpRequest: HttpServletRequest, - httpResponse: HttpServletResponse, - session: HttpSession?, - exMessage: String?, - ): Boolean { - return if (token != null) { - val updatedToken = checkUser(token, httpRequest, httpResponse, session) - ?: return false - httpRequest.radarToken = updatedToken - val authentication = RadarAuthentication(updatedToken) - authenticationManager.authenticate(authentication) - SecurityContextHolder.getContext().authentication = authentication - true - } else if (isOptional) { - logger.debug("Skipping optional token") - true - } else { - logger.error("Unauthorized - no valid token provided") - httpResponse.returnUnauthorized(httpRequest, exMessage) - false + override fun shouldNotFilter( + @Nonnull httpRequest: HttpServletRequest, + ): Boolean { + val shouldNotFilterUrl = ignoreUrls.find { it.matches(httpRequest) } + return if (shouldNotFilterUrl != null) { + Companion.logger.debug("Skipping JWT check for {} request", shouldNotFilterUrl) + true + } else { + false + } } - } - @Throws(IOException::class) - private fun checkUser( - token: RadarToken, - httpRequest: HttpServletRequest, - httpResponse: HttpServletResponse, - session: HttpSession?, - ): RadarToken? { - val userName = token.username ?: return token - val user = userRepository.findOneByLogin(userName) - return if (user != null) { - token.copyWithRoles(user.authorityReferences) - } else { - session?.removeAttribute(TOKEN_ATTRIBUTE) - httpResponse.returnUnauthorized(httpRequest, "User not found") - null + private fun tokenFromHeader(httpRequest: HttpServletRequest): String? = + httpRequest + .getHeader(HttpHeaders.AUTHORIZATION) + ?.takeIf { it.startsWith(AUTHORIZATION_BEARER_HEADER) } + ?.removePrefix(AUTHORIZATION_BEARER_HEADER) + ?.trim { it <= ' ' } + ?: parseCookies(httpRequest.getHeader(HttpHeaders.COOKIE)) + .find { it.name == "ory_kratos_session" } + ?.value + + @Throws(IOException::class) + private fun validateToken( + token: RadarToken?, + httpRequest: HttpServletRequest, + httpResponse: HttpServletResponse, + session: HttpSession?, + exMessage: String?, + ): Boolean { + return if (token != null) { + val updatedToken = + checkUser(token, httpRequest, httpResponse, session) + ?: return false + httpRequest.radarToken = updatedToken + val authentication = RadarAuthentication(updatedToken) + authenticationManager.authenticate(authentication) + SecurityContextHolder.getContext().authentication = authentication + true + } else if (isOptional) { + logger.debug("Skipping optional token") + true + } else { + logger.error("Unauthorized - no valid token provided") + httpResponse.returnUnauthorized(httpRequest, exMessage) + false + } } - } - companion object { - private fun HttpServletResponse.returnUnauthorized(request: HttpServletRequest, message: String?) { - status = HttpServletResponse.SC_UNAUTHORIZED - setHeader(HttpHeaders.WWW_AUTHENTICATE, AUTHORIZATION_BEARER_HEADER) - val fullMessage = if (message != null) { - "\"$message\"" + @Throws(IOException::class) + private fun checkUser( + token: RadarToken, + httpRequest: HttpServletRequest, + httpResponse: HttpServletResponse, + session: HttpSession?, + ): RadarToken? { + val userName = token.username ?: return token + val user = userRepository.findOneByLogin(userName) + return if (user != null) { + token.copyWithRoles(user.authorityReferences) } else { - "null" + session?.removeAttribute(TOKEN_ATTRIBUTE) + httpResponse.returnUnauthorized(httpRequest, "User not found") + null } - outputStream.print( - """ - {"error": "Unauthorized", - "status": "${HttpServletResponse.SC_UNAUTHORIZED}", - message": $fullMessage, - "path": "${request.requestURI}"} - """.trimIndent() - ) } - private val logger = LoggerFactory.getLogger(JwtAuthenticationFilter::class.java) - private const val AUTHORIZATION_BEARER_HEADER = "Bearer" - private const val TOKEN_ATTRIBUTE = "jwt" - - /** - * Authority references for given user. The user should have its roles mapped - * from the database. - * @return set of authority references. - */ - val User.authorityReferences: Set - get() = roles.mapTo(HashSet()) { role: Role? -> - val auth = role?.role - val referent = when (auth?.scope) { - RoleAuthority.Scope.GLOBAL -> null - RoleAuthority.Scope.ORGANIZATION -> role.organization?.name - RoleAuthority.Scope.PROJECT -> role.project?.projectName - null -> null - } - AuthorityReference(auth!!, referent) + companion object { + private fun HttpServletResponse.returnUnauthorized( + request: HttpServletRequest, + message: String?, + ) { + status = HttpServletResponse.SC_UNAUTHORIZED + setHeader(HttpHeaders.WWW_AUTHENTICATE, AUTHORIZATION_BEARER_HEADER) + val fullMessage = + if (message != null) { + "\"$message\"" + } else { + "null" + } + outputStream.print( + """ + {"error": "Unauthorized", + "status": "${HttpServletResponse.SC_UNAUTHORIZED}", + message": $fullMessage, + "path": "${request.requestURI}"} + """.trimIndent(), + ) } - - - @get:JvmStatic - @set:JvmStatic - var HttpSession.radarToken: RadarToken? - get() = getAttribute(TOKEN_ATTRIBUTE) as RadarToken? - set(value) = setAttribute(TOKEN_ATTRIBUTE, value) - - @get:JvmStatic - @set:JvmStatic - var HttpServletRequest.radarToken: RadarToken? - get() = getAttribute(TOKEN_ATTRIBUTE) as RadarToken? - set(value) = setAttribute(TOKEN_ATTRIBUTE, value) - - val Authentication?.isAnonymous: Boolean - get() { - this ?: return true - return authorities.size == 1 && - authorities.firstOrNull()?.authority == "ROLE_ANONYMOUS" - } + private val logger = LoggerFactory.getLogger(JwtAuthenticationFilter::class.java) + private const val AUTHORIZATION_BEARER_HEADER = "Bearer" + private const val TOKEN_ATTRIBUTE = "jwt" + + /** + * Authority references for given user. The user should have its roles mapped + * from the database. + * @return set of authority references. + */ + val User.authorityReferences: Set + get() = + roles.mapTo(HashSet()) { role: Role? -> + val auth = role?.role + val referent = + when (auth?.scope) { + RoleAuthority.Scope.GLOBAL -> null + RoleAuthority.Scope.ORGANIZATION -> role.organization?.name + RoleAuthority.Scope.PROJECT -> role.project?.projectName + null -> null + } + AuthorityReference(auth!!, referent) + } + + @get:JvmStatic + @set:JvmStatic + var HttpSession.radarToken: RadarToken? + get() = getAttribute(TOKEN_ATTRIBUTE) as RadarToken? + set(value) = setAttribute(TOKEN_ATTRIBUTE, value) + + @get:JvmStatic + @set:JvmStatic + var HttpServletRequest.radarToken: RadarToken? + get() = getAttribute(TOKEN_ATTRIBUTE) as RadarToken? + set(value) = setAttribute(TOKEN_ATTRIBUTE, value) + + val Authentication?.isAnonymous: Boolean + get() { + this ?: return true + return authorities.size == 1 && + authorities.firstOrNull()?.authority == "ROLE_ANONYMOUS" + } + } } -} diff --git a/src/main/java/org/radarbase/management/security/package-info.kt b/src/main/java/org/radarbase/management/security/PackageInfo.kt similarity index 63% rename from src/main/java/org/radarbase/management/security/package-info.kt rename to src/main/java/org/radarbase/management/security/PackageInfo.kt index 9739a9a01..0e9db8f56 100644 --- a/src/main/java/org/radarbase/management/security/package-info.kt +++ b/src/main/java/org/radarbase/management/security/PackageInfo.kt @@ -1,5 +1,6 @@ /** * Spring Security configuration. */ -package org.radarbase.management.security +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.security diff --git a/src/main/java/org/radarbase/management/security/PostgresApprovalStore.kt b/src/main/java/org/radarbase/management/security/PostgresApprovalStore.kt index 4ce4e6d1d..a21b730ca 100644 --- a/src/main/java/org/radarbase/management/security/PostgresApprovalStore.kt +++ b/src/main/java/org/radarbase/management/security/PostgresApprovalStore.kt @@ -36,7 +36,9 @@ import javax.sql.DataSource * @author Dave Syer * @author Modified by Nivethika */ -class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { +class PostgresApprovalStore( + dataSource: DataSource?, +) : ApprovalStore { private val jdbcTemplate: JdbcTemplate private val rowMapper: RowMapper = AuthorizationRowMapper() private var addApprovalStatement = DEFAULT_ADD_APPROVAL_STATEMENT @@ -79,8 +81,10 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { logger.debug(String.format("adding approvals: [%s]", approvals)) var success = true for (approval in approvals) { - if (!updateApproval(refreshApprovalStatement, approval) && !updateApproval( - addApprovalStatement, approval + if (!updateApproval(refreshApprovalStatement, approval) && + !updateApproval( + addApprovalStatement, + approval, ) ) { success = false @@ -94,23 +98,25 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { var success = true for (approval in approvals) { if (handleRevocationsAsExpiry) { - val refreshed = jdbcTemplate - .update(expireApprovalStatement) { ps: PreparedStatement -> - ps.setTimestamp(1, Timestamp(System.currentTimeMillis())) - ps.setString(2, approval.userId) - ps.setString(3, approval.clientId) - ps.setString(4, approval.scope) - } + val refreshed = + jdbcTemplate + .update(expireApprovalStatement) { ps: PreparedStatement -> + ps.setTimestamp(1, Timestamp(System.currentTimeMillis())) + ps.setString(2, approval.userId) + ps.setString(3, approval.clientId) + ps.setString(4, approval.scope) + } if (refreshed != 1) { success = false } } else { - val refreshed = jdbcTemplate - .update(deleteApprovalStatment) { ps: PreparedStatement -> - ps.setString(1, approval.userId) - ps.setString(2, approval.clientId) - ps.setString(3, approval.scope) - } + val refreshed = + jdbcTemplate + .update(deleteApprovalStatment) { ps: PreparedStatement -> + ps.setString(1, approval.userId) + ps.setString(2, approval.clientId) + ps.setString(3, approval.scope) + } if (refreshed != 1) { success = false } @@ -126,13 +132,15 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { fun purgeExpiredApprovals(): Boolean { logger.debug("Purging expired approvals from database") try { - val deleted = jdbcTemplate.update(deleteApprovalStatment) { ps: PreparedStatement -> - ps.setTimestamp( - 1, Timestamp( - Date().time + val deleted = + jdbcTemplate.update(deleteApprovalStatment) { ps: PreparedStatement -> + ps.setTimestamp( + 1, + Timestamp( + Date().time, + ), ) - ) - } + } logger.debug("$deleted expired approvals deleted") } catch (ex: DataAccessException) { logger.error("Error purging expired approvals", ex) @@ -141,27 +149,37 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { return true } - override fun getApprovals(userName: String, clientId: String): List { + override fun getApprovals( + userName: String, + clientId: String, + ): List { logger.debug("Finding approvals for userName {} and cliendId {}", userName, clientId) return jdbcTemplate.query(findApprovalStatement, rowMapper, userName, clientId) } - private fun updateApproval(sql: String, approval: Approval): Boolean { + private fun updateApproval( + sql: String, + approval: Approval, + ): Boolean { logger.debug(String.format("refreshing approval: [%s]", approval)) - val refreshed = jdbcTemplate.update(sql) { ps: PreparedStatement -> - ps.setTimestamp(1, Timestamp(approval.expiresAt.time)) - ps.setString(2, (if (approval.status == null) ApprovalStatus.APPROVED else approval.status).toString()) - ps.setTimestamp(3, Timestamp(approval.lastUpdatedAt.time)) - ps.setString(4, approval.userId) - ps.setString(5, approval.clientId) - ps.setString(6, approval.scope) - } + val refreshed = + jdbcTemplate.update(sql) { ps: PreparedStatement -> + ps.setTimestamp(1, Timestamp(approval.expiresAt.time)) + ps.setString(2, (if (approval.status == null) ApprovalStatus.APPROVED else approval.status).toString()) + ps.setTimestamp(3, Timestamp(approval.lastUpdatedAt.time)) + ps.setString(4, approval.userId) + ps.setString(5, approval.clientId) + ps.setString(6, approval.scope) + } return refreshed == 1 } private class AuthorizationRowMapper : RowMapper { @Throws(SQLException::class) - override fun mapRow(rs: ResultSet, rowNum: Int): Approval { + override fun mapRow( + rs: ResultSet, + rowNum: Int, + ): Approval { val userName = rs.getString(4) val clientId = rs.getString(5) val scope = rs.getString(6) @@ -169,8 +187,12 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { val status = rs.getString(2) val lastUpdatedAt: Date = rs.getTimestamp(3) return Approval( - userName, clientId, scope, expiresAt, - ApprovalStatus.valueOf(status), lastUpdatedAt + userName, + clientId, + scope, + expiresAt, + ApprovalStatus.valueOf(status), + lastUpdatedAt, ) } } @@ -178,25 +200,32 @@ class PostgresApprovalStore(dataSource: DataSource?) : ApprovalStore { companion object { private val logger = LoggerFactory.getLogger(PostgresApprovalStore::class.java) private const val TABLE_NAME = "oauth_approvals" - private const val FIELDS = ("\"expiresAt\", \"status\",\"lastModifiedAt\",\"userId\"," + "\"clientId\"," - + "\"scope\"") + private const val FIELDS = ( + "\"expiresAt\", \"status\",\"lastModifiedAt\",\"userId\"," + "\"clientId\"," + + "\"scope\"" + ) private const val WHERE_KEY = "where \"userId\"=? and \"clientId\"=?" private const val WHERE_KEY_AND_SCOPE = WHERE_KEY + " and \"scope\"=?" private const val AND_LESS_THAN_EXPIRE_AT = " and \"expiresAt\" <= ?" private val DEFAULT_ADD_APPROVAL_STATEMENT = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", TABLE_NAME, FIELDS) - private val DEFAULT_REFRESH_APPROVAL_STATEMENT = String.format( - "update %s set \"expiresAt\"=?, \"status\"=?, \"lastModifiedAt\"=? " - + WHERE_KEY_AND_SCOPE, TABLE_NAME - ) + private val DEFAULT_REFRESH_APPROVAL_STATEMENT = + String.format( + "update %s set \"expiresAt\"=?, \"status\"=?, \"lastModifiedAt\"=? " + + WHERE_KEY_AND_SCOPE, + TABLE_NAME, + ) private val DEFAULT_GET_APPROVAL_SQL = String.format("select %s from %s " + WHERE_KEY, FIELDS, TABLE_NAME) - private val DEFAULT_DELETE_APPROVAL_SQL = String.format( - "delete from %s " + WHERE_KEY_AND_SCOPE + AND_LESS_THAN_EXPIRE_AT, - TABLE_NAME - ) - private val DEFAULT_EXPIRE_APPROVAL_STATEMENT = String.format( - "update %s set " + "\"expiresAt\" = ? " - + WHERE_KEY_AND_SCOPE, TABLE_NAME - ) + private val DEFAULT_DELETE_APPROVAL_SQL = + String.format( + "delete from %s " + WHERE_KEY_AND_SCOPE + AND_LESS_THAN_EXPIRE_AT, + TABLE_NAME, + ) + private val DEFAULT_EXPIRE_APPROVAL_STATEMENT = + String.format( + "update %s set " + "\"expiresAt\" = ? " + + WHERE_KEY_AND_SCOPE, + TABLE_NAME, + ) } } diff --git a/src/main/java/org/radarbase/management/security/RadarAuthentication.kt b/src/main/java/org/radarbase/management/security/RadarAuthentication.kt index 3f6be9708..1830606e5 100644 --- a/src/main/java/org/radarbase/management/security/RadarAuthentication.kt +++ b/src/main/java/org/radarbase/management/security/RadarAuthentication.kt @@ -17,76 +17,72 @@ import java.security.Principal import java.util.stream.Collectors import javax.annotation.Nonnull -class RadarAuthentication(@param:Nonnull private val token: RadarToken) : Authentication, Principal { - private val authorities: List - private var isAuthenticated = true +class RadarAuthentication( + @param:Nonnull private val token: RadarToken, +) : Authentication, + Principal { + private val authorities: List + private var isAuthenticated = true - /** Instantiate authentication via a token. */ - init { - authorities = token.roles!!.stream() - .map(AuthorityReference::authority) - .distinct() - .map { role: String? -> SimpleGrantedAuthority(role) } - .collect(Collectors.toList()) - } - - override fun getName(): String { - return if (token.isClientCredentials) { - token.clientId!! - } else { - token.username!! + /** Instantiate authentication via a token. */ + init { + authorities = + token.roles + .stream() + .map(AuthorityReference::authority) + .distinct() + .map { role: String? -> SimpleGrantedAuthority(role) } + .collect(Collectors.toList()) } - } - override fun getAuthorities(): Collection { - return authorities - } + override fun getName(): String = + if (token.isClientCredentials) { + token.clientId!! + } else { + token.username!! + } - override fun getCredentials(): Any { - return token - } + override fun getAuthorities(): Collection = authorities - override fun getDetails(): Any? { - return null - } + override fun getCredentials(): Any = token - override fun getPrincipal(): Any? { - return if (token.isClientCredentials) { - null - } else { - this - } - } + override fun getDetails(): Any? = null - override fun isAuthenticated(): Boolean { - return isAuthenticated - } + override fun getPrincipal(): Any? = + if (token.isClientCredentials) { + null + } else { + this + } - @Throws(IllegalArgumentException::class) - override fun setAuthenticated(isAuthenticated: Boolean) { - this.isAuthenticated = isAuthenticated - } + override fun isAuthenticated(): Boolean = isAuthenticated - override fun equals(other: Any?): Boolean { - if (this === other) { - return true + @Throws(IllegalArgumentException::class) + override fun setAuthenticated(isAuthenticated: Boolean) { + this.isAuthenticated = isAuthenticated } - if (other == null || javaClass != other.javaClass) { - return false + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val that = other as RadarAuthentication + return isAuthenticated == that.isAuthenticated && token == that.token } - val that = other as RadarAuthentication - return isAuthenticated == that.isAuthenticated && token == that.token - } - override fun hashCode(): Int { - var result = token.hashCode() - result = 31 * result + if (isAuthenticated) 1 else 0 - return result - } + override fun hashCode(): Int { + var result = token.hashCode() + result = 31 * result + if (isAuthenticated) 1 else 0 + return result + } - override fun toString(): String { - return ("RadarAuthentication{" + "token=" + token - + ", authenticated=" + isAuthenticated() - + '}') + override fun toString(): String = + ( + "RadarAuthentication{" + "token=" + token + + ", authenticated=" + isAuthenticated() + + '}' + ) } -} diff --git a/src/main/java/org/radarbase/management/security/RadarAuthenticationProvider.kt b/src/main/java/org/radarbase/management/security/RadarAuthenticationProvider.kt index 403e0e4e9..601ee40b5 100644 --- a/src/main/java/org/radarbase/management/security/RadarAuthenticationProvider.kt +++ b/src/main/java/org/radarbase/management/security/RadarAuthenticationProvider.kt @@ -14,13 +14,12 @@ import org.springframework.security.core.AuthenticationException class RadarAuthenticationProvider : AuthenticationProvider { @Throws(AuthenticationException::class) - override fun authenticate(authentication: Authentication): Authentication? { - return if (authentication.isAuthenticated) { + override fun authenticate(authentication: Authentication): Authentication? = + if (authentication.isAuthenticated) { authentication - } else null - } + } else { + null + } - override fun supports(authentication: Class<*>): Boolean { - return authentication == RadarAuthentication::class.java - } + override fun supports(authentication: Class<*>): Boolean = authentication == RadarAuthentication::class.java } diff --git a/src/main/java/org/radarbase/management/security/SecurityUtils.kt b/src/main/java/org/radarbase/management/security/SecurityUtils.kt index 309c52ecc..c28af369f 100644 --- a/src/main/java/org/radarbase/management/security/SecurityUtils.kt +++ b/src/main/java/org/radarbase/management/security/SecurityUtils.kt @@ -25,25 +25,26 @@ object SecurityUtils { * @param authentication context authentication * @return user name if present */ - fun getUserName(authentication: Authentication): String? = authentication - .let { obj: Authentication -> obj.principal } - .let { principal: Any? -> - when (principal) { - is UserDetails -> { - return (authentication.principal as UserDetails).username - } + fun getUserName(authentication: Authentication): String? = + authentication + .let { obj: Authentication -> obj.principal } + .let { principal: Any? -> + when (principal) { + is UserDetails -> { + return (authentication.principal as UserDetails).username + } - is String -> { - return authentication.principal as String - } + is String -> { + return authentication.principal as String + } - is Authentication -> { - return principal.name - } + is Authentication -> { + return principal.name + } - else -> { - return null + else -> { + return null + } } } - } } diff --git a/src/main/java/org/radarbase/management/security/SpringSecurityAuditorAware.kt b/src/main/java/org/radarbase/management/security/SpringSecurityAuditorAware.kt index e6989ddf0..fa7e76914 100644 --- a/src/main/java/org/radarbase/management/security/SpringSecurityAuditorAware.kt +++ b/src/main/java/org/radarbase/management/security/SpringSecurityAuditorAware.kt @@ -14,9 +14,10 @@ import javax.annotation.Nonnull class SpringSecurityAuditorAware : AuditorAware { @Autowired private val authentication: Optional? = null + @Nonnull - override fun getCurrentAuditor(): Optional { - return authentication!!.map { obj: Authentication -> obj.name } + override fun getCurrentAuditor(): Optional = + authentication!! + .map { obj: Authentication -> obj.name } .filter { n: String -> n.isNotEmpty() } - } } diff --git a/src/main/java/org/radarbase/management/security/jwt/JwtAccessTokenConverter.kt b/src/main/java/org/radarbase/management/security/jwt/JwtAccessTokenConverter.kt index c4b2b9bc1..8db096699 100644 --- a/src/main/java/org/radarbase/management/security/jwt/JwtAccessTokenConverter.kt +++ b/src/main/java/org/radarbase/management/security/jwt/JwtAccessTokenConverter.kt @@ -9,7 +9,9 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer /** * Interface of a JwtAccessTokenConverter functions. */ -interface JwtAccessTokenConverter : TokenEnhancer, AccessTokenConverter { +interface JwtAccessTokenConverter : + TokenEnhancer, + AccessTokenConverter { /** * Decodes and verifies a JWT token string and extracts claims into a Map. * @param token string to decode. @@ -23,7 +25,10 @@ interface JwtAccessTokenConverter : TokenEnhancer, AccessTokenConverter { * @param authentication of the token. * @return JWT token string. */ - fun encode(accessToken: OAuth2AccessToken, authentication: OAuth2Authentication): String + fun encode( + accessToken: OAuth2AccessToken, + authentication: OAuth2Authentication, + ): String /** * Checks whether a token is access-token or refresh-token. diff --git a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtAccessTokenConverter.kt b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtAccessTokenConverter.kt index a57826d0a..15fafc91c 100644 --- a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtAccessTokenConverter.kt +++ b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtAccessTokenConverter.kt @@ -7,7 +7,6 @@ import com.auth0.jwt.exceptions.JWTVerificationException import com.auth0.jwt.exceptions.SignatureVerificationException import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper -import org.radarbase.management.security.jwt.ManagementPortalJwtAccessTokenConverter import org.slf4j.LoggerFactory import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken @@ -35,11 +34,12 @@ import java.util.stream.Stream open class ManagementPortalJwtAccessTokenConverter( algorithm: Algorithm, verifiers: MutableList, - private val refreshTokenVerifiers: List + private val refreshTokenVerifiers: List, ) : JwtAccessTokenConverter { - private val jsonParser = ObjectMapper().readerFor( - MutableMap::class.java - ) + private val jsonParser = + ObjectMapper().readerFor( + MutableMap::class.java, + ) private val tokenConverter: AccessTokenConverter /** @@ -77,12 +77,13 @@ open class ManagementPortalJwtAccessTokenConverter( override fun convertAccessToken( token: OAuth2AccessToken, - authentication: OAuth2Authentication - ): Map { - return tokenConverter.convertAccessToken(token, authentication) - } + authentication: OAuth2Authentication, + ): Map = tokenConverter.convertAccessToken(token, authentication) - override fun extractAccessToken(value: String, map: Map?): OAuth2AccessToken { + override fun extractAccessToken( + value: String, + map: Map?, + ): OAuth2AccessToken { var mapCopy = map?.toMutableMap() if (mapCopy?.containsKey(AccessTokenConverter.EXP) == true) { @@ -91,9 +92,8 @@ open class ManagementPortalJwtAccessTokenConverter( return tokenConverter.extractAccessToken(value, mapCopy) } - override fun extractAuthentication(map: Map?): OAuth2Authentication { - return tokenConverter.extractAuthentication(map) - } + override fun extractAuthentication(map: Map?): OAuth2Authentication = + tokenConverter.extractAuthentication(map) override fun setAlgorithm(algorithm: Algorithm) { this.algorithm = algorithm @@ -119,7 +119,7 @@ open class ManagementPortalJwtAccessTokenConverter( */ override fun enhance( accessToken: OAuth2AccessToken, - authentication: OAuth2Authentication + authentication: OAuth2Authentication, ): OAuth2AccessToken { // create new instance of token to enhance val resultAccessToken = DefaultOAuth2AccessToken(accessToken) @@ -154,24 +154,29 @@ open class ManagementPortalJwtAccessTokenConverter( if (refreshToken is ExpiringOAuth2RefreshToken) { val expiration = refreshToken.expiration refreshTokenToEnhance.expiration = expiration - encodedRefreshToken = DefaultExpiringOAuth2RefreshToken( - encode(refreshTokenToEnhance, authentication), expiration - ) + encodedRefreshToken = + DefaultExpiringOAuth2RefreshToken( + encode(refreshTokenToEnhance, authentication), + expiration, + ) } else { - encodedRefreshToken = DefaultOAuth2RefreshToken( - encode(refreshTokenToEnhance, authentication) - ) + encodedRefreshToken = + DefaultOAuth2RefreshToken( + encode(refreshTokenToEnhance, authentication), + ) } resultAccessToken.refreshToken = encodedRefreshToken } return resultAccessToken } - override fun isRefreshToken(token: OAuth2AccessToken): Boolean { - return token.additionalInformation?.containsKey(JwtAccessTokenConverter.ACCESS_TOKEN_ID) == true - } + override fun isRefreshToken(token: OAuth2AccessToken): Boolean = + token.additionalInformation?.containsKey(JwtAccessTokenConverter.ACCESS_TOKEN_ID) == true - override fun encode(accessToken: OAuth2AccessToken, authentication: OAuth2Authentication): String { + override fun encode( + accessToken: OAuth2AccessToken, + authentication: OAuth2Authentication, + ): String { // we need to override the encode method as well, Spring security does not know about // ECDSA, so it can not set the 'alg' header claim of the JWT to the correct value; here // we use the auth0 JWT implementation to create a signed, encoded JWT. @@ -179,27 +184,30 @@ open class ManagementPortalJwtAccessTokenConverter( val builder = JWT.create() // add the string array claims - Stream.of("aud", "sources", "roles", "authorities", "scope") + Stream + .of("aud", "sources", "roles", "authorities", "scope") .filter { key: String -> claims.containsKey(key) } .forEach { claim: String -> builder.withArrayClaim( claim, - (claims[claim] as Collection).toTypedArray() + (claims[claim] as Collection).toTypedArray(), ) } // add the string claims - Stream.of("sub", "iss", "user_name", "client_id", "grant_type", "jti", "ati") + Stream + .of("sub", "iss", "user_name", "client_id", "grant_type", "jti", "ati") .filter { key: String -> claims.containsKey(key) } .forEach { claim: String -> builder.withClaim(claim, claims[claim] as String?) } // add the date claims, they are in seconds since epoch, we need milliseconds - Stream.of("exp", "iat") + Stream + .of("exp", "iat") .filter { key: String -> claims.containsKey(key) } .forEach { claim: String -> builder.withClaim( claim, - Date.from(Instant.ofEpochSecond((claims[claim] as Long?)!!)) + Date.from(Instant.ofEpochSecond((claims[claim] as Long?)!!)), ) } return builder.sign(algorithm) @@ -210,10 +218,11 @@ open class ManagementPortalJwtAccessTokenConverter( val verifierToUse: List val claims: MutableMap try { - val decodedPayload = String( - Base64.getUrlDecoder().decode(jwt.payload), - StandardCharsets.UTF_8 - ) + val decodedPayload = + String( + Base64.getUrlDecoder().decode(jwt.payload), + StandardCharsets.UTF_8, + ) claims = jsonParser.readValue(decodedPayload) if (claims.containsKey(AccessTokenConverter.EXP) && claims[AccessTokenConverter.EXP] is Int) { val intValue = claims[AccessTokenConverter.EXP] as Int? @@ -236,7 +245,9 @@ open class ManagementPortalJwtAccessTokenConverter( } catch (ex: JWTVerificationException) { logger.debug( "Verifier {} with implementation {} did not accept token: {}", - verifier, verifier.javaClass, ex.message + verifier, + verifier.javaClass, + ex.message, ) } } diff --git a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtTokenStore.kt b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtTokenStore.kt index 512b66433..8ce921599 100644 --- a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtTokenStore.kt +++ b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalJwtTokenStore.kt @@ -53,7 +53,7 @@ class ManagementPortalJwtTokenStore : TokenStore { */ constructor( jwtAccessTokenConverter: JwtAccessTokenConverter, - approvalStore: ApprovalStore? + approvalStore: ApprovalStore?, ) { this.jwtAccessTokenConverter = jwtAccessTokenConverter this.approvalStore = approvalStore @@ -68,15 +68,15 @@ class ManagementPortalJwtTokenStore : TokenStore { this.approvalStore = approvalStore } - override fun readAuthentication(token: OAuth2AccessToken): OAuth2Authentication { - return readAuthentication(token.value) - } + override fun readAuthentication(token: OAuth2AccessToken): OAuth2Authentication = readAuthentication(token.value) - override fun readAuthentication(token: String): OAuth2Authentication { - return jwtAccessTokenConverter.extractAuthentication(jwtAccessTokenConverter.decode(token)) - } + override fun readAuthentication(token: String): OAuth2Authentication = + jwtAccessTokenConverter.extractAuthentication(jwtAccessTokenConverter.decode(token)) - override fun storeAccessToken(token: OAuth2AccessToken, authentication: OAuth2Authentication) { + override fun storeAccessToken( + token: OAuth2AccessToken, + authentication: OAuth2Authentication, + ) { // this is not really a store where we persist } @@ -88,10 +88,9 @@ class ManagementPortalJwtTokenStore : TokenStore { return accessToken } - private fun convertAccessToken(tokenValue: String): OAuth2AccessToken { - return jwtAccessTokenConverter + private fun convertAccessToken(tokenValue: String): OAuth2AccessToken = + jwtAccessTokenConverter .extractAccessToken(tokenValue, jwtAccessTokenConverter.decode(tokenValue)) - } override fun removeAccessToken(token: OAuth2AccessToken) { // this is not really store where we persist @@ -99,7 +98,7 @@ class ManagementPortalJwtTokenStore : TokenStore { override fun storeRefreshToken( refreshToken: OAuth2RefreshToken, - authentication: OAuth2Authentication + authentication: OAuth2Authentication, ) { // this is not really store where we persist } @@ -133,14 +132,15 @@ class ManagementPortalJwtTokenStore : TokenStore { return if (encodedRefreshToken.expiration != null) { DefaultExpiringOAuth2RefreshToken( encodedRefreshToken.value, - encodedRefreshToken.expiration + encodedRefreshToken.expiration, ) - } else DefaultOAuth2RefreshToken(encodedRefreshToken.value) + } else { + DefaultOAuth2RefreshToken(encodedRefreshToken.value) + } } - override fun readAuthenticationForRefreshToken(token: OAuth2RefreshToken): OAuth2Authentication { - return readAuthentication(token.value) - } + override fun readAuthenticationForRefreshToken(token: OAuth2RefreshToken): OAuth2Authentication = + readAuthentication(token.value) override fun removeRefreshToken(token: OAuth2RefreshToken) { remove(token.value) @@ -156,9 +156,12 @@ class ManagementPortalJwtTokenStore : TokenStore { for (scope in auth.oAuth2Request.scope) { approvals.add( Approval( - user.name, clientId, scope, Date(), - ApprovalStatus.APPROVED - ) + user.name, + clientId, + scope, + Date(), + ApprovalStatus.APPROVED, + ), ) } approvalStore!!.revokeApprovals(approvals) @@ -178,12 +181,8 @@ class ManagementPortalJwtTokenStore : TokenStore { override fun findTokensByClientIdAndUserName( clientId: String, - userName: String - ): Collection { - return emptySet() - } + userName: String, + ): Collection = emptySet() - override fun findTokensByClientId(clientId: String): Collection { - return emptySet() - } + override fun findTokensByClientId(clientId: String): Collection = emptySet() } diff --git a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalOauthKeyStoreHandler.kt b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalOauthKeyStoreHandler.kt index 752a295f1..2a7cdb362 100644 --- a/src/main/java/org/radarbase/management/security/jwt/ManagementPortalOauthKeyStoreHandler.kt +++ b/src/main/java/org/radarbase/management/security/jwt/ManagementPortalOauthKeyStoreHandler.kt @@ -43,254 +43,279 @@ import kotlin.collections.Map.Entry * this class does not assume a specific key type, while the Spring factory assumes RSA keys. */ @Component -class ManagementPortalOauthKeyStoreHandler @Autowired constructor( - environment: Environment, servletContext: ServletContext, private val managementPortalProperties: ManagementPortalProperties -) { - private val password: CharArray - private val store: KeyStore - private val loadedResource: Resource - private val oauthConfig: Oauth - private val verifierPublicKeyAliasList: List - private val managementPortalBaseUrl: String - val verifiers: MutableList - val refreshTokenVerifiers: MutableList +class ManagementPortalOauthKeyStoreHandler + @Autowired + constructor( + environment: Environment, + servletContext: ServletContext, + private val managementPortalProperties: ManagementPortalProperties, + ) { + private val password: CharArray + private val store: KeyStore + private val loadedResource: Resource + private val oauthConfig: Oauth + private val verifierPublicKeyAliasList: List + private val managementPortalBaseUrl: String + val verifiers: MutableList + val refreshTokenVerifiers: MutableList - /** - * Keystore factory. This tries to load the first valid keystore listed in resources. - * - * @throws IllegalArgumentException if none of the provided resources can be used to load a - * keystore. - */ - init { - checkOAuthConfig(managementPortalProperties) - oauthConfig = managementPortalProperties.oauth - password = oauthConfig.keyStorePassword.toCharArray() - val loadedStore: Entry = loadStore() - loadedResource = loadedStore.key - store = loadedStore.value - verifierPublicKeyAliasList = loadVerifiersPublicKeyAliasList() - managementPortalBaseUrl = - ("http://localhost:" + environment.getProperty("server.port") + servletContext.contextPath) - logger.info("Using Management Portal base-url {}", managementPortalBaseUrl) - val algorithms = loadAlgorithmsFromAlias().filter { obj: Algorithm? -> Objects.nonNull(obj) }.toList() - verifiers = algorithms.map { algo: Algorithm? -> - JWT.require(algo).withAudience(ManagementPortalJwtAccessTokenConverter.RES_MANAGEMENT_PORTAL).build() - }.toMutableList() - // No need to check audience with a refresh token: it can be used - // to refresh tokens intended for other resources. - refreshTokenVerifiers = algorithms.map { algo: Algorithm -> JWT.require(algo).build() }.toMutableList() - } + /** + * Keystore factory. This tries to load the first valid keystore listed in resources. + * + * @throws IllegalArgumentException if none of the provided resources can be used to load a + * keystore. + */ + init { + checkOAuthConfig(managementPortalProperties) + oauthConfig = managementPortalProperties.oauth + password = oauthConfig.keyStorePassword!!.toCharArray() + val loadedStore: Entry = loadStore() + loadedResource = loadedStore.key + store = loadedStore.value + verifierPublicKeyAliasList = loadVerifiersPublicKeyAliasList() + managementPortalBaseUrl = + ("http://localhost:" + environment.getProperty("server.port") + servletContext.contextPath) + logger.info("Using Management Portal base-url {}", managementPortalBaseUrl) + val algorithms = loadAlgorithmsFromAlias().filter { obj: Algorithm? -> Objects.nonNull(obj) }.toList() + verifiers = + algorithms + .map { algo: Algorithm? -> + JWT.require(algo).withAudience(RES_MANAGEMENT_PORTAL).build() + }.toMutableList() + // No need to check audience with a refresh token: it can be used + // to refresh tokens intended for other resources. + refreshTokenVerifiers = algorithms.map { algo: Algorithm -> JWT.require(algo).build() }.toMutableList() + } - @Nonnull - private fun loadStore(): Entry { - for (resource in KEYSTORE_PATHS) { - if (!resource.exists()) { - logger.debug("JWT key store {} does not exist. Ignoring this resource", resource) - continue - } - try { - val fileName = Objects.requireNonNull(resource.filename).lowercase() - val type = if (fileName.endsWith(".pfx") || fileName.endsWith(".p12")) "PKCS12" else "jks" - val localStore = KeyStore.getInstance(type) - localStore.load(resource.inputStream, password) - logger.debug("Loaded JWT key store {}", resource) - if (localStore != null) - return SimpleImmutableEntry(resource, localStore) - } catch (ex: CertificateException) { - logger.error("Cannot load JWT key store", ex) - } catch (ex: NoSuchAlgorithmException) { - logger.error("Cannot load JWT key store", ex) - } catch (ex: KeyStoreException) { - logger.error("Cannot load JWT key store", ex) - } catch (ex: IOException) { - logger.error("Cannot load JWT key store", ex) + @Nonnull + private fun loadStore(): Entry { + for (resource in KEYSTORE_PATHS) { + if (!resource.exists()) { + logger.debug("JWT key store {} does not exist. Ignoring this resource", resource) + continue + } + try { + val fileName = resource.filename?.lowercase() ?: throw NullPointerException("Resource filename is null") + val type = if (fileName.endsWith(".pfx") || fileName.endsWith(".p12")) "PKCS12" else "jks" + val localStore = KeyStore.getInstance(type) + localStore.load(resource.inputStream, password) + logger.debug("Loaded JWT key store {}", resource) + if (localStore != null) { + return SimpleImmutableEntry(resource, localStore) + } + } catch (ex: CertificateException) { + logger.error("Cannot load JWT key store", ex) + } catch (ex: NoSuchAlgorithmException) { + logger.error("Cannot load JWT key store", ex) + } catch (ex: KeyStoreException) { + logger.error("Cannot load JWT key store", ex) + } catch (ex: IOException) { + logger.error("Cannot load JWT key store", ex) + } } + throw IllegalArgumentException( + "Cannot load any of the given JWT key stores " + KEYSTORE_PATHS, + ) } - throw IllegalArgumentException( - "Cannot load any of the given JWT key stores " + KEYSTORE_PATHS - ) - } - private fun loadVerifiersPublicKeyAliasList(): List { - val publicKeyAliases: MutableList = ArrayList() - oauthConfig.signingKeyAlias?.let { publicKeyAliases.add(it) } - if (oauthConfig.checkingKeyAliases != null) { - publicKeyAliases.addAll(oauthConfig.checkingKeyAliases!!) + private fun loadVerifiersPublicKeyAliasList(): List { + val publicKeyAliases: MutableList = ArrayList() + oauthConfig.signingKeyAlias?.let { publicKeyAliases.add(it) } + if (oauthConfig.checkingKeyAliases != null) { + publicKeyAliases.addAll(oauthConfig.checkingKeyAliases!!) + } + return publicKeyAliases } - return publicKeyAliases - } - /** - * Returns configured public keys of token verifiers. - * @return List of public keys for token verification. - */ - fun loadJwks(): JsonWebKeySet { - return JsonWebKeySet(verifierPublicKeyAliasList.map { alias: String -> this.getKeyPair(alias) } - .map { keyPair: KeyPair? -> getJwtAlgorithm(keyPair) }.mapNotNull { obj: JwtAlgorithm? -> obj?.jwk }) - } - - /** - * Load default verifiers from configured keystore and aliases. - */ - private fun loadAlgorithmsFromAlias(): Collection { - return verifierPublicKeyAliasList - .map { alias: String -> this.getKeyPair(alias) } - .mapNotNull { keyPair -> getJwtAlgorithm(keyPair) } - .map { obj: JwtAlgorithm -> obj.algorithm } - } - - val algorithmForSigning: Algorithm /** - * Returns the signing algorithm extracted based on signing alias configured from keystore. - * @return signing algorithm. + * Returns configured public keys of token verifiers. + * @return List of public keys for token verification. */ - get() { - val signKey = oauthConfig.signingKeyAlias - logger.debug("Using JWT signing key {}", signKey) - val keyPair = getKeyPair(signKey) ?: throw IllegalArgumentException( - "Cannot load JWT signing key " + signKey + " from JWT key store." + fun loadJwks(): JsonWebKeySet = + JsonWebKeySet( + verifierPublicKeyAliasList + .map { alias: String -> this.getKeyPair(alias) } + .map { keyPair: KeyPair? -> getJwtAlgorithm(keyPair) } + .mapNotNull { obj: JwtAlgorithm? -> obj?.jwk }, ) - return getAlgorithmFromKeyPair(keyPair) - } - /** - * Get a key pair from the store using the store password. - * @param alias key pair alias - * @return loaded key pair or `null` if the key store does not contain a loadable key with - * given alias. - * @throws IllegalArgumentException if the key alias password is wrong or the key cannot - * loaded. - */ - private fun getKeyPair(alias: String): KeyPair? { - return getKeyPair(alias, password) - } + /** + * Load default verifiers from configured keystore and aliases. + */ + private fun loadAlgorithmsFromAlias(): Collection = + verifierPublicKeyAliasList + .map { alias: String -> this.getKeyPair(alias) } + .mapNotNull { keyPair -> getJwtAlgorithm(keyPair) } + .map { obj: JwtAlgorithm -> obj.algorithm } - /** - * Get a key pair from the store with a given alias and password. - * @param alias key pair alias - * @param password key pair password - * @return loaded key pair or `null` if the key store does not contain a loadable key with - * given alias. - * @throws IllegalArgumentException if the key alias password is wrong or the key cannot - * load. - */ - private fun getKeyPair(alias: String, password: CharArray): KeyPair? { - return try { - val key = store.getKey(alias, password) as PrivateKey? - if (key == null) { - logger.warn( - "JWT key store {} does not contain private key pair for alias {}", loadedResource, alias - ) - return null + val algorithmForSigning: Algorithm + /** + * Returns the signing algorithm extracted based on signing alias configured from keystore. + * @return signing algorithm. + */ + get() { + val signKey = oauthConfig.signingKeyAlias + logger.debug("Using JWT signing key {}", signKey) + val keyPair = + getKeyPair(signKey!!) ?: throw IllegalArgumentException( + "Cannot load JWT signing key " + signKey + " from JWT key store.", + ) + return getAlgorithmFromKeyPair(keyPair) } - val cert = store.getCertificate(alias) - if (cert == null) { + + /** + * Get a key pair from the store using the store password. + * @param alias key pair alias + * @return loaded key pair or `null` if the key store does not contain a loadable key with + * given alias. + * @throws IllegalArgumentException if the key alias password is wrong or the key cannot + * loaded. + */ + private fun getKeyPair(alias: String): KeyPair? = getKeyPair(alias, password) + + /** + * Get a key pair from the store with a given alias and password. + * @param alias key pair alias + * @param password key pair password + * @return loaded key pair or `null` if the key store does not contain a loadable key with + * given alias. + * @throws IllegalArgumentException if the key alias password is wrong or the key cannot + * load. + */ + private fun getKeyPair( + alias: String, + password: CharArray, + ): KeyPair? { + return try { + val key = store.getKey(alias, password) as PrivateKey? + if (key == null) { + logger.warn( + "JWT key store {} does not contain private key pair for alias {}", + loadedResource, + alias, + ) + return null + } + val cert = store.getCertificate(alias) + if (cert == null) { + logger.warn( + "JWT key store {} does not contain certificate pair for alias {}", + loadedResource, + alias, + ) + return null + } + val publicKey = cert.publicKey + if (publicKey == null) { + logger.warn( + "JWT key store {} does not contain public key pair for alias {}", + loadedResource, + alias, + ) + return null + } + KeyPair(publicKey, key) + } catch (ex: NoSuchAlgorithmException) { logger.warn( - "JWT key store {} does not contain certificate pair for alias {}", loadedResource, alias + "JWT key store {} contains unknown algorithm for key pair with alias {}: {}", + loadedResource, + alias, + ex.toString(), ) - return null - } - val publicKey = cert.publicKey - if (publicKey == null) { - logger.warn( - "JWT key store {} does not contain public key pair for alias {}", loadedResource, alias + null + } catch (ex: UnrecoverableKeyException) { + throw IllegalArgumentException( + "JWT key store $loadedResource contains unrecoverable key pair with alias $alias (the password may be wrong)", + ex, + ) + } catch (ex: KeyStoreException) { + throw IllegalArgumentException( + "JWT key store $loadedResource contains unrecoverable key pair with alias $alias (the password may be wrong)", + ex, ) - return null } - KeyPair(publicKey, key) - } catch (ex: NoSuchAlgorithmException) { - logger.warn( - "JWT key store {} contains unknown algorithm for key pair with alias {}: {}", - loadedResource, - alias, - ex.toString() - ) - null - } catch (ex: UnrecoverableKeyException) { - throw IllegalArgumentException( - "JWT key store $loadedResource contains unrecoverable key pair with alias $alias (the password may be wrong)", - ex - ) - } catch (ex: KeyStoreException) { - throw IllegalArgumentException( - "JWT key store $loadedResource contains unrecoverable key pair with alias $alias (the password may be wrong)", - ex - ) } - } - val tokenValidator: TokenValidator - /** Get the default token validator. */ - get() { - val loaderList = listOf( - JwksTokenVerifierLoader( - managementPortalBaseUrl + "/oauth/token_key", - RES_MANAGEMENT_PORTAL, - JwkAlgorithmParser() - ), - KratosTokenVerifierLoader(managementPortalProperties.identityServer.publicUrl(), requireAal2 = managementPortalProperties.oauth.requireAal2), - ) - return TokenValidator(loaderList) - } + val tokenValidator: TokenValidator + /** Get the default token validator. */ + get() { + val loaderList = + listOf( + JwksTokenVerifierLoader( + managementPortalBaseUrl + "/oauth/token_key", + RES_MANAGEMENT_PORTAL, + JwkAlgorithmParser(), + ), + KratosTokenVerifierLoader( + managementPortalProperties.identityServer.publicUrl() ?: "", + requireAal2 = managementPortalProperties.oauth.requireAal2, + ), + ) + return TokenValidator(loaderList) + } - companion object { - private val logger = LoggerFactory.getLogger( - ManagementPortalOauthKeyStoreHandler::class.java - ) - private val KEYSTORE_PATHS = listOf( - ClassPathResource("/config/keystore.p12"), ClassPathResource("/config/keystore.jks") - ) + companion object { + private val logger = + LoggerFactory.getLogger( + ManagementPortalOauthKeyStoreHandler::class.java, + ) + private val KEYSTORE_PATHS = + listOf( + ClassPathResource("/config/keystore.p12"), + ClassPathResource("/config/keystore.jks"), + ) - private fun checkOAuthConfig(managementPortalProperties: ManagementPortalProperties) { - val oauthConfig = managementPortalProperties.oauth - if (oauthConfig.keyStorePassword.isEmpty()) { - logger.error("oauth.keyStorePassword is empty") - throw IllegalArgumentException("oauth.keyStorePassword is empty") - } - if (oauthConfig.signingKeyAlias == null || oauthConfig.signingKeyAlias!!.isEmpty()) { - logger.error("oauth.signingKeyAlias is empty") - throw IllegalArgumentException("OauthConfig is not provided") + private fun checkOAuthConfig(managementPortalProperties: ManagementPortalProperties) { + val oauthConfig = managementPortalProperties.oauth + if (oauthConfig.keyStorePassword!!.isEmpty()) { + logger.error("oauth.keyStorePassword is empty") + throw IllegalArgumentException("oauth.keyStorePassword is empty") + } + if (oauthConfig.signingKeyAlias == null || oauthConfig.signingKeyAlias!!.isEmpty()) { + logger.error("oauth.signingKeyAlias is empty") + throw IllegalArgumentException("OauthConfig is not provided") + } } - } - - /** - * Returns extracted [Algorithm] from the KeyPair. - * @param keyPair to find algorithm. - * @return extracted algorithm. - */ - private fun getAlgorithmFromKeyPair(keyPair: KeyPair): Algorithm { - val alg = getJwtAlgorithm(keyPair) ?: throw IllegalArgumentException( - "KeyPair type " + keyPair.private.algorithm + " is unknown." - ) - return alg.algorithm - } - /** - * Get the JWT algorithm to sign or verify JWTs with. - * @param keyPair key pair for signing/verifying. - * @return algorithm or `null` if the key type is unknown. - */ - private fun getJwtAlgorithm(keyPair: KeyPair?): JwtAlgorithm? { - if (keyPair == null) { - return null + /** + * Returns extracted [Algorithm] from the KeyPair. + * @param keyPair to find algorithm. + * @return extracted algorithm. + */ + private fun getAlgorithmFromKeyPair(keyPair: KeyPair): Algorithm { + val alg = + getJwtAlgorithm(keyPair) ?: throw IllegalArgumentException( + "KeyPair type " + keyPair.private.algorithm + " is unknown.", + ) + return alg.algorithm } - val privateKey = keyPair.private - return when (privateKey) { - is ECPrivateKey -> { - EcdsaJwtAlgorithm(keyPair) - } - is RSAPrivateKey -> { - RsaJwtAlgorithm(keyPair) + /** + * Get the JWT algorithm to sign or verify JWTs with. + * @param keyPair key pair for signing/verifying. + * @return algorithm or `null` if the key type is unknown. + */ + private fun getJwtAlgorithm(keyPair: KeyPair?): JwtAlgorithm? { + if (keyPair == null) { + return null } + return when (val privateKey = keyPair.private) { + is ECPrivateKey -> { + EcdsaJwtAlgorithm(keyPair) + } - else -> { - logger.warn( - "No JWT algorithm found for key type {}", privateKey.javaClass - ) - null + is RSAPrivateKey -> { + RsaJwtAlgorithm(keyPair) + } + + else -> { + logger.warn( + "No JWT algorithm found for key type {}", + privateKey.javaClass, + ) + null + } } } } } -} diff --git a/src/main/java/org/radarbase/management/security/jwt/RadarTokenLoader.kt b/src/main/java/org/radarbase/management/security/jwt/RadarTokenLoader.kt index 7f1ca59a8..254c3a01a 100644 --- a/src/main/java/org/radarbase/management/security/jwt/RadarTokenLoader.kt +++ b/src/main/java/org/radarbase/management/security/jwt/RadarTokenLoader.kt @@ -11,7 +11,9 @@ class RadarTokenLoader { fun loadToken(httpServletRequest: HttpServletRequest): RadarToken? = httpServletRequest.radarToken ?.also { logger.debug("Using request RadarToken") } - ?: httpServletRequest.getSession(false)?.radarToken + ?: httpServletRequest + .getSession(false) + ?.radarToken ?.also { logger.debug("Using session RadarToken") } companion object { diff --git a/src/main/java/org/radarbase/management/security/jwt/algorithm/AsymmetricalJwtAlgorithm.kt b/src/main/java/org/radarbase/management/security/jwt/algorithm/AsymmetricalJwtAlgorithm.kt index 303b078fb..aec15c22c 100644 --- a/src/main/java/org/radarbase/management/security/jwt/algorithm/AsymmetricalJwtAlgorithm.kt +++ b/src/main/java/org/radarbase/management/security/jwt/algorithm/AsymmetricalJwtAlgorithm.kt @@ -5,20 +5,24 @@ import org.radarbase.auth.jwks.MPJsonWebKey import java.security.KeyPair import java.util.* -abstract class AsymmetricalJwtAlgorithm protected constructor(protected val keyPair: KeyPair) : JwtAlgorithm { +abstract class AsymmetricalJwtAlgorithm protected constructor( + protected val keyPair: KeyPair, +) : JwtAlgorithm { protected abstract val encodedStringHeader: String protected abstract val encodedStringFooter: String protected abstract val keyType: String override val verifierKeyEncodedString: String - get() = """ - ${encodedStringHeader} - ${String(Base64.getEncoder().encode(keyPair.public.encoded))} - ${encodedStringFooter} - """.trimIndent() + get() = + """ + $encodedStringHeader + ${String(Base64.getEncoder().encode(keyPair.public.encoded))} + $encodedStringFooter + """.trimIndent() override val jwk: JsonWebKey - get() = MPJsonWebKey( - algorithm.name, - keyType, - verifierKeyEncodedString - ) + get() = + MPJsonWebKey( + algorithm.name, + keyType, + verifierKeyEncodedString, + ) } diff --git a/src/main/java/org/radarbase/management/security/jwt/algorithm/EcdsaJwtAlgorithm.kt b/src/main/java/org/radarbase/management/security/jwt/algorithm/EcdsaJwtAlgorithm.kt index a886cdfdb..69ae33a51 100644 --- a/src/main/java/org/radarbase/management/security/jwt/algorithm/EcdsaJwtAlgorithm.kt +++ b/src/main/java/org/radarbase/management/security/jwt/algorithm/EcdsaJwtAlgorithm.kt @@ -5,17 +5,20 @@ import java.security.KeyPair import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey -class EcdsaJwtAlgorithm(keyPair: KeyPair) : AsymmetricalJwtAlgorithm(keyPair) { +class EcdsaJwtAlgorithm( + keyPair: KeyPair, +) : AsymmetricalJwtAlgorithm(keyPair) { /** ECDSA JWT algorithm. */ init { require(keyPair.private is ECPrivateKey) { "Cannot make EcdsaJwtAlgorithm with " + keyPair.private.javaClass } } override val algorithm: Algorithm - get() = Algorithm.ECDSA256( - keyPair.public as ECPublicKey, - keyPair.private as ECPrivateKey - ) + get() = + Algorithm.ECDSA256( + keyPair.public as ECPublicKey, + keyPair.private as ECPrivateKey, + ) override val encodedStringHeader: String get() = "-----BEGIN EC PUBLIC KEY-----" override val encodedStringFooter: String diff --git a/src/main/java/org/radarbase/management/security/jwt/algorithm/RsaJwtAlgorithm.kt b/src/main/java/org/radarbase/management/security/jwt/algorithm/RsaJwtAlgorithm.kt index 0592c9ba0..5b7dd4d14 100644 --- a/src/main/java/org/radarbase/management/security/jwt/algorithm/RsaJwtAlgorithm.kt +++ b/src/main/java/org/radarbase/management/security/jwt/algorithm/RsaJwtAlgorithm.kt @@ -5,17 +5,20 @@ import java.security.KeyPair import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey -class RsaJwtAlgorithm(keyPair: KeyPair) : AsymmetricalJwtAlgorithm(keyPair) { +class RsaJwtAlgorithm( + keyPair: KeyPair, +) : AsymmetricalJwtAlgorithm(keyPair) { /** RSA JWT algorithm. */ init { require(keyPair.private is RSAPrivateKey) { "Cannot make RsaJwtAlgorithm with " + keyPair.private.javaClass } } override val algorithm: Algorithm - get() = Algorithm.RSA256( - keyPair.public as RSAPublicKey, - keyPair.private as RSAPrivateKey - ) + get() = + Algorithm.RSA256( + keyPair.public as RSAPublicKey, + keyPair.private as RSAPrivateKey, + ) override val encodedStringHeader: String get() = "-----BEGIN PUBLIC KEY-----" override val encodedStringFooter: String diff --git a/src/main/java/org/radarbase/management/service/AuditEventService.kt b/src/main/java/org/radarbase/management/service/AuditEventService.kt index cc2b4c961..ca2638dba 100644 --- a/src/main/java/org/radarbase/management/service/AuditEventService.kt +++ b/src/main/java/org/radarbase/management/service/AuditEventService.kt @@ -21,16 +21,16 @@ import java.util.* @Transactional class AuditEventService( private val persistenceAuditEventRepository: PersistenceAuditEventRepository, - private val auditEventConverter: AuditEventConverter + private val auditEventConverter: AuditEventConverter, ) { - fun findAll(pageable: Pageable): Page { - return persistenceAuditEventRepository.findAll(pageable) + fun findAll(pageable: Pageable): Page = + persistenceAuditEventRepository + .findAll(pageable) .map { persistentAuditEvent: PersistentAuditEvent? -> auditEventConverter.convertToAuditEvent( - persistentAuditEvent!! + persistentAuditEvent!!, ) } - } /** * Find audit events by dates. @@ -41,24 +41,24 @@ class AuditEventService( * @return a page of audit events */ fun findByDates( - fromDate: LocalDateTime?, toDate: LocalDateTime?, - pageable: Pageable? - ): Page { - return persistenceAuditEventRepository + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + pageable: Pageable?, + ): Page = + persistenceAuditEventRepository .findAllByAuditEventDateBetween(fromDate, toDate, pageable) .map { persistentAuditEvent: PersistentAuditEvent? -> auditEventConverter.convertToAuditEvent( - persistentAuditEvent!! + persistentAuditEvent!!, ) } - } - fun find(id: Long): Optional { - return persistenceAuditEventRepository.findById(id) + fun find(id: Long): Optional = + persistenceAuditEventRepository + .findById(id) .map { persistentAuditEvent: PersistentAuditEvent? -> auditEventConverter.convertToAuditEvent( - persistentAuditEvent!! + persistentAuditEvent!!, ) } - } } diff --git a/src/main/java/org/radarbase/management/service/AuthService.kt b/src/main/java/org/radarbase/management/service/AuthService.kt index 07929fc7e..c91ddb222 100644 --- a/src/main/java/org/radarbase/management/service/AuthService.kt +++ b/src/main/java/org/radarbase/management/service/AuthService.kt @@ -1,7 +1,12 @@ package org.radarbase.management.service import kotlinx.coroutines.runBlocking -import org.radarbase.auth.authorization.* +import org.radarbase.auth.authorization.AuthorityReferenceSet +import org.radarbase.auth.authorization.AuthorizationOracle +import org.radarbase.auth.authorization.EntityDetails +import org.radarbase.auth.authorization.Permission +import org.radarbase.auth.authorization.RoleAuthority +import org.radarbase.auth.authorization.entityDetailsBuilder import org.radarbase.auth.token.RadarToken import org.radarbase.management.security.NotAuthorizedException import org.springframework.stereotype.Service @@ -26,7 +31,7 @@ class AuthService( if (!oracle.hasScope(token, permission)) { throw NotAuthorizedException( - "User ${token.username} with client ${token.clientId} does not have permission $permission" + "User ${token.username} with client ${token.clientId} does not have permission $permission", ) } } @@ -49,13 +54,14 @@ class AuthService( // entitydetails builder is null means we require global permission val entity = if (builder != null) entityDetailsBuilder(builder) else EntityDetails.global - val hasPermission = runBlocking { - oracle.hasPermission(token, permission, entity, scope) - } + val hasPermission = + runBlocking { + oracle.hasPermission(token, permission, entity, scope) + } if (!hasPermission) { throw NotAuthorizedException( "User ${token.username} with client ${token.clientId} does not have permission $permission to scope " + - "$scope of $entity" + "$scope of $entity", ) } } @@ -65,11 +71,16 @@ class AuthService( return oracle.referentsByScope(token, permission) } - fun mayBeGranted(role: RoleAuthority, permission: Permission): Boolean = with(oracle) { - role.mayBeGranted(permission) - } + fun mayBeGranted( + role: RoleAuthority, + permission: Permission, + ): Boolean = + with(oracle) { + role.mayBeGranted(permission) + } - fun mayBeGranted(authorities: Collection, permission: Permission): Boolean { - return authorities.any{ mayBeGranted(it, permission) } - } + fun mayBeGranted( + authorities: Collection, + permission: Permission, + ): Boolean = authorities.any { mayBeGranted(it, permission) } } diff --git a/src/main/java/org/radarbase/management/service/GroupService.kt b/src/main/java/org/radarbase/management/service/GroupService.kt index 400eb5e10..7c80ab30f 100644 --- a/src/main/java/org/radarbase/management/service/GroupService.kt +++ b/src/main/java/org/radarbase/management/service/GroupService.kt @@ -33,9 +33,8 @@ class GroupService( @Autowired private val groupRepository: GroupRepository, @Autowired private val projectRepository: ProjectRepository, @Autowired private val subjectRepository: SubjectRepository, - @Autowired private val groupMapper: GroupMapper + @Autowired private val groupMapper: GroupMapper, ) { - /** * Get the group by name. * @param projectName project name @@ -45,17 +44,20 @@ class GroupService( */ @Throws(NotFoundException::class) @Transactional - fun getGroup(projectName: String, groupName: String): GroupDTO { - return groupMapper.groupToGroupDTOFull( + fun getGroup( + projectName: String, + groupName: String, + ): GroupDTO = + groupMapper.groupToGroupDTOFull( groupRepository.findByProjectNameAndName( - projectName, groupName + projectName, + groupName, ) ?: throw NotFoundException( "Group $groupName not found in project $projectName", EntityName.GROUP, - ErrorConstants.ERR_GROUP_NOT_FOUND - ) + ErrorConstants.ERR_GROUP_NOT_FOUND, + ), ) - } /** * Delete the group by name. @@ -65,10 +67,17 @@ class GroupService( * @throws NotFoundException if the project or group is not found. */ @Transactional - fun deleteGroup(projectName: String, groupName: String, unlinkSubjects: Boolean) { - val group = groupRepository.findByProjectNameAndName(projectName, groupName) ?: throw NotFoundException( - "Group $groupName not found in project $projectName", EntityName.GROUP, ErrorConstants.ERR_GROUP_NOT_FOUND - ) + fun deleteGroup( + projectName: String, + groupName: String, + unlinkSubjects: Boolean, + ) { + val group = + groupRepository.findByProjectNameAndName(projectName, groupName) ?: throw NotFoundException( + "Group $groupName not found in project $projectName", + EntityName.GROUP, + ErrorConstants.ERR_GROUP_NOT_FOUND, + ) if (!unlinkSubjects) { val subjectCount = subjectRepository.countByGroupId(group.id) if (subjectCount > 0) { @@ -88,15 +97,21 @@ class GroupService( * @throws ConflictException if the group name already exists. */ @Transactional - fun createGroup(projectName: String, groupDto: GroupDTO): GroupDTO { - val project = projectRepository.findOneWithGroupsByName(projectName) ?: throw NotFoundException( - "Project with name $projectName not found", EntityName.PROJECT, ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND - ) + fun createGroup( + projectName: String, + groupDto: GroupDTO, + ): GroupDTO { + val project = + projectRepository.findOneWithGroupsByName(projectName) ?: throw NotFoundException( + "Project with name $projectName not found", + EntityName.PROJECT, + ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND, + ) if (project.groups.stream().anyMatch { g: Group -> g.name == groupDto.name }) { throw ConflictException( "Group " + groupDto.name + " already exists in project " + projectName, EntityName.GROUP, - ErrorConstants.ERR_GROUP_EXISTS + ErrorConstants.ERR_GROUP_EXISTS, ) } return try { @@ -107,15 +122,13 @@ class GroupService( project.groups.add(group) projectRepository.save(project) groupDtoResult - } catch(e: Throwable) { + } catch (e: Throwable) { throw NotFoundException( "Group ${groupDto.name} not found in project $projectName", EntityName.GROUP, - ErrorConstants.ERR_GROUP_NOT_FOUND + ErrorConstants.ERR_GROUP_NOT_FOUND, ) } - - } /** @@ -124,9 +137,12 @@ class GroupService( * @throws NotFoundException if the project is not found. */ fun listGroups(projectName: String): List { - val project = projectRepository.findOneWithGroupsByName(projectName) ?: throw NotFoundException( - "Project with name $projectName not found", EntityName.PROJECT, ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND - ) + val project = + projectRepository.findOneWithGroupsByName(projectName) ?: throw NotFoundException( + "Project with name $projectName not found", + EntityName.PROJECT, + ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND, + ) return groupMapper.groupToGroupDTOs(project.groups) } @@ -143,14 +159,16 @@ class GroupService( projectName: String, groupName: String, subjectsToAdd: List, - subjectsToRemove: List + subjectsToRemove: List, ) { - groupRepository - val group = groupRepository.findByProjectNameAndName(projectName, groupName) ?: throw NotFoundException( - "Group $groupName not found in project $projectName", EntityName.GROUP, ErrorConstants.ERR_GROUP_NOT_FOUND - ) + val group = + groupRepository.findByProjectNameAndName(projectName, groupName) ?: throw NotFoundException( + "Group $groupName not found in project $projectName", + EntityName.GROUP, + ErrorConstants.ERR_GROUP_NOT_FOUND, + ) val entitiesToAdd = getSubjectEntities(projectName, subjectsToAdd) val entitiesToRemove = getSubjectEntities(projectName, subjectsToRemove) @@ -165,7 +183,8 @@ class GroupService( } private fun getSubjectEntities( - projectName: String, subjectsToModify: List + projectName: String, + subjectsToModify: List, ): List { val logins: MutableList = ArrayList() val ids: MutableList = ArrayList() @@ -180,11 +199,15 @@ class GroupService( for (s in subjectEntities) { val login = s.user!!.login s.activeProject ?: throw BadRequestException( - "Subject $login is not assigned to a project", EntityName.SUBJECT, ErrorConstants.ERR_VALIDATION + "Subject $login is not assigned to a project", + EntityName.SUBJECT, + ErrorConstants.ERR_VALIDATION, ) if (projectName != s.activeProject!!.projectName) { throw BadRequestException( - "Subject $login belongs to a different project", EntityName.SUBJECT, ErrorConstants.ERR_VALIDATION + "Subject $login belongs to a different project", + EntityName.SUBJECT, + ErrorConstants.ERR_VALIDATION, ) } } @@ -192,7 +215,9 @@ class GroupService( } private fun extractSubjectIdentities( - subjectsToModify: List, logins: MutableList, ids: MutableList + subjectsToModify: List, + logins: MutableList, + ids: MutableList, ) { // Each item should specify either a login or an ID, // since having both will require an extra validation step @@ -204,14 +229,17 @@ class GroupService( val id = item.id if (id == null && login == null) { throw BadRequestException( - "Subject identification must be specified", EntityName.GROUP, ErrorConstants.ERR_VALIDATION + "Subject identification must be specified", + EntityName.GROUP, + ErrorConstants.ERR_VALIDATION, ) } if (id != null && login != null) { throw BadRequestException( - "Subject identification must be specify either ID or Login. " + "Do not provide both values to avoid potential confusion.", + "Subject identification must be specify either ID or Login. " + + "Do not provide both values to avoid potential confusion.", EntityName.GROUP, - ErrorConstants.ERR_VALIDATION + ErrorConstants.ERR_VALIDATION, ) } if (id != null) { diff --git a/src/main/java/org/radarbase/management/service/IdentityService.kt b/src/main/java/org/radarbase/management/service/IdentityService.kt index 672626188..1a9021741 100644 --- a/src/main/java/org/radarbase/management/service/IdentityService.kt +++ b/src/main/java/org/radarbase/management/service/IdentityService.kt @@ -1,13 +1,21 @@ package org.radarbase.management.service -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.accept +import io.ktor.client.request.delete +import io.ktor.client.request.post +import io.ktor.client.request.put +import io.ktor.client.request.setBody +import io.ktor.client.request.url +import io.ktor.http.ContentType +import io.ktor.http.contentType +import io.ktor.http.isSuccess +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json @@ -31,28 +39,31 @@ import java.time.Duration @Transactional class IdentityService( @Autowired private val managementPortalProperties: ManagementPortalProperties, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - private val httpClient = HttpClient(CIO).config { - install(HttpTimeout) { - connectTimeoutMillis = Duration.ofSeconds(10).toMillis() - socketTimeoutMillis = Duration.ofSeconds(10).toMillis() - requestTimeoutMillis = Duration.ofSeconds(300).toMillis() - } - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - coerceInputValues = true - }) + private val httpClient = + HttpClient(CIO).config { + install(HttpTimeout) { + connectTimeoutMillis = Duration.ofSeconds(10).toMillis() + socketTimeoutMillis = Duration.ofSeconds(10).toMillis() + requestTimeoutMillis = Duration.ofSeconds(300).toMillis() + } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + coerceInputValues = true + }, + ) + } } - } lateinit var adminUrl: String lateinit var publicUrl: String init { - adminUrl = managementPortalProperties.identityServer.adminUrl() - publicUrl = managementPortalProperties.identityServer.publicUrl() + adminUrl = managementPortalProperties.identityServer.adminUrl() ?: "undefined" + publicUrl = managementPortalProperties.identityServer.publicUrl() ?: "undefined" log.debug("kratos serverUrl set to ${managementPortalProperties.identityServer.publicUrl()}") log.debug("kratos serverAdminUrl set to ${managementPortalProperties.identityServer.adminUrl()}") @@ -66,12 +77,13 @@ class IdentityService( withContext(Dispatchers.IO) { val identity = createIdentity(user) - val postRequestBuilder = HttpRequestBuilder().apply { - url("${adminUrl}/admin/identities") - contentType(ContentType.Application.Json) - accept(ContentType.Application.Json) - setBody(identity) - } + val postRequestBuilder = + HttpRequestBuilder().apply { + url("$adminUrl/admin/identities") + contentType(ContentType.Application.Json) + accept(ContentType.Application.Json) + setBody(identity) + } val response = httpClient.post(postRequestBuilder) if (response.status.isSuccess()) { @@ -98,20 +110,20 @@ class IdentityService( withContext(Dispatchers.IO) { val identity = createIdentity(user) - val response = httpClient.put { - url("${adminUrl}/admin/identities/${user.identity}") - contentType(ContentType.Application.Json) - accept(ContentType.Application.Json) - setBody(identity) - } - + val response = + httpClient.put { + url("$adminUrl/admin/identities/${user.identity}") + contentType(ContentType.Application.Json) + accept(ContentType.Application.Json) + setBody(identity) + } if (response.status.isSuccess()) { kratosIdentity = response.body() log.debug("Updated identity for user ${user.login} to IDP as ${kratosIdentity.id}") } else { throw IdpException( - "Couldn't update identity to server at $adminUrl" + "Couldn't update identity to server at $adminUrl", ) } } @@ -124,21 +136,21 @@ class IdentityService( suspend fun deleteAssociatedIdentity(userIdentity: String?) { withContext(Dispatchers.IO) { userIdentity ?: throw IdpException( - "user with ID ${userIdentity} could not be deleted from the IDP. No identity was set" + "user with ID $userIdentity could not be deleted from the IDP. No identity was set", ) - val response = httpClient.delete { - url("${adminUrl}/admin/identities/${userIdentity}") - contentType(ContentType.Application.Json) - accept(ContentType.Application.Json) - } - + val response = + httpClient.delete { + url("$adminUrl/admin/identities/$userIdentity") + contentType(ContentType.Application.Json) + accept(ContentType.Application.Json) + } if (response.status.isSuccess()) { - log.debug("Deleted identity for user ${userIdentity}") + log.debug("Deleted identity for user $userIdentity") } else { throw IdpException( - "Couldn't delete identity from server at " + managementPortalProperties.identityServer.serverUrl + "Couldn't delete identity from server at " + managementPortalProperties.identityServer.serverUrl, ) } } @@ -155,30 +167,33 @@ class IdentityService( return KratosSessionDTO.Identity( schema_id = "user", traits = KratosSessionDTO.Traits(email = user.email), - metadata_public = KratosSessionDTO.Metadata( - aud = emptyList(), - sources = emptyList(), //empty at the time of creation - roles = user.roles.mapNotNull { role: Role -> - val auth = role.authority?.name - when (role.role?.scope) { - RoleAuthority.Scope.GLOBAL -> auth - RoleAuthority.Scope.ORGANIZATION -> role.organization!!.name + ":" + auth - RoleAuthority.Scope.PROJECT -> role.project!!.projectName + ":" + auth - null -> null - } - }.toList(), - authorities = user.authorities, - scope = Permission.scopes().filter { scope -> - val permission = Permission.ofScope(scope) - val auths = user.roles.mapNotNull { it.role } - - return@filter authService.mayBeGranted(auths, permission) - }, - mp_login = user.login - ) + metadata_public = + KratosSessionDTO.Metadata( + aud = emptyList(), + sources = emptyList(), // empty at the time of creation + roles = + user.roles + .mapNotNull { role: Role -> + val auth = role.authority?.name + when (role.role?.scope) { + RoleAuthority.Scope.GLOBAL -> auth + RoleAuthority.Scope.ORGANIZATION -> role.organization!!.name + ":" + auth + RoleAuthority.Scope.PROJECT -> role.project!!.projectName + ":" + auth + null -> null + } + }.toList(), + authorities = user.authorities, + scope = + Permission.scopes().filter { scope -> + val permission = Permission.ofScope(scope) + val auths = user.roles.mapNotNull { it.role } + + return@filter authService.mayBeGranted(auths, permission) + }, + mp_login = user.login, + ), ) - } - catch (e: Throwable){ + } catch (e: Throwable) { val message = "could not convert user ${user.login} to identity" log.error(message) throw IdpException(message, e) @@ -200,24 +215,25 @@ class IdentityService( ) withContext(Dispatchers.IO) { - val response = httpClient.post { - url("${adminUrl}/admin/recovery/link") - contentType(ContentType.Application.Json) - accept(ContentType.Application.Json) - setBody( - mapOf( - "expires_in" to "24h", - "identity_id" to user.identity + val response = + httpClient.post { + url("$adminUrl/admin/recovery/link") + contentType(ContentType.Application.Json) + accept(ContentType.Application.Json) + setBody( + mapOf( + "expires_in" to "24h", + "identity_id" to user.identity, + ), ) - ) - } + } if (response.status.isSuccess()) { recoveryLink = response.body>()["recovery_link"]!! log.debug("recovery link for user ${user.login} is $recoveryLink") } else { throw IdpException( - "couldn't get recovery link from server at $adminUrl" + "couldn't get recovery link from server at $adminUrl", ) } } diff --git a/src/main/java/org/radarbase/management/service/MailService.kt b/src/main/java/org/radarbase/management/service/MailService.kt index 05496e60f..3b1e2ff01 100644 --- a/src/main/java/org/radarbase/management/service/MailService.kt +++ b/src/main/java/org/radarbase/management/service/MailService.kt @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.MessageSource import org.springframework.mail.javamail.JavaMailSender -import org.springframework.mail.javamail.JavaMailSenderImpl import org.springframework.mail.javamail.MimeMessageHelper import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service @@ -28,7 +27,6 @@ class MailService( @Autowired private val messageSource: MessageSource, @Autowired private val templateEngine: SpringTemplateEngine, ) { - /** * Send an email. * @param to email address to send to @@ -39,21 +37,30 @@ class MailService( */ @Async fun sendEmail( - to: String?, subject: String?, content: String?, isMultipart: Boolean, - isHtml: Boolean + to: String?, + subject: String?, + content: String?, + isMultipart: Boolean, + isHtml: Boolean, ) { log.debug( "Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}", - isMultipart, isHtml, to, subject, content + isMultipart, + isHtml, + to, + subject, + content, ) // Prepare message using a Spring helper val mimeMessage = javaMailSender.createMimeMessage() try { - val message = MimeMessageHelper( - mimeMessage, isMultipart, - StandardCharsets.UTF_8.name() - ) + val message = + MimeMessageHelper( + mimeMessage, + isMultipart, + StandardCharsets.UTF_8.name(), + ) message.setTo(to!!) message.setFrom(managementPortalProperties.mail.from) message.setSubject(subject!!) @@ -77,7 +84,7 @@ class MailService( context.setVariable(USER, user) context.setVariable( BASE_URL, - managementPortalProperties.common.managementPortalBaseUrl + managementPortalProperties.common.managementPortalBaseUrl, ) val content = templateEngine.process("activationEmail", context) val subject = messageSource.getMessage("email.activation.title", null, locale) @@ -89,14 +96,17 @@ class MailService( * @param user the user */ @Async - fun sendCreationEmail(user: User, duration: Long) { + fun sendCreationEmail( + user: User, + duration: Long, + ) { log.debug("Sending creation email to '{}'", user.email) val locale = Locale.forLanguageTag(user.langKey) val context = Context(locale) context.setVariable(USER, user) context.setVariable( BASE_URL, - managementPortalProperties.common.managementPortalBaseUrl + managementPortalProperties.common.managementPortalBaseUrl, ) context.setVariable(EXPIRY, Duration.ofSeconds(duration).toHours()) val content = templateEngine.process("creationEmail", context) @@ -110,14 +120,17 @@ class MailService( * @param email the address to send to */ @Async - fun sendCreationEmailForGivenEmail(user: User, email: String?) { + fun sendCreationEmailForGivenEmail( + user: User, + email: String?, + ) { log.debug("Sending creation email to '{}'", email) val locale = Locale.forLanguageTag(user.langKey) val context = Context(locale) context.setVariable(USER, user) context.setVariable( BASE_URL, - managementPortalProperties.common.managementPortalBaseUrl + managementPortalProperties.common.managementPortalBaseUrl, ) val content = templateEngine.process("creationEmail", context) val subject = messageSource.getMessage("email.activation.title", null, locale) @@ -136,7 +149,7 @@ class MailService( context.setVariable(USER, user) context.setVariable( BASE_URL, - managementPortalProperties.common.managementPortalBaseUrl + managementPortalProperties.common.managementPortalBaseUrl, ) val content = templateEngine.process("passwordResetEmail", context) val subject = messageSource.getMessage("email.reset.title", null, locale) diff --git a/src/main/java/org/radarbase/management/service/MetaTokenService.kt b/src/main/java/org/radarbase/management/service/MetaTokenService.kt index 529011bd7..0f2f0caf6 100644 --- a/src/main/java/org/radarbase/management/service/MetaTokenService.kt +++ b/src/main/java/org/radarbase/management/service/MetaTokenService.kt @@ -75,19 +75,21 @@ class MetaTokenService { val metaToken = getToken(tokenName) // process the response if the token is not fetched or not expired return if (metaToken.isValid) { - val refreshToken = oAuthClientService!!.createAccessToken( - metaToken.subject!!.user!!, - metaToken.clientId!! - ) - .refreshToken - .value + val refreshToken = + oAuthClientService!! + .createAccessToken( + metaToken.subject!!.user!!, + metaToken.clientId!!, + ).refreshToken + .value // create response - val result = TokenDTO( - refreshToken, - URL(managementPortalProperties!!.common.baseUrl), - subjectService!!.getPrivacyPolicyUrl(metaToken.subject!!) - ) + val result = + TokenDTO( + refreshToken, + URL(managementPortalProperties!!.common.baseUrl), + subjectService!!.getPrivacyPolicyUrl(metaToken.subject!!), + ) // change fetched status to true. if (!metaToken.isFetched()) { @@ -98,7 +100,8 @@ class MetaTokenService { } else { throw RequestGoneException( "Token $tokenName already fetched or expired. ", - EntityName.META_TOKEN, "error.TokenCannotBeSent" + EntityName.META_TOKEN, + "error.TokenCannotBeSent", ) } } @@ -110,15 +113,14 @@ class MetaTokenService { * @return fetched token as [MetaToken]. */ @Transactional(readOnly = true) - fun getToken(tokenName: String): MetaToken { - return metaTokenRepository!!.findOneByTokenName(tokenName) + fun getToken(tokenName: String): MetaToken = + metaTokenRepository!!.findOneByTokenName(tokenName) ?: throw NotFoundException( - "Meta token not found with tokenName", - EntityName.META_TOKEN, - ErrorConstants.ERR_TOKEN_NOT_FOUND, - Collections.singletonMap("tokenName", tokenName) - ) - } + "Meta token not found with tokenName", + EntityName.META_TOKEN, + ErrorConstants.ERR_TOKEN_NOT_FOUND, + Collections.singletonMap("tokenName", tokenName), + ) /** * Saves a unique meta-token instance, by checking for token-name collision. @@ -130,15 +132,16 @@ class MetaTokenService { clientId: String?, fetched: Boolean?, expiryTime: Instant?, - persistent: Boolean + persistent: Boolean, ): MetaToken { - val metaToken = MetaToken() - .generateName(if (persistent) MetaToken.LONG_ID_LENGTH else MetaToken.SHORT_ID_LENGTH) - .fetched(fetched!!) - .expiryDate(expiryTime) - .subject(subject) - .clientId(clientId) - .persistent(persistent) + val metaToken = + MetaToken() + .generateName(if (persistent) MetaToken.LONG_ID_LENGTH else MetaToken.SHORT_ID_LENGTH) + .fetched(fetched!!) + .expiryDate(expiryTime) + .subject(subject) + .clientId(clientId) + .persistent(persistent) return try { metaTokenRepository!!.save(metaToken) } catch (e: ConstraintViolationException) { @@ -157,16 +160,28 @@ class MetaTokenService { * @throws MalformedURLException when token URL cannot be formed properly. */ @Throws(URISyntaxException::class, MalformedURLException::class, NotAuthorizedException::class) - fun createMetaToken(subject: Subject, clientId: String?, persistent: Boolean): ClientPairInfoDTO { - val timeout = getMetaTokenTimeout(persistent, project = subject.activeProject - ?:throw NotAuthorizedException("Cannot calculate meta-token duration without configured project") - ) + fun createMetaToken( + subject: Subject, + clientId: String?, + persistent: Boolean, + ): ClientPairInfoDTO { + val timeout = + getMetaTokenTimeout( + persistent, + project = + subject.activeProject + ?: throw NotAuthorizedException("Cannot calculate meta-token duration without configured project"), + ) // tokenName should be generated - val metaToken = saveUniqueToken( - subject, clientId, false, - Instant.now().plus(timeout), persistent - ) + val metaToken = + saveUniqueToken( + subject, + clientId, + false, + Instant.now().plus(timeout), + persistent, + ) val tokenName = metaToken.tokenName return if (metaToken.id != null && tokenName != null) { // get base url from settings @@ -175,13 +190,16 @@ class MetaTokenService { val tokenUrl = baseUrl + ResourceUriService.getUri(metaToken).getPath() // create response ClientPairInfoDTO( - URL(baseUrl), tokenName, - URL(tokenUrl), timeout + URL(baseUrl), + tokenName, + URL(tokenUrl), + timeout, ) } else { throw InvalidStateException( - "Could not create a valid token", EntityName.OAUTH_CLIENT, - "error.couldNotCreateToken" + "Could not create a valid token", + EntityName.OAUTH_CLIENT, + "error.couldNotCreateToken", ) } } @@ -193,7 +211,10 @@ class MetaTokenService { * @return meta-token timeout duration. * @throws BadRequestException if a persistent token is requested but it is not configured. */ - fun getMetaTokenTimeout(persistent: Boolean, project: Project?): Duration { + fun getMetaTokenTimeout( + persistent: Boolean, + project: Project?, + ): Duration { val timeoutConfig: String? val defaultTimeout: Duration if (persistent) { @@ -201,7 +222,8 @@ class MetaTokenService { if (timeoutConfig == null || timeoutConfig.isEmpty()) { throw BadRequestException( "Cannot create persistent token: not supported in configuration.", - EntityName.META_TOKEN, ErrorConstants.ERR_PERSISTENT_TOKEN_DISABLED + EntityName.META_TOKEN, + ErrorConstants.ERR_PERSISTENT_TOKEN_DISABLED, ) } defaultTimeout = MetaTokenResource.DEFAULT_PERSISTENT_META_TOKEN_TIMEOUT @@ -218,7 +240,8 @@ class MetaTokenService { // if the token timeout cannot be read, log the error and use the default value. log.warn( "Cannot parse meta-token timeout config. Using default value {}", - defaultTimeout, e + defaultTimeout, + e, ) defaultTimeout } @@ -232,14 +255,17 @@ class MetaTokenService { @Scheduled(cron = "0 0 0 1 * ?") fun removeStaleTokens() { log.info("Scheduled scan for expired and fetched meta-tokens starting now") - metaTokenRepository!!.findAllByFetchedOrExpired(Instant.now()) - .forEach(Consumer { metaToken: MetaToken -> - log.info( - "Deleting deleting expired or fetched token {}", - metaToken.tokenName - ) - metaTokenRepository.delete(metaToken) - }) + metaTokenRepository!! + .findAllByFetchedOrExpired(Instant.now()) + .forEach( + Consumer { metaToken: MetaToken -> + log.info( + "Deleting deleting expired or fetched token {}", + metaToken.tokenName, + ) + metaTokenRepository.delete(metaToken) + }, + ) } fun delete(token: MetaToken) { diff --git a/src/main/java/org/radarbase/management/service/OAuthClientService.kt b/src/main/java/org/radarbase/management/service/OAuthClientService.kt index 7f09ce8f7..6af65f52c 100644 --- a/src/main/java/org/radarbase/management/service/OAuthClientService.kt +++ b/src/main/java/org/radarbase/management/service/OAuthClientService.kt @@ -32,12 +32,9 @@ import java.util.* class OAuthClientService( @Autowired private val clientDetailsService: JdbcClientDetailsService, @Autowired private val clientDetailsMapper: ClientDetailsMapper, - @Autowired private val authorizationServerEndpointsConfiguration: AuthorizationServerEndpointsConfiguration + @Autowired private val authorizationServerEndpointsConfiguration: AuthorizationServerEndpointsConfiguration, ) { - - fun findAllOAuthClients(): List { - return clientDetailsService.listClientDetails() - } + fun findAllOAuthClients(): List = clientDetailsService.listClientDetails() /** * Find ClientDetails by OAuth client id. @@ -46,19 +43,20 @@ class OAuthClientService( * @return a ClientDetails object with the requested client ID * @throws NotFoundException If there is no client with the requested ID */ - fun findOneByClientId(clientId: String?): ClientDetails { - return try { + fun findOneByClientId(clientId: String?): ClientDetails = + try { clientDetailsService.loadClientByClientId(clientId) } catch (e: NoSuchClientException) { log.error("Pair client request for unknown client id: {}", clientId) val errorParams: MutableMap = HashMap() errorParams["clientId"] = clientId throw NotFoundException( - "Client not found for client-id", EntityName.Companion.OAUTH_CLIENT, - ErrorConstants.ERR_OAUTH_CLIENT_ID_NOT_FOUND, errorParams + "Client not found for client-id", + EntityName.Companion.OAUTH_CLIENT, + ErrorConstants.ERR_OAUTH_CLIENT_ID_NOT_FOUND, + errorParams, ) } - } /** * Update Oauth-client with new information. @@ -67,7 +65,7 @@ class OAuthClientService( * @return Updated [ClientDetails] instance. */ fun updateOauthClient(clientDetailsDto: ClientDetailsDTO): ClientDetails { - val details: ClientDetails? = clientDetailsMapper.clientDetailsDTOToClientDetails(clientDetailsDto) + val details: ClientDetails = clientDetailsMapper.clientDetailsDTOToClientDetails(clientDetailsDto) // update client. clientDetailsService.updateClientDetails(details) val updated = findOneByClientId(clientDetailsDto.clientId) @@ -75,7 +73,7 @@ class OAuthClientService( if (clientDetailsDto.clientSecret != null && clientDetailsDto.clientSecret != updated.clientSecret) { clientDetailsService.updateClientSecret( clientDetailsDto.clientId, - clientDetailsDto.clientSecret + clientDetailsDto.clientSecret, ) } return findOneByClientId(clientDetailsDto.clientId) @@ -102,18 +100,19 @@ class OAuthClientService( if (existingClient != null) { throw ConflictException( "OAuth client already exists with this id", - EntityName.Companion.OAUTH_CLIENT, ErrorConstants.ERR_CLIENT_ID_EXISTS, - Collections.singletonMap("client_id", clientDetailsDto.clientId) + EntityName.Companion.OAUTH_CLIENT, + ErrorConstants.ERR_CLIENT_ID_EXISTS, + Collections.singletonMap("client_id", clientDetailsDto.clientId), ) } } catch (ex: NoSuchClientException) { // Client does not exist yet, we can go ahead and create it log.info( "No client existing with client-id {}. Proceeding to create new client", - clientDetailsDto.clientId + clientDetailsDto.clientId, ) } - val details: ClientDetails? = clientDetailsMapper.clientDetailsDTOToClientDetails(clientDetailsDto) + val details: ClientDetails = clientDetailsMapper.clientDetailsDTOToClientDetails(clientDetailsDto) // create oauth client. clientDetailsService.addClientDetails(details) return findOneByClientId(clientDetailsDto.clientId) @@ -128,24 +127,42 @@ class OAuthClientService( * @param user user of the token. * @return Created [OAuth2AccessToken] instance. */ - fun createAccessToken(user: User, clientId: String): OAuth2AccessToken { - val authorities = user.authorities!! - .map { a -> SimpleGrantedAuthority(a) } + fun createAccessToken( + user: User, + clientId: String, + ): OAuth2AccessToken { + val authorities = + user.authorities + .map { a -> SimpleGrantedAuthority(a) } // lookup the OAuth client // getOAuthClient checks if the id exists val client = findOneByClientId(clientId) - val requestParameters = Collections.singletonMap( - OAuth2Utils.GRANT_TYPE, "authorization_code" - ) + val requestParameters = + Collections.singletonMap( + OAuth2Utils.GRANT_TYPE, + "authorization_code", + ) val responseTypes = setOf("code") - val oAuth2Request = OAuth2Request( - requestParameters, clientId, authorities, true, client.scope, - client.resourceIds, null, responseTypes, emptyMap() - ) - val authenticationToken: Authentication = UsernamePasswordAuthenticationToken( - user.login, null, authorities - ) - return authorizationServerEndpointsConfiguration.getEndpointsConfigurer() + val oAuth2Request = + OAuth2Request( + requestParameters, + clientId, + authorities, + true, + client.scope, + client.resourceIds, + null, + responseTypes, + emptyMap(), + ) + val authenticationToken: Authentication = + UsernamePasswordAuthenticationToken( + user.login, + null, + authorities, + ) + return authorizationServerEndpointsConfiguration + .getEndpointsConfigurer() .tokenServices .createAccessToken(OAuth2Authentication(oAuth2Request, authenticationToken)) } @@ -161,13 +178,17 @@ class OAuthClientService( */ fun checkProtected(details: ClientDetails) { val info = details.additionalInformation - if (Objects.nonNull(info) && info.containsKey(PROTECTED_KEY) && info[PROTECTED_KEY] - .toString().equals("true", ignoreCase = true) + if (Objects.nonNull(info) && + info.containsKey(PROTECTED_KEY) && + info[PROTECTED_KEY] + .toString() + .equals("true", ignoreCase = true) ) { throw InvalidRequestException( - "Cannot modify protected client", EntityName.Companion.OAUTH_CLIENT, + "Cannot modify protected client", + EntityName.Companion.OAUTH_CLIENT, ErrorConstants.ERR_OAUTH_CLIENT_PROTECTED, - Collections.singletonMap("client_id", details.clientId) + Collections.singletonMap("client_id", details.clientId), ) } } diff --git a/src/main/java/org/radarbase/management/service/OrganizationService.kt b/src/main/java/org/radarbase/management/service/OrganizationService.kt index 46f3c8e3e..4a339d765 100644 --- a/src/main/java/org/radarbase/management/service/OrganizationService.kt +++ b/src/main/java/org/radarbase/management/service/OrganizationService.kt @@ -24,9 +24,8 @@ class OrganizationService( @Autowired private val projectRepository: ProjectRepository, @Autowired private val organizationMapper: OrganizationMapper, @Autowired private val projectMapper: ProjectMapper, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * Save an organization. * @@ -49,18 +48,20 @@ class OrganizationService( fun findAll(): List { val organizationsOfUser: List val referents = authService.referentsByScope(Permission.ORGANIZATION_READ) - organizationsOfUser = if (referents.global) { - organizationRepository.findAll() - } else { - val projectNames = referents.allProjects - val organizationsOfProject = - organizationRepository.findAllByProjectNames(projectNames) - val organizationsOfRole = referents.organizations - .mapNotNull { name: String -> organizationRepository.findOneByName(name) } - (organizationsOfRole + organizationsOfProject) - .distinct() - .toList() - } + organizationsOfUser = + if (referents.global) { + organizationRepository.findAll() + } else { + val projectNames = referents.allProjects + val organizationsOfProject = + organizationRepository.findAllByProjectNames(projectNames) + val organizationsOfRole = + referents.organizations + .mapNotNull { name: String -> organizationRepository.findOneByName(name) } + (organizationsOfRole + organizationsOfProject) + .distinct() + .toList() + } return organizationMapper.organizationsToOrganizationDTOs(organizationsOfUser) } @@ -87,18 +88,20 @@ class OrganizationService( if (referents.isEmpty()) { return emptyList() } - val projectStream: List = if (referents.global || referents.hasOrganization(organizationName)) { - projectRepository.findAllByOrganizationName(organizationName) - } else if (referents.hasAnyProjects()) { - projectRepository.findAllByOrganizationName(organizationName) - .filter { project: Project -> - referents.hasAnyProject( - project.projectName!! - ) - } - } else { - return listOf() - } + val projectStream: List = + if (referents.global || referents.hasOrganization(organizationName)) { + projectRepository.findAllByOrganizationName(organizationName) + } else if (referents.hasAnyProjects()) { + projectRepository + .findAllByOrganizationName(organizationName) + .filter { project: Project -> + referents.hasAnyProject( + project.projectName!!, + ) + } + } else { + return listOf() + } return projectStream .mapNotNull { project: Project? -> projectMapper.projectToProjectDTO(project) } .toList() diff --git a/src/main/java/org/radarbase/management/service/package-info.kt b/src/main/java/org/radarbase/management/service/PackageInfo.kt similarity index 60% rename from src/main/java/org/radarbase/management/service/package-info.kt rename to src/main/java/org/radarbase/management/service/PackageInfo.kt index 763684f5a..5f742a472 100644 --- a/src/main/java/org/radarbase/management/service/package-info.kt +++ b/src/main/java/org/radarbase/management/service/PackageInfo.kt @@ -1,5 +1,6 @@ /** * Service layer beans. */ -package org.radarbase.management.service +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.service diff --git a/src/main/java/org/radarbase/management/service/PasswordService.kt b/src/main/java/org/radarbase/management/service/PasswordService.kt index 9245e110e..bb657336b 100644 --- a/src/main/java/org/radarbase/management/service/PasswordService.kt +++ b/src/main/java/org/radarbase/management/service/PasswordService.kt @@ -17,7 +17,9 @@ import java.security.SecureRandom import java.util.* @Service -class PasswordService(private val passwordEncoder: PasswordEncoder) { +class PasswordService( + private val passwordEncoder: PasswordEncoder, +) { private val random: Random = SecureRandom() /** @@ -25,39 +27,36 @@ class PasswordService(private val passwordEncoder: PasswordEncoder) { * @param password password to encode. * @return encoded password. */ - fun encode(password: String?): String { - return passwordEncoder.encode(password) - } + fun encode(password: String?): String = passwordEncoder.encode(password) /** * Generates a random password that is already encoded. * @return encoded password. */ - fun generateEncodedPassword(): String { - return encode(generateString(ALPHANUMERIC, 30)) - } + fun generateEncodedPassword(): String = encode(generateString(ALPHANUMERIC, 30)) /** * Generates a random numeric reset key. * @return reset key. */ - fun generateResetKey(): String { - return generateString(NUMERIC, 20) - } + fun generateResetKey(): String = generateString(NUMERIC, 20) - private fun generateString(allowedCharacters: IntArray, length: Int): String { - return random.ints(0, allowedCharacters.size) + private fun generateString( + allowedCharacters: IntArray, + length: Int, + ): String = + random + .ints(0, allowedCharacters.size) .map { i: Int -> allowedCharacters[i] } .limit(length.toLong()) .collect( { StringBuilder(length) }, - { obj: StringBuilder, codePoint: Int -> obj.appendCodePoint(codePoint) }) { obj: StringBuilder, s: StringBuilder? -> + { obj: StringBuilder, codePoint: Int -> obj.appendCodePoint(codePoint) }, + ) { obj: StringBuilder, s: StringBuilder? -> obj.append( - s + s, ) - } - .toString() - } + }.toString() /** * Check that given password is strong enough, based on complexity and length. @@ -67,30 +66,34 @@ class PasswordService(private val passwordEncoder: PasswordEncoder) { fun checkPasswordStrength(password: String?) { if (isPasswordWeak(password)) { throw BadRequestException( - "Weak password. Use a password with more variety of" - + "numeric, alphabetical and symbol characters.", + "Weak password. Use a password with more variety of" + + "numeric, alphabetical and symbol characters.", EntityName.Companion.USER, - ErrorConstants.ERR_PASSWORD_TOO_WEAK + ErrorConstants.ERR_PASSWORD_TOO_WEAK, ) } else if (password!!.length > 100) { throw BadRequestException( "Password too long", EntityName.Companion.USER, - ErrorConstants.ERR_PASSWORD_TOO_LONG + ErrorConstants.ERR_PASSWORD_TOO_LONG, ) } } /** Check whether given password is too weak. */ - private fun isPasswordWeak(password: String?): Boolean { - return (password!!.length < 8 || noneInRange(password, UPPER[0], UPPER[UPPER.size - 1]) - || noneInRange(password, LOWER[0], LOWER[LOWER.size - 1]) - || noneInRange(password, NUMERIC[0], NUMERIC[NUMERIC.size - 1])) - } + private fun isPasswordWeak(password: String?): Boolean = + ( + password!!.length < 8 || + noneInRange(password, UPPER[0], UPPER[UPPER.size - 1]) || + noneInRange(password, LOWER[0], LOWER[LOWER.size - 1]) || + noneInRange(password, NUMERIC[0], NUMERIC[NUMERIC.size - 1]) + ) - private fun noneInRange(str: String?, startInclusive: Int, endInclusive: Int): Boolean { - return str!!.chars().noneMatch { c: Int -> c >= startInclusive && c < endInclusive } - } + private fun noneInRange( + str: String?, + startInclusive: Int, + endInclusive: Int, + ): Boolean = str!!.chars().noneMatch { c: Int -> c >= startInclusive && c < endInclusive } companion object { val NUMERIC: IntArray diff --git a/src/main/java/org/radarbase/management/service/ProjectService.kt b/src/main/java/org/radarbase/management/service/ProjectService.kt index c778fc4b1..8b206fcfc 100644 --- a/src/main/java/org/radarbase/management/service/ProjectService.kt +++ b/src/main/java/org/radarbase/management/service/ProjectService.kt @@ -28,9 +28,8 @@ class ProjectService( @Autowired private val projectRepository: ProjectRepository, @Autowired private val projectMapper: ProjectMapper, @Autowired private val sourceTypeMapper: SourceTypeMapper, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * Save a project. * @@ -50,18 +49,24 @@ class ProjectService( * @return the list of entities */ @Transactional(readOnly = true) - fun findAll(fetchMinimal: Boolean, pageable: Pageable): Page<*> { + fun findAll( + fetchMinimal: Boolean, + pageable: Pageable, + ): Page<*> { val projects: Page val referents = authService.referentsByScope(Permission.PROJECT_READ) - projects = if (referents.isEmpty()) { - PageImpl(listOf()) - } else if (referents.global) { - projectRepository.findAllWithEagerRelationships(pageable) - } else { - projectRepository.findAllWithEagerRelationshipsInOrganizationsOrProjects( - pageable, referents.organizations, referents.allProjects - ) - } + projects = + if (referents.isEmpty()) { + PageImpl(listOf()) + } else if (referents.global) { + projectRepository.findAllWithEagerRelationships(pageable) + } else { + projectRepository.findAllWithEagerRelationshipsInOrganizationsOrProjects( + pageable, + referents.organizations, + referents.allProjects, + ) + } return if (!fetchMinimal) { projects.map { project: Project -> projectMapper.projectToProjectDTO(project) } } else { @@ -89,13 +94,14 @@ class ProjectService( @Transactional(readOnly = true) fun findOne(id: Long): ProjectDTO? { log.debug("Request to get Project : {}", id) - val project = projectRepository.findOneWithEagerRelationships(id) - ?: throw NotFoundException( - "Project not found with id", - EntityName.PROJECT, - ErrorConstants.ERR_PROJECT_ID_NOT_FOUND, - Collections.singletonMap("id", id.toString()) - ) + val project = + projectRepository.findOneWithEagerRelationships(id) + ?: throw NotFoundException( + "Project not found with id", + EntityName.PROJECT, + ErrorConstants.ERR_PROJECT_ID_NOT_FOUND, + Collections.singletonMap("id", id.toString()), + ) return projectMapper.projectToProjectDTO(project) } @@ -109,12 +115,14 @@ class ProjectService( @Transactional(readOnly = true) fun findOneByName(name: String): ProjectDTO { log.debug("Request to get Project by name: {}", name) - val project = projectRepository.findOneWithEagerRelationshipsByName(name) - ?: throw NotFoundException( - "Project not found with projectName $name", - EntityName.PROJECT, - ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND, - Collections.singletonMap("projectName", name)) + val project = + projectRepository.findOneWithEagerRelationshipsByName(name) + ?: throw NotFoundException( + "Project not found with projectName $name", + EntityName.PROJECT, + ErrorConstants.ERR_PROJECT_NAME_NOT_FOUND, + Collections.singletonMap("projectName", name), + ) return projectMapper.projectToProjectDTO(project)!! } diff --git a/src/main/java/org/radarbase/management/service/ResourceUriService.kt b/src/main/java/org/radarbase/management/service/ResourceUriService.kt index ecb5ad361..b1d159db4 100644 --- a/src/main/java/org/radarbase/management/service/ResourceUriService.kt +++ b/src/main/java/org/radarbase/management/service/ResourceUriService.kt @@ -27,9 +27,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: OrganizationDTO): URI { - return URI(HeaderUtil.buildPath("api", "organizations", resource.name)) - } + fun getUri(resource: OrganizationDTO): URI = URI(HeaderUtil.buildPath("api", "organizations", resource.name)) /** * Get the API location for the given resource. @@ -38,9 +36,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: SubjectDTO): URI { - return URI(resource.login?.let { HeaderUtil.buildPath("api", "subjects", it) }) - } + fun getUri(resource: SubjectDTO): URI = URI(resource.login?.let { HeaderUtil.buildPath("api", "subjects", it) }) /** * Get the API location for the given resource. @@ -49,9 +45,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: ClientDetailsDTO): URI { - return URI(HeaderUtil.buildPath("api", "oauth-clients", resource.clientId)) - } + fun getUri(resource: ClientDetailsDTO): URI = URI(HeaderUtil.buildPath("api", "oauth-clients", resource.clientId)) /** * Get the API location for the given resource. @@ -60,9 +54,8 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: MinimalSourceDetailsDTO): URI { - return URI(HeaderUtil.buildPath("api", "sources", resource.sourceName!!)) - } + fun getUri(resource: MinimalSourceDetailsDTO): URI = + URI(HeaderUtil.buildPath("api", "sources", resource.sourceName!!)) /** * Get the API location for the given resource. @@ -71,14 +64,15 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: RoleDTO?): URI { - return URI( + fun getUri(resource: RoleDTO?): URI = + URI( HeaderUtil.buildPath( - "api", "roles", resource?.projectName!!, - resource.authorityName!! - ) + "api", + "roles", + resource?.projectName!!, + resource.authorityName!!, + ), ) - } /** * Get the API location for the given resource. @@ -87,14 +81,16 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: SourceTypeDTO): URI { - return URI( + fun getUri(resource: SourceTypeDTO): URI = + URI( HeaderUtil.buildPath( - "api", "source-types", resource.producer, - resource.model, resource.catalogVersion - ) + "api", + "source-types", + resource.producer, + resource.model, + resource.catalogVersion, + ), ) - } /** * Get the API location for the given resource. @@ -103,9 +99,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: SourceDTO): URI { - return URI(HeaderUtil.buildPath("api", "sources", resource.sourceName)) - } + fun getUri(resource: SourceDTO): URI = URI(HeaderUtil.buildPath("api", "sources", resource.sourceName)) /** * Get the API location for the given resource. @@ -114,9 +108,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: Source): URI { - return URI(HeaderUtil.buildPath("api", "sources", resource.sourceName!!)) - } + fun getUri(resource: Source): URI = URI(HeaderUtil.buildPath("api", "sources", resource.sourceName!!)) /** * Get the API location for the given resource. @@ -125,9 +117,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: User): URI { - return URI(HeaderUtil.buildPath("api", "users", resource.login)) - } + fun getUri(resource: User): URI = URI(HeaderUtil.buildPath("api", "users", resource.login)) /** * Get the API location for the given resource. @@ -136,9 +126,8 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: SourceDataDTO): URI { - return URI(HeaderUtil.buildPath("api", "source-data", resource.sourceDataName!!)) - } + fun getUri(resource: SourceDataDTO): URI = + URI(HeaderUtil.buildPath("api", "source-data", resource.sourceDataName!!)) /** * Get the API location for the given resource. @@ -147,9 +136,7 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: ProjectDTO): URI { - return URI(HeaderUtil.buildPath("api", "projects", resource.projectName!!)) - } + fun getUri(resource: ProjectDTO): URI = URI(HeaderUtil.buildPath("api", "projects", resource.projectName!!)) /** * Get the API location for the given resource. @@ -158,7 +145,5 @@ object ResourceUriService { * @throws URISyntaxException See [URI.URI] */ @Throws(URISyntaxException::class) - fun getUri(resource: MetaToken): URI { - return URI(HeaderUtil.buildPath("api", "meta-token", resource.tokenName!!)) - } + fun getUri(resource: MetaToken): URI = URI(HeaderUtil.buildPath("api", "meta-token", resource.tokenName!!)) } diff --git a/src/main/java/org/radarbase/management/service/RevisionService.kt b/src/main/java/org/radarbase/management/service/RevisionService.kt index b04b9a59c..e7d910c50 100644 --- a/src/main/java/org/radarbase/management/service/RevisionService.kt +++ b/src/main/java/org/radarbase/management/service/RevisionService.kt @@ -52,8 +52,9 @@ import javax.validation.constraints.NotNull @Service @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = true) -class RevisionService(@param:Autowired private val revisionEntityRepository: CustomRevisionEntityRepository) : - ApplicationContextAware { +class RevisionService( + @param:Autowired private val revisionEntityRepository: CustomRevisionEntityRepository, +) : ApplicationContextAware { @PersistenceContext private val entityManager: EntityManager? = null private val dtoMapperMap: ConcurrentMap?, Function> = ConcurrentHashMap() @@ -70,25 +71,31 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus val auditReader = auditReader return try { // find first revision of the entity - val firstRevision = auditReader.createQuery() - .forRevisionsOfEntity(entity.javaClass, false, true) - .add(AuditEntity.id().eq(entity.id)) - .add( - AuditEntity.revisionNumber().minimize() - .computeAggregationInInstanceContext() - ) - .singleResult as Array<*> + val firstRevision = + auditReader + .createQuery() + .forRevisionsOfEntity(entity.javaClass, false, true) + .add(AuditEntity.id().eq(entity.id)) + .add( + AuditEntity + .revisionNumber() + .minimize() + .computeAggregationInInstanceContext(), + ).singleResult as Array<*> val first = firstRevision[1] as CustomRevisionEntity // find last revision of the entity - val lastRevision = auditReader.createQuery() - .forRevisionsOfEntity(entity.javaClass, false, true) - .add(AuditEntity.id().eq(entity.id)) - .add( - AuditEntity.revisionNumber().maximize() - .computeAggregationInInstanceContext() - ) - .singleResult as Array<*> + val lastRevision = + auditReader + .createQuery() + .forRevisionsOfEntity(entity.javaClass, false, true) + .add(AuditEntity.id().eq(entity.id)) + .add( + AuditEntity + .revisionNumber() + .maximize() + .computeAggregationInInstanceContext(), + ).singleResult as Array<*> val last = lastRevision[1] as CustomRevisionEntity // now populate the result object and return it @@ -96,23 +103,22 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus .setCreatedAt( ZonedDateTime.ofInstant( first.timestamp!!.toInstant(), - ZoneId.systemDefault() - ) - ) - .setCreatedBy(first.auditor) + ZoneId.systemDefault(), + ), + ).setCreatedBy(first.auditor) .setLastModifiedAt( ZonedDateTime.ofInstant( last.timestamp!!.toInstant(), - ZoneId.systemDefault() - ) - ) - .setLastModifiedBy(last.auditor) + ZoneId.systemDefault(), + ), + ).setLastModifiedBy(last.auditor) } catch (ex: NonUniqueResultException) { // should not happen since we call 'minimize' throw IllegalStateException( - "Query for revision returned a " - + "non-unique result. Please report this to the administrator together with " - + "the request issued.", ex + "Query for revision returned a " + + "non-unique result. Please report this to the administrator together with " + + "the request issued.", + ex, ) } catch (ex: NoResultException) { // we did not find any auditing info, so we just return an empty object @@ -129,18 +135,20 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus * @param clazz the entity class * @param the entity class * @return the entity at the specified revision - */ + */ fun findRevision( revisionNb: Int?, id: Long?, clazz: Class?, - dtoMapper: Function + dtoMapper: Function, ): R? { - val value: T? = auditReader.createQuery() - .forRevisionsOfEntity(clazz, true, true) - .add(AuditEntity.id().eq(id)) - .add(AuditEntity.revisionNumber().eq(revisionNb)) - .singleResult as T + val value: T? = + auditReader + .createQuery() + .forRevisionsOfEntity(clazz, true, true) + .add(AuditEntity.id().eq(id)) + .add(AuditEntity.revisionNumber().eq(revisionNb)) + .singleResult as T return if (value != null) dtoMapper.apply(value) else null } @@ -150,10 +158,10 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus * @param pageable Page information * @return the page of revisions [RevisionInfoDTO] */ - fun getRevisions(pageable: Pageable): Page { - return revisionEntityRepository.findAll(pageable) + fun getRevisions(pageable: Pageable): Page = + revisionEntityRepository + .findAll(pageable) .map { rev -> RevisionInfoDTO.from(rev!!, getChangesForRevision(rev.id)) } - } /** * Get a page of revisions for a given entity. @@ -162,45 +170,62 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus * @param entity the entity for which to get the revisions * @return the requested page of revisions for the given entity */ - fun getRevisionsForEntity(pageable: Pageable, entity: AbstractEntity): Page { + fun getRevisionsForEntity( + pageable: Pageable, + entity: AbstractEntity, + ): Page { val auditReader = auditReader - val count = auditReader.createQuery() - .forRevisionsOfEntity(entity.javaClass, false, true) - .add(AuditEntity.id().eq(entity.id)) - .addProjection(AuditEntity.revisionNumber().count()) - .singleResult as Number + val count = + auditReader + .createQuery() + .forRevisionsOfEntity(entity.javaClass, false, true) + .add(AuditEntity.id().eq(entity.id)) + .addProjection(AuditEntity.revisionNumber().count()) + .singleResult as Number // find all revisions of the entity class that have the correct id - val query = auditReader.createQuery() - .forRevisionsOfEntity(entity.javaClass, false, true) - .add(AuditEntity.id().eq(entity.id)) + val query = + auditReader + .createQuery() + .forRevisionsOfEntity(entity.javaClass, false, true) + .add(AuditEntity.id().eq(entity.id)) // add the page sorting information to the query pageable.sort - .forEach(Consumer { order: Sort.Order -> - query.addOrder( - if (order.direction.isAscending) AuditEntity.property( - order.property - ).asc() else AuditEntity.property(order.property).desc() - ) - }) + .forEach( + Consumer { order: Sort.Order -> + query.addOrder( + if (order.direction.isAscending) { + AuditEntity + .property( + order.property, + ).asc() + } else { + AuditEntity.property(order.property).desc() + }, + ) + }, + ) // add the page constraints (offset and amount of results) - query.setFirstResult(Math.toIntExact(pageable.offset)) + query + .setFirstResult(Math.toIntExact(pageable.offset)) .setMaxResults(Math.toIntExact(pageable.pageSize.toLong())) val dtoMapper = getDtoMapper(entity.javaClass) val resultList = query.resultList as List?> - val revisionDtos = resultList - .map { objArray: Array<*>? -> - RevisionDTO( - Revision.of( - CustomRevisionMetadata((objArray!![1] as CustomRevisionEntity)), - objArray[0] - ), - objArray[2] as RevisionType, - objArray[0]?.let { dtoMapper.apply(it) } - ) - } + val revisionDtos = + resultList + .filter { it != null } + .map { objArray: Array<*>? -> + RevisionDTO( + Revision.of( + CustomRevisionMetadata((objArray!![1] as CustomRevisionEntity)), + objArray[0]!!, + ), + objArray[2]!! as RevisionType, + objArray[0]?.let { dtoMapper.apply(it) }, + ) + } return PageImpl(revisionDtos, pageable, count.toLong()) } @@ -213,13 +238,16 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus */ @Throws(NotFoundException::class) fun getRevision(revision: Int): RevisionInfoDTO { - val revisionEntity = revisionEntityRepository.findById(revision) - .orElse(null) - ?: throw NotFoundException( - "Revision not found with revision id", EntityName.REVISION, - ErrorConstants.ERR_REVISIONS_NOT_FOUND, - Collections.singletonMap("revision-id", revision.toString()) - ) + val revisionEntity = + revisionEntityRepository + .findById(revision) + .orElse(null) + ?: throw NotFoundException( + "Revision not found with revision id", + EntityName.REVISION, + ErrorConstants.ERR_REVISIONS_NOT_FOUND, + Collections.singletonMap("revision-id", revision.toString()), + ) return RevisionInfoDTO.from(revisionEntity, getChangesForRevision(revision)) } @@ -237,13 +265,16 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus // show up in revisions where they were still around. However, clearing for every request // causes the revisions api to be quite slow, so we retrieve the changes manually using // the AuditReader. - val revisionEntity = revisionEntityRepository.findById(revision) - .orElse(null) - ?: throw NotFoundException( - "The requested revision could not be found.", EntityName.REVISION, - ErrorConstants.ERR_REVISIONS_NOT_FOUND, - Collections.singletonMap("revision-id", revision.toString()) - ) + val revisionEntity = + revisionEntityRepository + .findById(revision) + .orElse(null) + ?: throw NotFoundException( + "The requested revision could not be found.", + EntityName.REVISION, + ErrorConstants.ERR_REVISIONS_NOT_FOUND, + Collections.singletonMap("revision-id", revision.toString()), + ) val auditReader = auditReader val result: MutableMap> = HashMap(5) for (revisionType in RevisionType.values()) { @@ -256,12 +287,17 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus for (revisionType in RevisionType.values()) { result[revisionType] ?.addAll( - (listOf(auditReader.createQuery() - .forEntitiesModifiedAtRevision(entityClass, revision) - .add(AuditEntity.revisionType().eq(revisionType)) - .resultList - .let { toDto(it) } as Collection<*>) - )) + ( + listOf( + auditReader + .createQuery() + .forEntitiesModifiedAtRevision(entityClass, revision) + .add(AuditEntity.revisionType().eq(revisionType)) + .resultList + .let { toDto(it) } as Collection<*>, + ) + ), + ) } } return result @@ -274,13 +310,11 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus * @param entity the entity to map to it's DTO form * @return the DTO form of the given entity */ - private fun toDto(entity: Any?): Any? { - return if (entity != null) getDtoMapper(entity.javaClass).apply(entity) else null - } + private fun toDto(entity: Any?): Any? = if (entity != null) getDtoMapper(entity.javaClass).apply(entity) else null - private fun getDtoMapper(@NotNull entity: Class<*>?): Function { - return dtoMapperMap.computeIfAbsent(entity) { clazz: Class<*>? -> addMapperForClass(clazz) } - } + private fun getDtoMapper( + @NotNull entity: Class<*>?, + ): Function = dtoMapperMap.computeIfAbsent(entity) { clazz: Class<*>? -> addMapperForClass(clazz) } @Throws(BeansException::class) override fun setApplicationContext(applicationContext: ApplicationContext) { @@ -306,21 +340,26 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus @Throws(AuditException::class, NonUniqueResultException::class) fun getLatestRevisionForEntity( clazz: Class<*>, - criteria: List + criteria: List, ): Optional { - val query = auditReader.createQuery() - .forRevisionsOfEntity(clazz, true, true) - .add( - AuditEntity.revisionNumber().maximize() - .computeAggregationInInstanceContext() - ) + val query = + auditReader + .createQuery() + .forRevisionsOfEntity(clazz, true, true) + .add( + AuditEntity + .revisionNumber() + .maximize() + .computeAggregationInInstanceContext(), + ) criteria.forEach(Consumer { criterion: AuditCriterion? -> query.add(criterion) }) return try { Optional.ofNullable(toDto(query.singleResult)) } catch (ex: NoResultException) { log.debug( - "No entity of type " + clazz.getName() + " found in the revision history " - + "with the given criteria", ex + "No entity of type " + clazz.getName() + " found in the revision history " + + "with the given criteria", + ex, ) Optional.empty() } @@ -331,34 +370,41 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus val scanner = ClassPathScanningCandidateComponentProvider(true) scanner.addIncludeFilter(AnnotationTypeFilter(Mapper::class.java)) // look only in the mapper package - return scanner.findCandidateComponents("org.radarbase.management.service.mapper").stream() - .flatMap(Function>> { bd: BeanDefinition -> - val mapper = beanFromDefinition(bd) - Arrays.stream(mapper.javaClass.getMethods()) // look for methods that return our entity's DTO, and take exactly one - // argument of the same type as our entity - .filter { m: Method -> - m.genericReturnType.typeName.endsWith( - clazz!!.getSimpleName() + "DTO" - ) && m.genericParameterTypes.size == 1 && m.genericParameterTypes[0].typeName == clazz.getTypeName() - } - .map(Function { method: Method -> - Function { obj: Any? -> - if (obj == null) { - return@Function null - } - try { - return@Function method.invoke(mapper, obj) - } catch (ex: IllegalAccessException) { - log.error(ex.message, ex) - return@Function null - } catch (ex: InvocationTargetException) { - log.error(ex.message, ex) - return@Function null - } - } - }) - }) - .findAny() + return scanner + .findCandidateComponents("org.radarbase.management.service.mapper") + .stream() + .flatMap( + Function>> { bd: BeanDefinition -> + val mapper = beanFromDefinition(bd) + Arrays + .stream(mapper.javaClass.getMethods()) // look for methods that return our entity's DTO, and take exactly one + // argument of the same type as our entity + .filter { m: Method -> + m.genericReturnType.typeName.endsWith( + clazz!!.getSimpleName() + "DTO", + ) && + m.genericParameterTypes.size == 1 && + m.genericParameterTypes[0].typeName == clazz.getTypeName() + }.map( + Function { method: Method -> + Function { obj: Any? -> + if (obj == null) { + return@Function null + } + try { + return@Function method.invoke(mapper, obj) + } catch (ex: IllegalAccessException) { + log.error(ex.message, ex) + return@Function null + } catch (ex: InvocationTargetException) { + log.error(ex.message, ex) + return@Function null + } + } + }, + ) + }, + ).findAny() .orElse(Function { null }) } @@ -370,20 +416,21 @@ class RevisionService(@param:Autowired private val revisionEntityRepository: Cus } catch (ex: ClassNotFoundException) { // should not happen, we got the classname from the bean definition throw InvalidStateException( - ex.message, EntityName.REVISION, "error.classNotFound" + ex.message, + EntityName.REVISION, + "error.classNotFound", ) } } - private fun classForEntityName(entityName: String): Class<*> { - return try { + private fun classForEntityName(entityName: String): Class<*> = + try { Class.forName(entityName) } catch (ex: ClassNotFoundException) { // this should not happen log.error("Unable to load class for modified entity", ex) throw InvalidStateException(ex.message, EntityName.REVISION, "error.classNotFound") } - } private val auditReader: AuditReader get() = AuditReaderFactory.get(entityManager) diff --git a/src/main/java/org/radarbase/management/service/RoleService.kt b/src/main/java/org/radarbase/management/service/RoleService.kt index 71a4a4cf5..457a1343c 100644 --- a/src/main/java/org/radarbase/management/service/RoleService.kt +++ b/src/main/java/org/radarbase/management/service/RoleService.kt @@ -31,9 +31,10 @@ class RoleService( @Autowired private val authorityRepository: AuthorityRepository, @Autowired private val organizationRepository: OrganizationRepository, @Autowired private val projectRepository: ProjectRepository, - @Autowired private val roleMapper: RoleMapper + @Autowired private val roleMapper: RoleMapper, ) { - @Autowired lateinit private var userService: UserService + @Autowired + private lateinit var userService: UserService /** * Save a role. @@ -59,24 +60,37 @@ class RoleService( */ @Transactional(readOnly = true) fun findAll(): List { - val optUser = userService.getUserWithAuthorities() - ?: // return an empty list if we do not have a current user (e.g. with client credentials - // oauth2 grant) - return emptyList() + val optUser = + userService.getUserWithAuthorities() + ?: // return an empty list if we do not have a current user (e.g. with client credentials + // oauth2 grant) + return emptyList() val currentUserAuthorities = optUser.authorities return if (currentUserAuthorities.contains(RoleAuthority.SYS_ADMIN.authority)) { log.debug("Request to get all Roles") - roleRepository.findAll().filterNotNull().map { role: Role -> roleMapper.roleToRoleDTO(role) }.toList() - } else (if (currentUserAuthorities.contains(RoleAuthority.PROJECT_ADMIN.authority)) { - log.debug("Request to get project admin's project Projects") - optUser.roles.asSequence().filter { role: Role? -> - (RoleAuthority.PROJECT_ADMIN.authority == role?.authority?.name) - }.mapNotNull { r: Role -> r.project?.projectName }.distinct() - .flatMap { name: String -> roleRepository.findAllRolesByProjectName(name) } - .map { role -> roleMapper.roleToRoleDTO(role) }.toList() + roleRepository + .findAll() + .filterNotNull() + .map { role: Role -> roleMapper.roleToRoleDTO(role) } + .toList() } else { - emptyList() - }) as List + ( + if (currentUserAuthorities.contains(RoleAuthority.PROJECT_ADMIN.authority)) { + log.debug("Request to get project admin's project Projects") + optUser.roles + .asSequence() + .filter { role: Role? -> + (RoleAuthority.PROJECT_ADMIN.authority == role?.authority?.name) + }.mapNotNull { r: Role -> r.project?.projectName } + .distinct() + .flatMap { name: String -> roleRepository.findAllRolesByProjectName(name) } + .map { role -> roleMapper.roleToRoleDTO(role) } + .toList() + } else { + emptyList() + } + ) + } } /** @@ -87,8 +101,10 @@ class RoleService( @Transactional(readOnly = true) fun findSuperAdminRoles(): List { log.debug("Request to get admin Roles") - return roleRepository.findRolesByAuthorityName(RoleAuthority.SYS_ADMIN.authority) - .map { role: Role -> roleMapper.roleToRoleDTO(role) }.toList() + return roleRepository + .findRolesByAuthorityName(RoleAuthority.SYS_ADMIN.authority) + .map { role: Role -> roleMapper.roleToRoleDTO(role) } + .toList() } /** @@ -119,11 +135,9 @@ class RoleService( * @param role to get or create * @return role from database */ - fun getGlobalRole(role: RoleAuthority): Role { - return roleRepository.findRolesByAuthorityName(role.authority).firstOrNull() + fun getGlobalRole(role: RoleAuthority): Role = + roleRepository.findRolesByAuthorityName(role.authority).firstOrNull() ?: createNewRole(role) { _: Role? -> } - } - /** * Get or create given organization role. @@ -131,24 +145,28 @@ class RoleService( * @param organizationId organization ID * @return role from database */ - fun getOrganizationRole(role: RoleAuthority, organizationId: Long): Role { - return roleRepository.findOneByOrganizationIdAndAuthorityName( - organizationId, role.authority + fun getOrganizationRole( + role: RoleAuthority, + organizationId: Long, + ): Role = + roleRepository.findOneByOrganizationIdAndAuthorityName( + organizationId, + role.authority, ) ?: createNewRole(role) { r: Role -> - r.organization = organizationRepository.findById(organizationId).orElseThrow { - NotFoundException( - "Cannot find organization for authority", - EntityName.USER, - ErrorConstants.ERR_INVALID_AUTHORITY, - mapOf( - Pair("authorityName", role.authority), - Pair("projectId", organizationId.toString()) + r.organization = + organizationRepository.findById(organizationId).orElseThrow { + NotFoundException( + "Cannot find organization for authority", + EntityName.USER, + ErrorConstants.ERR_INVALID_AUTHORITY, + mapOf( + Pair("authorityName", role.authority), + Pair("projectId", organizationId.toString()), + ), ) - ) - } + } } - } /** * Get or create given project role. @@ -156,20 +174,25 @@ class RoleService( * @param projectId organization ID * @return role from database */ - fun getProjectRole(role: RoleAuthority, projectId: Long): Role { - return roleRepository.findOneByProjectIdAndAuthorityName( - projectId, role.authority + fun getProjectRole( + role: RoleAuthority, + projectId: Long, + ): Role = + roleRepository.findOneByProjectIdAndAuthorityName( + projectId, + role.authority, ) ?: createNewRole(role) { r: Role -> r.project = projectRepository.findByIdWithOrganization(projectId) ?: throw NotFoundException( - "Cannot find project for authority", EntityName.USER, ErrorConstants.ERR_INVALID_AUTHORITY, + "Cannot find project for authority", + EntityName.USER, + ErrorConstants.ERR_INVALID_AUTHORITY, mapOf( Pair("authorityName", role.authority), - Pair("projectId", projectId.toString()) - ) + Pair("projectId", projectId.toString()), + ), ) } - } /** * Get all roles related to a project. @@ -178,16 +201,20 @@ class RoleService( */ fun getRolesByProject(projectName: String): List { log.debug("Request to get all Roles for projectName $projectName") - return roleRepository.findAllRolesByProjectName(projectName) - .map { role: Role -> roleMapper.roleToRoleDTO(role) }.toList() + return roleRepository + .findAllRolesByProjectName(projectName) + .map { role: Role -> roleMapper.roleToRoleDTO(role) } + .toList() } - private fun getAuthority(role: RoleAuthority): Authority { - return authorityRepository.findByAuthorityName(role.authority) + private fun getAuthority(role: RoleAuthority): Authority = + authorityRepository.findByAuthorityName(role.authority) ?: authorityRepository.saveAndFlush(Authority(role)) - } - private fun createNewRole(role: RoleAuthority, apply: Consumer): Role { + private fun createNewRole( + role: RoleAuthority, + apply: Consumer, + ): Role { val newRole = Role() newRole.authority = getAuthority(role) apply.accept(newRole) @@ -201,10 +228,12 @@ class RoleService( * @return an [Optional] containing the role if it exists, and empty otherwise */ fun findOneByProjectNameAndAuthorityName( - projectName: String?, authorityName: String? + projectName: String?, + authorityName: String?, ): RoleDTO? { log.debug("Request to get role of project {} and authority {}", projectName, authorityName) - return roleRepository.findOneByProjectNameAndAuthorityName(projectName, authorityName) + return roleRepository + .findOneByProjectNameAndAuthorityName(projectName, authorityName) .let { role -> role?.let { roleMapper.roleToRoleDTO(it) } } } @@ -221,24 +250,26 @@ class RoleService( @JvmStatic fun getRoleAuthority(roleDto: RoleDTO): RoleAuthority { val authority: RoleAuthority - authority = try { - valueOfAuthority(roleDto.authorityName!!) - } catch (ex: IllegalArgumentException) { - throw BadRequestException( - "Authority not found with " + "authorityName", - EntityName.USER, - ErrorConstants.ERR_INVALID_AUTHORITY, - Collections.singletonMap( - "authorityName", roleDto.authorityName + authority = + try { + valueOfAuthority(roleDto.authorityName!!) + } catch (ex: IllegalArgumentException) { + throw BadRequestException( + "Authority not found with " + "authorityName", + EntityName.USER, + ErrorConstants.ERR_INVALID_AUTHORITY, + Collections.singletonMap( + "authorityName", + roleDto.authorityName, + ), ) - ) - } + } if (authority.scope === RoleAuthority.Scope.ORGANIZATION && roleDto.organizationId == null) { throw BadRequestException( "Authority with " + "authorityName should have organization ID", EntityName.USER, ErrorConstants.ERR_INVALID_AUTHORITY, - Collections.singletonMap("authorityName", roleDto.authorityName) + Collections.singletonMap("authorityName", roleDto.authorityName), ) } if (authority.scope === RoleAuthority.Scope.PROJECT && roleDto.projectId == null) { @@ -246,7 +277,7 @@ class RoleService( "Authority with " + "authorityName should have project ID", EntityName.USER, ErrorConstants.ERR_INVALID_AUTHORITY, - Collections.singletonMap("authorityName", roleDto.authorityName) + Collections.singletonMap("authorityName", roleDto.authorityName), ) } return authority diff --git a/src/main/java/org/radarbase/management/service/SiteSettingsService.kt b/src/main/java/org/radarbase/management/service/SiteSettingsService.kt index ae35231e5..60ca72e77 100644 --- a/src/main/java/org/radarbase/management/service/SiteSettingsService.kt +++ b/src/main/java/org/radarbase/management/service/SiteSettingsService.kt @@ -13,8 +13,8 @@ import org.springframework.transaction.annotation.Transactional @Service @Transactional class SiteSettingsService( - @Autowired private val managementPortalProperties: ManagementPortalProperties) { - + @Autowired private val managementPortalProperties: ManagementPortalProperties, +) { /** * Convert a [SiteSettings] to a [SiteSettingsDto] object. * @param siteSettings The object to convert @@ -22,7 +22,7 @@ class SiteSettingsService( */ fun createSiteSettingsDto(siteSettings: SiteSettings): SiteSettingsDto { val siteSettingsDto = SiteSettingsDto() - siteSettingsDto.hiddenSubjectFields = siteSettings.hiddenSubjectFields + siteSettingsDto.hiddenSubjectFields = siteSettings.hiddenSubjectFields ?: emptyList() return siteSettingsDto } diff --git a/src/main/java/org/radarbase/management/service/SourceDataService.kt b/src/main/java/org/radarbase/management/service/SourceDataService.kt index ec175844a..ef104524a 100644 --- a/src/main/java/org/radarbase/management/service/SourceDataService.kt +++ b/src/main/java/org/radarbase/management/service/SourceDataService.kt @@ -20,7 +20,7 @@ import org.springframework.transaction.annotation.Transactional @Transactional class SourceDataService( private val sourceDataRepository: SourceDataRepository, - private val sourceDataMapper: SourceDataMapper + private val sourceDataMapper: SourceDataMapper, ) { /** * Save a sourceData. @@ -32,8 +32,9 @@ class SourceDataService( log.debug("Request to save SourceData : {}", sourceDataDto) if (sourceDataDto?.sourceDataType == null) { throw BadRequestException( - ErrorConstants.ERR_VALIDATION, EntityName.SOURCE_DATA, - "Source Data must contain a type or a topic." + ErrorConstants.ERR_VALIDATION, + EntityName.SOURCE_DATA, + "Source Data must contain a type or a topic.", ) } var sourceData = sourceDataMapper.sourceDataDTOToSourceData(sourceDataDto) @@ -49,7 +50,9 @@ class SourceDataService( @Transactional(readOnly = true) fun findAll(): List { log.debug("Request to get all SourceData") - return sourceDataRepository.findAll().stream() + return sourceDataRepository + .findAll() + .stream() .map { sourceData: SourceData? -> sourceDataMapper.sourceDataToSourceDataDTO(sourceData) } .toList() } @@ -62,7 +65,8 @@ class SourceDataService( @Transactional(readOnly = true) fun findAll(pageable: Pageable): Page { log.debug("Request to get all SourceData") - return sourceDataRepository.findAll(pageable) + return sourceDataRepository + .findAll(pageable) .map { sourceData: SourceData? -> sourceDataMapper.sourceDataToSourceDataDTO(sourceData) } } @@ -88,7 +92,8 @@ class SourceDataService( @Transactional(readOnly = true) fun findOneBySourceDataName(sourceDataName: String?): SourceDataDTO? { log.debug("Request to get SourceData : {}", sourceDataName) - return sourceDataRepository.findOneBySourceDataName(sourceDataName) + return sourceDataRepository + .findOneBySourceDataName(sourceDataName) .let { sourceData: SourceData? -> sourceDataMapper.sourceDataToSourceDataDTO(sourceData) } } @@ -100,7 +105,9 @@ class SourceDataService( @Transactional fun delete(id: Long?) { log.debug("Request to delete SourceData : {}", id) - sourceDataRepository.deleteById(id) + if (id != null) { + sourceDataRepository.deleteById(id) + } } companion object { diff --git a/src/main/java/org/radarbase/management/service/SourceService.kt b/src/main/java/org/radarbase/management/service/SourceService.kt index 84d84c586..fc6cc1593 100644 --- a/src/main/java/org/radarbase/management/service/SourceService.kt +++ b/src/main/java/org/radarbase/management/service/SourceService.kt @@ -31,9 +31,8 @@ class SourceService( @Autowired private val sourceMapper: SourceMapper, @Autowired private val projectRepository: ProjectRepository, @Autowired private val sourceTypeMapper: SourceTypeMapper, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * Save a Source. * @@ -53,13 +52,12 @@ class SourceService( * @return the list of entities */ @Transactional(readOnly = true) - fun findAll(): List { - return sourceRepository + fun findAll(): List = + sourceRepository .findAll() .filterNotNull() .map { source: Source -> sourceMapper.sourceToSourceDTO(source) } .toList() - } /** * Get all the sourceData with pagination. @@ -89,7 +87,8 @@ class SourceService( @Transactional(readOnly = true) fun findOneByName(sourceName: String): SourceDTO? { log.debug("Request to get Source : {}", sourceName) - return sourceRepository.findOneBySourceName(sourceName) + return sourceRepository + .findOneBySourceName(sourceName) .let { source: Source? -> source?.let { sourceMapper.sourceToSourceDTO(it) } } } @@ -102,7 +101,8 @@ class SourceService( @Transactional(readOnly = true) fun findOneById(id: Long): Optional { log.debug("Request to get Source by id: {}", id) - return Optional.ofNullable(sourceRepository.findById(id).orElse(null)) + return Optional + .ofNullable(sourceRepository.findById(id).orElse(null)) .map { source: Source? -> source?.let { sourceMapper.sourceToSourceDTO(it) } } } @@ -115,11 +115,13 @@ class SourceService( fun delete(id: Long) { log.info("Request to delete Source : {}", id) val sourceHistory = sourceRepository.findRevisions(id) - val sources = sourceHistory.content - .mapNotNull { obj -> obj.entity } - .filter{ it.assigned - ?: false } - .toList() + val sources = + sourceHistory.content + .mapNotNull { obj -> obj.entity } + .filter { + it.assigned + ?: false + }.toList() if (sources.isEmpty()) { sourceRepository.deleteById(id) } else { @@ -128,7 +130,9 @@ class SourceService( errorParams["id"] = id.toString() throw InvalidRequestException( "Cannot delete a source that was once assigned.", - EntityName.SOURCE, "error.usedSourceDeletion", errorParams + EntityName.SOURCE, + "error.usedSourceDeletion", + errorParams, ) } } @@ -138,10 +142,13 @@ class SourceService( * * @return list of sources */ - fun findAllByProjectId(projectId: Long, pageable: Pageable): Page { - return sourceRepository.findAllSourcesByProjectId(pageable, projectId) + fun findAllByProjectId( + projectId: Long, + pageable: Pageable, + ): Page = + sourceRepository + .findAllSourcesByProjectId(pageable, projectId) .map { source -> sourceMapper.sourceToSourceWithoutProjectDTO(source) } - } /** * Returns all sources by project in [MinimalSourceDetailsDTO] format. @@ -150,33 +157,34 @@ class SourceService( */ fun findAllMinimalSourceDetailsByProject( projectId: Long, - pageable: Pageable - ): Page { - return sourceRepository.findAllSourcesByProjectId(pageable, projectId) + pageable: Pageable, + ): Page = + sourceRepository + .findAllSourcesByProjectId(pageable, projectId) .map { source: Source -> sourceMapper.sourceToMinimalSourceDetailsDTO(source) } - } /** * Returns list of not-assigned sources by project id. */ - fun findAllByProjectAndAssigned(projectId: Long?, assigned: Boolean): List { - return sourceMapper.sourcesToSourceDTOs( - sourceRepository.findAllSourcesByProjectIdAndAssigned(projectId, assigned) + fun findAllByProjectAndAssigned( + projectId: Long?, + assigned: Boolean, + ): List = + sourceMapper.sourcesToSourceDTOs( + sourceRepository.findAllSourcesByProjectIdAndAssigned(projectId, assigned), ) - } /** * Returns list of not-assigned sources by project id. */ fun findAllMinimalSourceDetailsByProjectAndAssigned( - projectId: Long?, assigned: Boolean - ): List { - return sourceRepository + projectId: Long?, + assigned: Boolean, + ): List = + sourceRepository .findAllSourcesByProjectIdAndAssigned(projectId, assigned) - ?.map { source -> sourceMapper.sourceToMinimalSourceDetailsDTO(source) } - ?.toList() - ?: listOf() - } + .map { source -> sourceMapper.sourceToMinimalSourceDetailsDTO(source) } + .toList() /** * This method does a safe update of source assigned to a subject. It will allow updates of @@ -188,9 +196,8 @@ class SourceService( */ fun safeUpdateOfAttributes( sourceToUpdate: Source, - attributes: Map? + attributes: Map?, ): MinimalSourceDetailsDTO { - // update source attributes val updatedAttributes: MutableMap = HashMap() updatedAttributes.putAll(sourceToUpdate.attributes) @@ -219,8 +226,8 @@ class SourceService( if (existingSource.project != null) { e.project = existingSource.project?.projectName } - if (existingSource.subject != null - && existingSource.subject!!.user != null + if (existingSource.subject != null && + existingSource.subject!!.user != null ) { e.subject = existingSource.subject?.user?.login } @@ -230,22 +237,26 @@ class SourceService( if (existingSource.project?.id != sourceDto.project?.id) { if (existingSource.assigned!!) { throw InvalidRequestException( - "Cannot transfer an assigned source", EntityName.SOURCE, - "error.sourceIsAssigned" + "Cannot transfer an assigned source", + EntityName.SOURCE, + "error.sourceIsAssigned", ) } // check whether source-type of the device is assigned to the new project // to be transferred. - val sourceType = projectRepository - .findSourceTypeByProjectIdAndSourceTypeId( - sourceDto.project?.id, - existingSource.sourceType?.id - ) - ?: throw InvalidRequestException( - "Cannot transfer a source to a project which doesn't have compatible " - + "source-type", IdentifierGenerator.ENTITY_NAME, "error.invalidTransfer" - ) + val sourceType = + projectRepository + .findSourceTypeByProjectIdAndSourceTypeId( + sourceDto.project?.id, + existingSource.sourceType?.id, + ) + ?: throw InvalidRequestException( + "Cannot transfer a source to a project which doesn't have compatible " + + "source-type", + IdentifierGenerator.ENTITY_NAME, + "error.invalidTransfer", + ) // set old source-type, ensures compatibility sourceDto.sourceType = existingSource.sourceType?.let { sourceTypeMapper.sourceTypeToSourceTypeDTO(it) } diff --git a/src/main/java/org/radarbase/management/service/SourceTypeService.kt b/src/main/java/org/radarbase/management/service/SourceTypeService.kt index da67436af..54d2871f3 100644 --- a/src/main/java/org/radarbase/management/service/SourceTypeService.kt +++ b/src/main/java/org/radarbase/management/service/SourceTypeService.kt @@ -34,9 +34,8 @@ class SourceTypeService( @Autowired private val sourceDataRepository: SourceDataRepository, @Autowired private val catalogSourceTypeMapper: CatalogSourceTypeMapper, @Autowired private val catalogSourceDataMapper: CatalogSourceDataMapper, - @Autowired private val projectMapper: ProjectMapper + @Autowired private val projectMapper: ProjectMapper, ) { - /** * Save a sourceType. * @@ -77,7 +76,8 @@ class SourceTypeService( */ fun findAll(pageable: Pageable): Page { log.debug("Request to get SourceTypes") - return sourceTypeRepository.findAll(pageable) + return sourceTypeRepository + .findAll(pageable) .map { sourceType: SourceType -> sourceTypeMapper.sourceTypeToSourceTypeDTO(sourceType) } } @@ -96,29 +96,36 @@ class SourceTypeService( */ fun findByProducerAndModelAndVersion( @NotNull producer: String?, - @NotNull model: String?, @NotNull version: String? + @NotNull model: String?, + @NotNull version: String?, ): SourceTypeDTO { log.debug( "Request to get SourceType by producer and model and version: {}, {}, {}", - producer, model, version + producer, + model, + version, ) if (producer == null || model == null || version == null) { throw NotFoundException( - "SourceType not found with producer, model, " + "version ", EntityName.SOURCE_TYPE, - ErrorConstants.ERR_SOURCE_TYPE_NOT_FOUND, Collections.singletonMap( + "SourceType not found with producer, model, " + "version ", + EntityName.SOURCE_TYPE, + ErrorConstants.ERR_SOURCE_TYPE_NOT_FOUND, + Collections.singletonMap( "producer-model-version", - "$producer-$model-$version" - ) + "$producer-$model-$version", + ), ) } val result = sourceTypeRepository.findOneWithEagerRelationshipsByProducerAndModelAndVersion(producer, model, version) ?: throw NotFoundException( - "SourceType not found with producer, model, " + "version ", EntityName.SOURCE_TYPE, - ErrorConstants.ERR_SOURCE_TYPE_NOT_FOUND, Collections.singletonMap( + "SourceType not found with producer, model, " + "version ", + EntityName.SOURCE_TYPE, + ErrorConstants.ERR_SOURCE_TYPE_NOT_FOUND, + Collections.singletonMap( "producer-model-version", - "$producer-$model-$version" - ) + "$producer-$model-$version", + ), ) return sourceTypeMapper.sourceTypeToSourceTypeDTO(result) @@ -129,22 +136,27 @@ class SourceTypeService( */ fun findByProducer(producer: String): List { log.debug("Request to get SourceType by producer: {}", producer) - val sourceTypes = sourceTypeRepository - .findWithEagerRelationshipsByProducer(producer) + val sourceTypes = + sourceTypeRepository + .findWithEagerRelationshipsByProducer(producer) return sourceTypeMapper.sourceTypesToSourceTypeDTOs( - sourceTypes + sourceTypes, ) } /** * Fetch SourceType by producer and model. */ - fun findByProducerAndModel(producer: String, model: String): List { + fun findByProducerAndModel( + producer: String, + model: String, + ): List { log.debug("Request to get SourceType by producer and model: {}, {}", producer, model) - val sourceTypes = sourceTypeRepository - .findWithEagerRelationshipsByProducerAndModel(producer, model) + val sourceTypes = + sourceTypeRepository + .findWithEagerRelationshipsByProducerAndModel(producer, model) return sourceTypeMapper.sourceTypesToSourceTypeDTOs( - sourceTypes + sourceTypes, ) } @@ -156,12 +168,15 @@ class SourceTypeService( * @param version the SourceType catalogVersion * @return the list of projects associated with this SourceType */ - fun findProjectsBySourceType(producer: String, model: String, version: String): List { - return projectMapper.projectsToProjectDTOs( + fun findProjectsBySourceType( + producer: String, + model: String, + version: String, + ): List = + projectMapper.projectsToProjectDTOs( sourceTypeRepository - .findProjectsBySourceType(producer, model, version) + .findProjectsBySourceType(producer, model, version), ) - } /** * Converts given [CatalogSourceType] to [SourceType] and saves it to the databse @@ -171,23 +186,26 @@ class SourceTypeService( @Transactional fun saveSourceTypesFromCatalogServer(catalogSourceTypes: List) { for (catalogSourceType in catalogSourceTypes) { - var sourceType = catalogSourceTypeMapper - .catalogSourceTypeToSourceType(catalogSourceType) + var sourceType = + catalogSourceTypeMapper + .catalogSourceTypeToSourceType(catalogSourceType) if (!isSourceTypeValid(sourceType)) { continue } // check whether a source-type is already available with given config if (sourceTypeRepository.hasOneByProducerAndModelAndVersion( - sourceType!!.producer!!, sourceType.model!!, - sourceType.catalogVersion!! + sourceType!!.producer!!, + sourceType.model!!, + sourceType.catalogVersion!!, ) ) { // skip for existing source-types log.info( - "Source-type {} is already available ", sourceType.producer - + "_" + sourceType.model - + "_" + sourceType.catalogVersion + "Source-type {} is already available ", + sourceType.producer + + "_" + sourceType.model + + "_" + sourceType.catalogVersion, ) } else { try { @@ -206,17 +224,21 @@ class SourceTypeService( log.info("Completed source-type import from catalog-server") } - private fun saveSourceData(sourceType: SourceType?, catalogSourceData: CatalogSourceData?) { + private fun saveSourceData( + sourceType: SourceType?, + catalogSourceData: CatalogSourceData?, + ) { try { - val sourceData = catalogSourceDataMapper - .catalogSourceDataToSourceData(catalogSourceData) + val sourceData = + catalogSourceDataMapper + .catalogSourceDataToSourceData(catalogSourceData) // sourceDataName should be unique // generated by combining sourceDataType and source-type configs sourceData!!.sourceDataName( - sourceType!!.producer - + "_" + sourceType.model - + "_" + sourceType.catalogVersion - + "_" + sourceData.sourceDataType + sourceType!!.producer + + "_" + sourceType.model + + "_" + sourceType.catalogVersion + + "_" + sourceData.sourceDataType, ) sourceData.sourceType(sourceType) sourceDataRepository.save(sourceData) @@ -227,25 +249,29 @@ class SourceTypeService( companion object { private val log = LoggerFactory.getLogger(SourceTypeService::class.java) + private fun isSourceTypeValid(sourceType: SourceType?): Boolean { if (sourceType!!.producer == null) { log.warn( - "Catalog source-type {} does not have a vendor. " - + "Skipping importing this type", sourceType.name + "Catalog source-type {} does not have a vendor. " + + "Skipping importing this type", + sourceType.name, ) return false } if (sourceType.model == null) { log.warn( - "Catalog source-type {} does not have a model. " - + "Skipping importing this type", sourceType.name + "Catalog source-type {} does not have a model. " + + "Skipping importing this type", + sourceType.name, ) return false } if (sourceType.catalogVersion == null) { log.warn( - "Catalog source-type {} does not have a version. " - + "Skipping importing this type", sourceType.name + "Catalog source-type {} does not have a version. " + + "Skipping importing this type", + sourceType.name, ) return false } diff --git a/src/main/java/org/radarbase/management/service/SubjectService.kt b/src/main/java/org/radarbase/management/service/SubjectService.kt index ad281e4c4..8be24eefc 100644 --- a/src/main/java/org/radarbase/management/service/SubjectService.kt +++ b/src/main/java/org/radarbase/management/service/SubjectService.kt @@ -66,9 +66,8 @@ class SubjectService( @Autowired private val managementPortalProperties: ManagementPortalProperties, @Autowired private val passwordService: PasswordService, @Autowired private val authorityRepository: AuthorityRepository, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * Create a new subject. * @@ -78,12 +77,12 @@ class SubjectService( @Transactional fun createSubject(subjectDto: SubjectDTO): SubjectDTO? { val subject = subjectMapper.subjectDTOToSubject(subjectDto) ?: throw NullPointerException() - //assign roles + // assign roles val user = subject.user val project = projectMapper.projectDTOToProject(subjectDto.project) val projectParticipantRole = getProjectParticipantRole(project, RoleAuthority.PARTICIPANT) val roles = user!!.roles - roles?.add(projectParticipantRole) + roles.add(projectParticipantRole) // Set group subject.group = getSubjectGroup(project, subjectDto.group) @@ -96,12 +95,14 @@ class SubjectService( user.resetDate = ZonedDateTime.now() // default subject is activated. user.activated = true - //set if any devices are set as assigned + // set if any devices are set as assigned if (subject.sources.isNotEmpty()) { - subject.sources.forEach(Consumer { s: Source -> - s.assigned = true - s.subject(subject) - }) + subject.sources.forEach( + Consumer { s: Source -> + s.assigned = true + s.subject(subject) + }, + ) } if (subject.enrollmentDate == null) { subject.enrollmentDate = ZonedDateTime.now() @@ -110,15 +111,19 @@ class SubjectService( return subjectMapper.subjectToSubjectReducedProjectDTO(subjectRepository.save(subject)) } - private fun getSubjectGroup(project: Project?, groupName: String?): Group? { - return if (project == null || groupName == null) { + private fun getSubjectGroup( + project: Project?, + groupName: String?, + ): Group? = + if (project == null || groupName == null) { null - } else groupRepository.findByProjectIdAndName(project.id, groupName) ?: throw BadRequestException( - "Group " + groupName + " does not exist in project " + project.projectName, - EntityName.GROUP, - ErrorConstants.ERR_GROUP_NOT_FOUND - ) - } + } else { + groupRepository.findByProjectIdAndName(project.id, groupName) ?: throw BadRequestException( + "Group " + groupName + " does not exist in project " + project.projectName, + EntityName.GROUP, + ErrorConstants.ERR_GROUP_NOT_FOUND, + ) + } /** * Fetch Participant role of the project if available, otherwise create a new Role and assign. @@ -127,21 +132,29 @@ class SubjectService( * @return relevant Participant role * @throws java.util.NoSuchElementException if the authority name is not in the database */ - private fun getProjectParticipantRole(project: Project?, authority: RoleAuthority): Role { - val ans: Role? = roleRepository.findOneByProjectIdAndAuthorityName( - project?.id, authority.authority - ) + private fun getProjectParticipantRole( + project: Project?, + authority: RoleAuthority, + ): Role { + val ans: Role? = + roleRepository.findOneByProjectIdAndAuthorityName( + project?.id, + authority.authority, + ) return if (ans == null) { val subjectRole = Role() - val auth: Authority = authorityRepository.findByAuthorityName( - authority.authority - ) ?: authorityRepository.save(Authority(authority)) + val auth: Authority = + authorityRepository.findByAuthorityName( + authority.authority, + ) ?: authorityRepository.save(Authority(authority)) subjectRole.authority = auth subjectRole.project = project roleRepository.save(subjectRole) subjectRole - } else ans + } else { + ans + } } /** @@ -157,36 +170,49 @@ class SubjectService( } val subjectFromDb = ensureSubject(newSubjectDto) val sourcesToUpdate = subjectFromDb.sources - //set only the devices assigned to a subject as assigned + // set only the devices assigned to a subject as assigned subjectMapper.safeUpdateSubjectFromDTO(newSubjectDto, subjectFromDb) sourcesToUpdate.addAll(subjectFromDb.sources) - subjectFromDb.sources.forEach(Consumer { s: Source -> - s.subject(subjectFromDb).assigned = true }) + subjectFromDb.sources.forEach( + Consumer { s: Source -> + s.subject(subjectFromDb).assigned = true + }, + ) sourceRepository.saveAll(sourcesToUpdate) // update participant role subjectFromDb.user!!.roles = updateParticipantRoles(subjectFromDb, newSubjectDto) // Set group - subjectFromDb.group = getSubjectGroup( - subjectFromDb.activeProject, newSubjectDto.group - ) + subjectFromDb.group = + getSubjectGroup( + subjectFromDb.activeProject, + newSubjectDto.group, + ) return subjectMapper.subjectToSubjectReducedProjectDTO( - subjectRepository.save(subjectFromDb) + subjectRepository.save(subjectFromDb), ) } - private fun updateParticipantRoles(subject: Subject, subjectDto: SubjectDTO): MutableSet { + private fun updateParticipantRoles( + subject: Subject, + subjectDto: SubjectDTO, + ): MutableSet { if (subjectDto.project == null || subjectDto.project!!.projectName == null) { return subject.user!!.roles } - val existingRoles = subject.user!!.roles.map { - // make participant inactive in projects that do not match the new project - if (it.authority!!.name == RoleAuthority.PARTICIPANT.authority && it.project!!.projectName != subjectDto.project!!.projectName) { - return@map getProjectParticipantRole(it.project, RoleAuthority.INACTIVE_PARTICIPANT) - } else { - // do not modify other roles. - return@map it - } - }.toMutableSet() + val existingRoles = + subject.user!! + .roles + .map { + // make participant inactive in projects that do not match the new project + if (it.authority!!.name == RoleAuthority.PARTICIPANT.authority && + it.project!!.projectName != subjectDto.project!!.projectName + ) { + return@map getProjectParticipantRole(it.project, RoleAuthority.INACTIVE_PARTICIPANT) + } else { + // do not modify other roles. + return@map it + } + }.toMutableSet() // Ensure that given project is present val newProjectRole = @@ -194,7 +220,6 @@ class SubjectService( existingRoles.add(newProjectRole) return existingRoles - } /** @@ -219,19 +244,17 @@ class SubjectService( return subjectMapper.subjectToSubjectReducedProjectDTO(subjectRepository.save(subject)) } - private fun ensureSubject(subjectDto: SubjectDTO): Subject { - return try { + private fun ensureSubject(subjectDto: SubjectDTO): Subject = + try { subjectDto.id?.let { subjectRepository.findById(it).get() } ?: throw Exception("invalid subject ${subjectDto.login}: No ID") - } - catch(e: Throwable) { + } catch (e: Throwable) { throw NotFoundException( "Subject with ID " + subjectDto.id + " not found.", EntityName.SUBJECT, - ErrorConstants.ERR_SUBJECT_NOT_FOUND + ErrorConstants.ERR_SUBJECT_NOT_FOUND, ) } - } /** * Unassign all sources from a subject. This method saves the unassigned sources, but does NOT @@ -240,12 +263,14 @@ class SubjectService( * @param subject The subject for which to unassign all sources */ private fun unassignAllSources(subject: Subject) { - subject.sources.forEach(Consumer { source: Source -> - source.assigned = false - source.subject = null - source.deleted = true - sourceRepository.save(source) - }) + subject.sources.forEach( + Consumer { source: Source -> + source.assigned = false + source.subject = null + source.deleted = true + sourceRepository.save(source) + }, + ) subject.sources.clear() } @@ -257,16 +282,23 @@ class SubjectService( */ @Transactional fun assignOrUpdateSource( - subject: Subject, sourceType: SourceType, project: Project?, sourceRegistrationDto: MinimalSourceDetailsDTO + subject: Subject, + sourceType: SourceType, + project: Project?, + sourceRegistrationDto: MinimalSourceDetailsDTO, ): MinimalSourceDetailsDTO { val assignedSource: Source if (sourceRegistrationDto.sourceId != null) { // update meta-data and source-name for existing sources assignedSource = updateSourceAssignedSubject(subject, sourceRegistrationDto) } else if (sourceType.canRegisterDynamically!!) { - val sources = subjectRepository.findSubjectSourcesBySourceType( - subject.user!!.login, sourceType.producer, sourceType.model, sourceType.catalogVersion - ) + val sources = + subjectRepository.findSubjectSourcesBySourceType( + subject.user!!.login, + sourceType.producer, + sourceType.model, + sourceType.catalogVersion, + ) // create a source and register metadata // we allow only one source of a source-type per subject if (sources.isNullOrEmpty()) { @@ -284,7 +316,7 @@ class SubjectService( "SourceName already in use. Cannot create a " + "source with existing source-name ", EntityName.SUBJECT, ErrorConstants.ERR_SOURCE_NAME_EXISTS, - Collections.singletonMap("source-name", source.sourceName) + Collections.singletonMap("source-name", source.sourceName), ) } source = sourceRepository.save(source) @@ -295,7 +327,7 @@ class SubjectService( "A Source of SourceType with the specified producer, model and version" + " was already registered for subject login", EntityName.SUBJECT, ErrorConstants.ERR_SOURCE_TYPE_EXISTS, - sourceTypeAttributes(sourceType, subject) + sourceTypeAttributes(sourceType, subject), ) } } else { @@ -304,7 +336,7 @@ class SubjectService( "The source type is not eligible for dynamic " + "registration", EntityName.SOURCE_TYPE, "error.InvalidDynamicSourceRegistration", - sourceTypeAttributes(sourceType, subject) + sourceTypeAttributes(sourceType, subject), ) } subjectRepository.save(subject) @@ -319,12 +351,15 @@ class SubjectService( * @return Updated [Source] instance. */ private fun updateSourceAssignedSubject( - subject: Subject, sourceRegistrationDto: MinimalSourceDetailsDTO + subject: Subject, + sourceRegistrationDto: MinimalSourceDetailsDTO, ): Source { // for manually registered devices only add meta-data - val source = subjectRepository.findSubjectSourcesBySourceId( - subject.user?.login, sourceRegistrationDto.sourceId - ) + val source = + subjectRepository.findSubjectSourcesBySourceId( + subject.user?.login, + sourceRegistrationDto.sourceId, + ) if (source == null) { val errorParams: MutableMap = HashMap() errorParams["sourceId"] = sourceRegistrationDto.sourceId.toString() @@ -333,7 +368,7 @@ class SubjectService( "No source with source-id to assigned to the subject with subject-login", EntityName.SUBJECT, ErrorConstants.ERR_SOURCE_NOT_FOUND, - errorParams + errorParams, ) } @@ -371,7 +406,7 @@ class SubjectService( "subject not found for given login.", EntityName.SUBJECT, ErrorConstants.ERR_SUBJECT_NOT_FOUND, - Collections.singletonMap("subjectLogin", login) + Collections.singletonMap("subjectLogin", login), ) } @@ -384,8 +419,11 @@ class SubjectService( fun findSubjectSourcesFromRevisions(subject: Subject): List? { val revisions = subject.id?.let { subjectRepository.findRevisions(it) } // collect distinct sources in a set - val sources: List? = revisions?.content?.flatMap { p: Revision -> p.entity.sources } - ?.distinctBy { obj: Source -> obj.sourceId } + val sources: List? = + revisions + ?.content + ?.flatMap { p: Revision -> p.entity.sources } + ?.distinctBy { obj: Source -> obj.sourceId } return sources?.map { p: Source -> sourceMapper.sourceToMinimalSourceDetailsDTO(p) }?.toList() } @@ -400,7 +438,10 @@ class SubjectService( * revision number */ @Throws(NotFoundException::class, NotAuthorizedException::class) - fun findRevision(login: String?, revision: Int?): SubjectDTO { + fun findRevision( + login: String?, + revision: Int?, + ): SubjectDTO { // first get latest known version of the subject, if it's deleted we can't load the entity // directly by e.g. findOneByLogin val latest = getLatestRevision(login) @@ -408,12 +449,15 @@ class SubjectService( e.project(latest.project?.projectName).subject(latest.login) }) return revisionService.findRevision( - revision, latest.id, Subject::class.java, subjectMapper::subjectToSubjectReducedProjectDTO + revision, + latest.id, + Subject::class.java, + subjectMapper::subjectToSubjectReducedProjectDTO, ) ?: throw NotFoundException( "subject not found for given login and revision.", EntityName.SUBJECT, ErrorConstants.ERR_SUBJECT_NOT_FOUND, - Collections.singletonMap("subjectLogin", login) + Collections.singletonMap("subjectLogin", login), ) } @@ -426,26 +470,31 @@ class SubjectService( */ @Throws(NotFoundException::class) fun getLatestRevision(login: String?): SubjectDTO { - val user = revisionService.getLatestRevisionForEntity( - User::class.java, listOf(AuditEntity.property("login").eq(login)) - ).orElseThrow { - NotFoundException( - "Subject latest revision not found " + "for login", - EntityName.SUBJECT, - ErrorConstants.ERR_SUBJECT_NOT_FOUND, - Collections.singletonMap("subjectLogin", login) - ) - } as UserDTO - return revisionService.getLatestRevisionForEntity( - Subject::class.java, listOf(AuditEntity.property("user").eq(user)) - ).orElseThrow { - NotFoundException( - "Subject latest revision not found " + "for login", - EntityName.SUBJECT, - ErrorConstants.ERR_SUBJECT_NOT_FOUND, - Collections.singletonMap("subjectLogin", login) - ) - } as SubjectDTO + val user = + revisionService + .getLatestRevisionForEntity( + User::class.java, + listOf(AuditEntity.property("login").eq(login)), + ).orElseThrow { + NotFoundException( + "Subject latest revision not found " + "for login", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, + Collections.singletonMap("subjectLogin", login), + ) + } as UserDTO + return revisionService + .getLatestRevisionForEntity( + Subject::class.java, + listOf(AuditEntity.property("user").eq(user)), + ).orElseThrow { + NotFoundException( + "Subject latest revision not found " + "for login", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, + Collections.singletonMap("subjectLogin", login), + ) + } as SubjectDTO } /** @@ -457,7 +506,9 @@ class SubjectService( fun findOneByLogin(login: String?): Subject { val subject = subjectRepository.findOneWithEagerBySubjectLogin(login) return subject ?: throw NotFoundException( - "Subject not found with login", EntityName.SUBJECT, ErrorConstants.ERR_SUBJECT_NOT_FOUND + "Subject not found with login", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, ) } @@ -472,7 +523,8 @@ class SubjectService( // since the lastLoadedId param defines the offset // within the query specification return subjectRepository.findAll( - SubjectSpecification(criteria), criteria.pageable + SubjectSpecification(criteria), + criteria.pageable, ) } @@ -488,10 +540,10 @@ class SubjectService( * @return URL of privacy policy for this token */ fun getPrivacyPolicyUrl(subject: Subject): URL { - // load default url from config - val policyUrl: String = subject.activeProject?.attributes?.get(ProjectDTO.PRIVACY_POLICY_URL) - ?: managementPortalProperties.common.privacyPolicyUrl + val policyUrl: String = + subject.activeProject?.attributes?.get(ProjectDTO.PRIVACY_POLICY_URL) + ?: managementPortalProperties.common.privacyPolicyUrl return try { URL(policyUrl) } catch (e: MalformedURLException) { @@ -502,15 +554,17 @@ class SubjectService( "No valid privacy-policy Url configured. Please " + "verify your project's privacy-policy url and/or general url config", EntityName.OAUTH_CLIENT, ErrorConstants.ERR_NO_VALID_PRIVACY_POLICY_URL_CONFIGURED, - params + params, ) } } companion object { private val log = LoggerFactory.getLogger(SubjectService::class.java) + private fun sourceTypeAttributes( - sourceType: SourceType, subject: Subject + sourceType: SourceType, + subject: Subject, ): Map { val errorParams: MutableMap = HashMap() errorParams["producer"] = sourceType.producer diff --git a/src/main/java/org/radarbase/management/service/UserService.kt b/src/main/java/org/radarbase/management/service/UserService.kt index ade4f6123..464e42819 100644 --- a/src/main/java/org/radarbase/management/service/UserService.kt +++ b/src/main/java/org/radarbase/management/service/UserService.kt @@ -1,13 +1,5 @@ package org.radarbase.management.service -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.radarbase.auth.authorization.EntityDetails @@ -49,465 +41,505 @@ import java.util.function.Function */ @Service @Transactional -class UserService @Autowired constructor( - private val userRepository: UserRepository, - private val passwordService: PasswordService, - private val userMapper: UserMapper, - private val revisionService: RevisionService, - private val managementPortalProperties: ManagementPortalProperties, - private val authService: AuthService, - private val identityService: IdentityService -) { +class UserService @Autowired - lateinit var roleService: RoleService - - /** - * Activate a user with the given activation key. - * @param key the activation key - * @return an [Optional] which is populated with the activated user if the registration - * key was found, and is empty otherwise. - */ - fun activateRegistration(key: String): User { - log.debug("Activating user for activation key {}", key) - return userRepository.findOneByActivationKey(key).let { user: User? -> - // activate given user for the registration key. - user?.activated = true - user?.activationKey = null - log.debug("Activated user: {}", user) - user - } ?: throw NotFoundException( - "User with activation key $key not found", - EntityName.USER, - ErrorConstants.ERR_ENTITY_NOT_FOUND, - mapOf(Pair("activationKey", key)) - ) - } + constructor( + private val userRepository: UserRepository, + private val passwordService: PasswordService, + private val userMapper: UserMapper, + private val revisionService: RevisionService, + private val managementPortalProperties: ManagementPortalProperties, + private val authService: AuthService, + private val identityService: IdentityService, + ) { + @Autowired + lateinit var roleService: RoleService + + /** + * Activate a user with the given activation key. + * @param key the activation key + * @return an [Optional] which is populated with the activated user if the registration + * key was found, and is empty otherwise. + */ + fun activateRegistration(key: String): User { + log.debug("Activating user for activation key {}", key) + return userRepository.findOneByActivationKey(key).let { user: User? -> + // activate given user for the registration key. + user?.activated = true + user?.activationKey = null + log.debug("Activated user: {}", user) + user + } ?: throw NotFoundException( + "User with activation key $key not found", + EntityName.USER, + ErrorConstants.ERR_ENTITY_NOT_FOUND, + mapOf(Pair("activationKey", key)), + ) + } - /** - * Update a user password with a given reset key. - * @param newPassword the updated password - * @param key the reset key - * @return an [Optional] which is populated with the user whose password was reset if - * the reset key was found, and is empty otherwise - */ - fun completePasswordReset(newPassword: String, key: String): User? { - log.debug("Reset user password for reset key {}", key) - val user = userRepository.findOneByResetKey(key) - val oneDayAgo = ZonedDateTime.now().minusSeconds( - managementPortalProperties.common.activationKeyTimeoutInSeconds.toLong() - ) - return if (user?.resetDate?.isAfter(oneDayAgo) == true) { - user.password = passwordService.encode(newPassword) - user.resetKey = null - user.resetDate = null - user.activated = true - user - } else - null - } + /** + * Update a user password with a given reset key. + * @param newPassword the updated password + * @param key the reset key + * @return an [Optional] which is populated with the user whose password was reset if + * the reset key was found, and is empty otherwise + */ + fun completePasswordReset( + newPassword: String, + key: String, + ): User? { + log.debug("Reset user password for reset key {}", key) + val user = userRepository.findOneByResetKey(key) + val oneDayAgo = + ZonedDateTime.now().minusSeconds( + managementPortalProperties.common.activationKeyTimeoutInSeconds.toLong(), + ) + return if (user?.resetDate?.isAfter(oneDayAgo) == true) { + user.password = passwordService.encode(newPassword) + user.resetKey = null + user.resetDate = null + user.activated = true + user + } else { + null + } + } - /** - * Find the deactivated user and set the user's reset key to a new random value and set their - * reset date to now. - * Note: We do not use activation key for activating an account. It happens by resetting - * generated password. Resetting activation is by resetting reset-key and reset-date to now. - * @param login the login of the user - * @return an [Optional] which holds the user if an deactivated user was found with the - * given login, and is empty otherwise - */ - fun requestActivationReset(login: String): User? { - val user = userRepository.findOneByLogin(login) - if (user?.activated != true) { - user?.resetKey = passwordService.generateResetKey() - user?.resetDate = ZonedDateTime.now() + /** + * Find the deactivated user and set the user's reset key to a new random value and set their + * reset date to now. + * Note: We do not use activation key for activating an account. It happens by resetting + * generated password. Resetting activation is by resetting reset-key and reset-date to now. + * @param login the login of the user + * @return an [Optional] which holds the user if an deactivated user was found with the + * given login, and is empty otherwise + */ + fun requestActivationReset(login: String): User? { + val user = userRepository.findOneByLogin(login) + if (user?.activated != true) { + user?.resetKey = passwordService.generateResetKey() + user?.resetDate = ZonedDateTime.now() + } + + return user } - return user - } + /** + * Set a user's reset key to a new random value and set their reset date to now. + * @param mail the email address of the user + * @return an [Optional] which holds the user if an activated user was found with the + * given email address, and is empty otherwise + */ + fun requestPasswordReset(mail: String): User? { + val user = userRepository.findOneByEmail(mail) + return if (user?.activated == true) { + user.resetKey = passwordService.generateResetKey() + user.resetDate = ZonedDateTime.now() + user + } else { + null + } + } - /** - * Set a user's reset key to a new random value and set their reset date to now. - * @param mail the email address of the user - * @return an [Optional] which holds the user if an activated user was found with the - * given email address, and is empty otherwise - */ - fun requestPasswordReset(mail: String): User? { - val user = userRepository.findOneByEmail(mail) - return if (user?.activated == true) { + /** + * Add a new user to the database. + * + * + * The new user will not be activated and have a random password assigned. It is the + * responsibility of the caller to make sure the new user has a means of activating their + * account. + * @param userDto the user information + * @return the newly created user + */ + @Throws(NotAuthorizedException::class) + suspend fun createUser(userDto: UserDTO): User { + var user = User() + user.setLogin(userDto.login) + user.firstName = userDto.firstName + user.lastName = userDto.lastName + user.email = userDto.email + if (userDto.langKey == null) { + user.langKey = "en" // default language + } else { + user.langKey = userDto.langKey + } + user.password = passwordService.generateEncodedPassword() user.resetKey = passwordService.generateResetKey() user.resetDate = ZonedDateTime.now() - user - } else - null - } - - /** - * Add a new user to the database. - * - * - * The new user will not be activated and have a random password assigned. It is the - * responsibility of the caller to make sure the new user has a means of activating their - * account. - * @param userDto the user information - * @return the newly created user - */ - @Throws(NotAuthorizedException::class) - suspend fun createUser(userDto: UserDTO): User { - var user = User() - user.setLogin(userDto.login) - user.firstName = userDto.firstName - user.lastName = userDto.lastName - user.email = userDto.email - if (userDto.langKey == null) { - user.langKey = "en" // default language - } else { - user.langKey = userDto.langKey - } - user.password = passwordService.generateEncodedPassword() - user.resetKey = passwordService.generateResetKey() - user.resetDate = ZonedDateTime.now() - user.activated = true - user.roles = getUserRoles(userDto.roles, mutableSetOf()) - - try{ - user.identity = identityService.saveAsIdentity(user)?.id - } - catch (e: Throwable) { - log.warn("could not save user ${user.login} as identity", e) - } + user.activated = true + user.roles = getUserRoles(userDto.roles, mutableSetOf()) - user = withContext(Dispatchers.IO) { - userRepository.save(user) - } + try { + user.identity = identityService.saveAsIdentity(user)?.id + } catch (e: Throwable) { + log.warn("could not save user ${user.login} as identity", e) + } - log.debug("Created Information for User: {}", user) - return user - } + user = + withContext(Dispatchers.IO) { + userRepository.save(user) + } - @Throws(NotAuthorizedException::class) - private fun getUserRoles(roleDtos: Set?, oldRoles: MutableSet): MutableSet { - if (roleDtos == null) { - return mutableSetOf() + log.debug("Created Information for User: {}", user) + return user } - val roles = roleDtos.map { roleDto: RoleDTO -> - val authority = getRoleAuthority(roleDto) - when (authority.scope) { - RoleAuthority.Scope.GLOBAL -> roleService.getGlobalRole(authority) - RoleAuthority.Scope.ORGANIZATION -> roleService.getOrganizationRole( - authority, roleDto.organizationId!! - ) - RoleAuthority.Scope.PROJECT -> roleService.getProjectRole( - authority, roleDto.projectId!! - ) + @Throws(NotAuthorizedException::class) + private fun getUserRoles( + roleDtos: Set?, + oldRoles: MutableSet, + ): MutableSet { + if (roleDtos == null) { + return mutableSetOf() } - }.toMutableSet() - checkAuthorityForRoleChange(roles, oldRoles) - return roles - } - - @Throws(NotAuthorizedException::class) - private fun checkAuthorityForRoleChange(roles: Set, oldRoles: Set) { - val updatedRoles = HashSet(roles) - updatedRoles.removeAll(oldRoles) - for (r in updatedRoles) { - checkAuthorityForRoleChange(r) + val roles = + roleDtos + .map { roleDto: RoleDTO -> + val authority = getRoleAuthority(roleDto) + when (authority.scope) { + RoleAuthority.Scope.GLOBAL -> roleService.getGlobalRole(authority) + RoleAuthority.Scope.ORGANIZATION -> + roleService.getOrganizationRole( + authority, + roleDto.organizationId!!, + ) + + RoleAuthority.Scope.PROJECT -> + roleService.getProjectRole( + authority, + roleDto.projectId!!, + ) + } + }.toMutableSet() + checkAuthorityForRoleChange(roles, oldRoles) + return roles } - val removedRoles = HashSet(oldRoles) - removedRoles.removeAll(roles) - for (r in removedRoles) { - checkAuthorityForRoleChange(r) + + @Throws(NotAuthorizedException::class) + private fun checkAuthorityForRoleChange( + roles: Set, + oldRoles: Set, + ) { + val updatedRoles = HashSet(roles) + updatedRoles.removeAll(oldRoles) + for (r in updatedRoles) { + checkAuthorityForRoleChange(r) + } + val removedRoles = HashSet(oldRoles) + removedRoles.removeAll(roles) + for (r in removedRoles) { + checkAuthorityForRoleChange(r) + } } - } - @Throws(NotAuthorizedException::class) - private fun checkAuthorityForRoleChange(role: Role) { - authService.checkPermission(Permission.ROLE_UPDATE, { e: EntityDetails -> - when (role.role?.scope) { - RoleAuthority.Scope.GLOBAL -> {} - RoleAuthority.Scope.ORGANIZATION -> e.organization(role.organization?.name) - RoleAuthority.Scope.PROJECT -> { - if (role.project?.organization != null) { - e.organization(role.project?.organizationName) + @Throws(NotAuthorizedException::class) + private fun checkAuthorityForRoleChange(role: Role) { + authService.checkPermission(Permission.ROLE_UPDATE, { e: EntityDetails -> + when (role.role?.scope) { + RoleAuthority.Scope.GLOBAL -> {} + RoleAuthority.Scope.ORGANIZATION -> e.organization(role.organization?.name) + RoleAuthority.Scope.PROJECT -> { + if (role.project?.organization != null) { + e.organization(role.project?.organizationName) + } + e.project(role.project?.projectName) } - e.project(role.project?.projectName) - } - else -> throw IllegalStateException("Unknown authority scope.") - } - }) - } + else -> throw IllegalStateException("Unknown authority scope.") + } + }) + } - /** - * Update basic information (first name, last name, email, language) for the current user. - * - * @param firstName first name of user - * @param lastName last name of user - * @param email email id of user - * @param langKey language key - */ - suspend fun updateUser( - userName: String, firstName: String?, lastName: String?, email: String?, langKey: String? - ) { - val userWithEmail = email?.let { userRepository.findOneByEmail(it) } - val user: User - if (userWithEmail != null) { - user = userWithEmail - if (!user.login.equals(userName, ignoreCase = true)) { - throw ConflictException( - "Email address $email already in use", + /** + * Update basic information (first name, last name, email, language) for the current user. + * + * @param firstName first name of user + * @param lastName last name of user + * @param email email id of user + * @param langKey language key + */ + suspend fun updateUser( + userName: String, + firstName: String?, + lastName: String?, + email: String?, + langKey: String?, + ) { + val userWithEmail = email?.let { userRepository.findOneByEmail(it) } + val user: User + if (userWithEmail != null) { + user = userWithEmail + if (!user.login.equals(userName, ignoreCase = true)) { + throw ConflictException( + "Email address $email already in use", + EntityName.USER, + ErrorConstants.ERR_EMAIL_EXISTS, + mapOf(Pair("email", email)), + ) + } + } else { + user = userRepository.findOneByLogin(userName) ?: throw NotFoundException( + "User with login $userName not found", EntityName.USER, - ErrorConstants.ERR_EMAIL_EXISTS, - mapOf(Pair("email", email)) + ErrorConstants.ERR_ENTITY_NOT_FOUND, + mapOf(Pair("user", userName)), ) } - } else { - user = userRepository.findOneByLogin(userName) ?: throw NotFoundException( - "User with login $userName not found", - EntityName.USER, - ErrorConstants.ERR_ENTITY_NOT_FOUND, - mapOf(Pair("user", userName)) - ) - } - user.firstName = firstName - user.lastName = lastName - user.email = email - user.langKey = langKey - log.debug("Changed Information for User: {}", user) - userRepository.save(user) - - try { - identityService.updateAssociatedIdentity(user) - } - catch (e: Throwable){ - log.warn(e.message, e) - } - } - - /** - * Update all information for a specific user, and return the modified user. - * - * @param userDto user to update - * @return updated user - */ - @Transactional - @Throws(NotAuthorizedException::class) - suspend fun updateUser(userDto: UserDTO): UserDTO? { - val userOpt = userDto.id?.let { userRepository.findById(it) } - return if (userOpt?.isPresent == true) { - var user = userOpt.get() - user.firstName = userDto.firstName - user.lastName = userDto.lastName - user.email = userDto.email - user.activated = userDto.isActivated - user.langKey = userDto.langKey - val managedRoles = user.roles - val oldRoles = java.util.Set.copyOf(managedRoles) - managedRoles.clear() - managedRoles.addAll(getUserRoles(userDto.roles, oldRoles)) - user = userRepository.save(user) + user.firstName = firstName + user.lastName = lastName + user.email = email + user.langKey = langKey log.debug("Changed Information for User: {}", user) - try{ - identityService.updateAssociatedIdentity(user) - } - catch (e: Throwable) { - log.warn("could not update user ${user.login} with identity ${user.identity} from IDP", e) - } - - userMapper.userToUserDTO(user) - } else { - null - } - } + userRepository.save(user) - /** - * Delete the user with the given login. - * @param login the login to delete - */ - suspend fun deleteUser(login: String) { - val user = userRepository.findOneByLogin(login) - if (user != null) { - userRepository.delete(user) try { - identityService.deleteAssociatedIdentity(user.identity) - } - catch (e: Throwable){ + identityService.updateAssociatedIdentity(user) + } catch (e: Throwable) { log.warn(e.message, e) } - log.debug("Deleted User: {}", user) - } else { - log.warn("could not delete User with login: {}", login) } - } - /** - * Change the password of the user with the given login. - * @param password the new password - */ - fun changePassword(password: String) { - val currentUser = SecurityUtils.currentUserLogin - ?: throw InvalidRequestException( - "Cannot change password of unknown user", "", ErrorConstants.ERR_ENTITY_NOT_FOUND - ) - changePassword(currentUser, password) - } + /** + * Update all information for a specific user, and return the modified user. + * + * @param userDto user to update + * @return updated user + */ + @Transactional + @Throws(NotAuthorizedException::class) + suspend fun updateUser(userDto: UserDTO): UserDTO? { + val userOpt = userDto.id?.let { userRepository.findById(it) } + return if (userOpt?.isPresent == true) { + var user = userOpt.get() + user.firstName = userDto.firstName + user.lastName = userDto.lastName + user.email = userDto.email + user.activated = userDto.isActivated + user.langKey = userDto.langKey + val managedRoles = user.roles + val oldRoles = java.util.Set.copyOf(managedRoles) + managedRoles.clear() + managedRoles.addAll(getUserRoles(userDto.roles, oldRoles)) + user = userRepository.save(user) + log.debug("Changed Information for User: {}", user) + try { + identityService.updateAssociatedIdentity(user) + } catch (e: Throwable) { + log.warn("could not update user ${user.login} with identity ${user.identity} from IDP", e) + } - /** - * Change the user's password. - * @param password the new password - * @param login of the user to change password - */ - fun changePassword(login: String, password: String) { - val user = userRepository.findOneByLogin(login) - - if (user != null) { - val encryptedPassword = passwordService.encode(password) - user.password = encryptedPassword - log.debug("Changed password for User: {}", user) + userMapper.userToUserDTO(user) + } else { + null + } } - } - - /** - * Change the admin user's password. Should only be called in application startup - * @param email the new admin email - */ - @Transactional - suspend fun addAdminEmail(email: String): UserDTO { - // find the admin user - val user = userRepository.findOneByLogin("admin") - ?: throw Exception("No admin user found") - - // add the email - user.email = email - log.debug("Set admin email to: {}", email) - - // there is no identity for this user, so we create it and save it to the IDP - val id = identityService.saveAsIdentity(user) - // then save the identifier and update our database - user.identity = id?.id - return userMapper.userToUserDTO(user) - ?: throw Exception("Admin user could not be converted to DTO") - } - - /** - * Get a page of users. - * @param pageable the page information - * @return the requested page of users - */ - @Transactional(readOnly = true) - fun getAllManagedUsers(pageable: Pageable): Page { - log.debug("Request to get all Users") - return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER) - .map { user: User? -> userMapper.userToUserDTO(user) } - } - - /** - * Get the user with the given login. - * @param login the login - * @return an [Optional] which holds the user if one was found with the given login, - * and is empty otherwise - */ - @Transactional(readOnly = true) - fun getUserWithAuthoritiesByLogin(login: String): UserDTO? { - return userMapper.userToUserDTO(userRepository.findOneWithRolesByLogin(login)) - } - @Transactional(readOnly = true) - /** - * Get the current user. - * @return the currently authenticated user, or null if no user is currently authenticated - */ - fun getUserWithAuthorities(): User? { - return SecurityUtils.currentUserLogin?.let { userRepository.findOneWithRolesByLogin(it) } - } - - /** - * Not activated users should be automatically deleted after 3 days. - * - * This is scheduled to - * get fired everyday, at 01:00 (am). This is aimed at users, not subjects. So filter our - * users with *PARTICIPANT role and perform the action. - */ - @Scheduled(cron = "0 0 1 * * ?") - fun removeNotActivatedUsers() { - log.info("Scheduled scan for expired user accounts starting now") - val cutoff = ZonedDateTime.now().minus(Period.ofDays(3)) - val authorities = Arrays.asList( - RoleAuthority.PARTICIPANT.authority, RoleAuthority.INACTIVE_PARTICIPANT.authority - ) - userRepository.findAllByActivatedAndAuthoritiesNot(false, authorities).stream() - .filter { user: User? -> user?.let { revisionService.getAuditInfo(it).createdAt }!!.isBefore(cutoff) } - .forEach { user: User -> + /** + * Delete the user with the given login. + * @param login the login to delete + */ + suspend fun deleteUser(login: String) { + val user = userRepository.findOneByLogin(login) + if (user != null) { + userRepository.delete(user) try { - userRepository.delete(user) - log.info("Deleted not activated user after 3 days: {}", user.login) - } catch (ex: DataIntegrityViolationException) { - log.error("Could not delete user with login " + user.login, ex) + identityService.deleteAssociatedIdentity(user.identity) + } catch (e: Throwable) { + log.warn(e.message, e) } + log.debug("Deleted User: {}", user) + } else { + log.warn("could not delete User with login: {}", login) } - } + } - /** - * Find all user with given filter. - * - * @param userFilter filtering for users. - * @param pageable paging information - * @param includeProvenance whether to include created and modification fields. - * @return page of users. - */ - fun findUsers( - userFilter: UserFilter, pageable: Pageable?, includeProvenance: Boolean - ): Page? { - return pageable?.let { - userRepository.findAll(userFilter, it) - .map(if (includeProvenance) Function { user: User? -> userMapper.userToUserDTO(user) } else Function { user: User? -> - userMapper.userToUserDTONoProvenance( - user + /** + * Change the password of the user with the given login. + * @param password the new password + */ + fun changePassword(password: String) { + val currentUser = + SecurityUtils.currentUserLogin + ?: throw InvalidRequestException( + "Cannot change password of unknown user", + "", + ErrorConstants.ERR_ENTITY_NOT_FOUND, ) - }) + changePassword(currentUser, password) } - } - /** - * Update the roles of the given user. - * @param login user login - * @param roleDtos new roles to set - * @throws NotAuthorizedException if the current user is not allowed to modify the roles - * of the target user. - */ - @Transactional - @Throws(NotAuthorizedException::class) - suspend fun updateRoles(login: String, roleDtos: Set?) { - val user = userRepository.findOneByLogin(login) - ?: throw NotFoundException( - "User with login $login not found", - EntityName.USER, - ErrorConstants.ERR_ENTITY_NOT_FOUND, - mapOf(Pair("user", login)) - ) + /** + * Change the user's password. + * @param password the new password + * @param login of the user to change password + */ + fun changePassword( + login: String, + password: String, + ) { + val user = userRepository.findOneByLogin(login) + + if (user != null) { + val encryptedPassword = passwordService.encode(password) + user.password = encryptedPassword + log.debug("Changed password for User: {}", user) + } + } - val managedRoles = user.roles - val oldRoles = managedRoles.toMutableSet() + /** + * Change the admin user's password. Should only be called in application startup + * @param email the new admin email + */ + @Transactional + suspend fun addAdminEmail(email: String): UserDTO { + // find the admin user + val user = + userRepository.findOneByLogin("admin") + ?: throw Exception("No admin user found") + + // add the email + user.email = email + log.debug("Set admin email to: {}", email) + + // there is no identity for this user, so we create it and save it to the IDP + val id = identityService.saveAsIdentity(user) + // then save the identifier and update our database + user.identity = id?.id + return userMapper.userToUserDTO(user) + ?: throw Exception("Admin user could not be converted to DTO") + } - managedRoles.clear() - roleDtos?.let { getUserRoles(it, oldRoles) }?.let { managedRoles.addAll(it) } - ?: throw Exception("could not add roles for user: $user") - userRepository.save(user) + /** + * Get a page of users. + * @param pageable the page information + * @return the requested page of users + */ + @Transactional(readOnly = true) + fun getAllManagedUsers(pageable: Pageable): Page { + log.debug("Request to get all Users") + return userRepository + .findAllByLoginNot(pageable, Constants.ANONYMOUS_USER) + .map { user: User? -> userMapper.userToUserDTO(user) } + } - try { - identityService.updateAssociatedIdentity(user) + /** + * Get the user with the given login. + * @param login the login + * @return an [Optional] which holds the user if one was found with the given login, + * and is empty otherwise + */ + @Transactional(readOnly = true) + fun getUserWithAuthoritiesByLogin(login: String): UserDTO? = + userMapper.userToUserDTO(userRepository.findOneWithRolesByLogin(login)) + + /** + * Get the current user. + * @return the currently authenticated user, or null if no user is currently authenticated + */ + @Transactional(readOnly = true) + fun getUserWithAuthorities(): User? = + SecurityUtils.currentUserLogin?.let { userRepository.findOneWithRolesByLogin(it) } + + /** + * Not activated users should be automatically deleted after 3 days. + * + * This is scheduled to + * get fired everyday, at 01:00 (am). This is aimed at users, not subjects. So filter our + * users with *PARTICIPANT role and perform the action. + */ + @Scheduled(cron = "0 0 1 * * ?") + fun removeNotActivatedUsers() { + log.info("Scheduled scan for expired user accounts starting now") + val cutoff = ZonedDateTime.now().minus(Period.ofDays(3)) + val authorities = + Arrays.asList( + RoleAuthority.PARTICIPANT.authority, + RoleAuthority.INACTIVE_PARTICIPANT.authority, + ) + userRepository + .findAllByActivatedAndAuthoritiesNot(false, authorities) + .stream() + .filter { user: User? -> user?.let { revisionService.getAuditInfo(it).createdAt }!!.isBefore(cutoff) } + .forEach { user: User -> + try { + userRepository.delete(user) + log.info("Deleted not activated user after 3 days: {}", user.login) + } catch (ex: DataIntegrityViolationException) { + log.error("Could not delete user with login " + user.login, ex) + } + } } - catch (e: Throwable){ - log.warn(e.message, e) + + /** + * Find all user with given filter. + * + * @param userFilter filtering for users. + * @param pageable paging information + * @param includeProvenance whether to include created and modification fields. + * @return page of users. + */ + fun findUsers( + userFilter: UserFilter, + pageable: Pageable?, + includeProvenance: Boolean, + ): Page? = + pageable?.let { + userRepository + .findAll(userFilter, it) + .map( + if (includeProvenance) { + Function { user: User? -> userMapper.userToUserDTO(user) } + } else { + Function { user: User? -> + userMapper.userToUserDTONoProvenance( + user, + ) + } + }, + ) + } + + /** + * Update the roles of the given user. + * @param login user login + * @param roleDtos new roles to set + * @throws NotAuthorizedException if the current user is not allowed to modify the roles + * of the target user. + */ + @Transactional + @Throws(NotAuthorizedException::class) + suspend fun updateRoles( + login: String, + roleDtos: Set?, + ) { + val user = + userRepository.findOneByLogin(login) + ?: throw NotFoundException( + "User with login $login not found", + EntityName.USER, + ErrorConstants.ERR_ENTITY_NOT_FOUND, + mapOf(Pair("user", login)), + ) + + val managedRoles = user.roles + val oldRoles = managedRoles.toMutableSet() + + managedRoles.clear() + roleDtos?.let { getUserRoles(it, oldRoles) }?.let { managedRoles.addAll(it) } + ?: throw Exception("could not add roles for user: $user") + userRepository.save(user) + + try { + identityService.updateAssociatedIdentity(user) + } catch (e: Throwable) { + log.warn(e.message, e) + } } - } - @Throws(IdpException::class) - suspend fun getRecoveryLink(user: User): String { - return identityService.getRecoveryLink(user) - } + @Throws(IdpException::class) + suspend fun getRecoveryLink(user: User): String = identityService.getRecoveryLink(user) - companion object { - private val log = LoggerFactory.getLogger(UserService::class.java) + companion object { + private val log = LoggerFactory.getLogger(UserService::class.java) + } } -} diff --git a/src/main/java/org/radarbase/management/service/catalog/CatalogSourceData.kt b/src/main/java/org/radarbase/management/service/catalog/CatalogSourceData.kt index 2f7dfa81e..a2f22b18c 100644 --- a/src/main/java/org/radarbase/management/service/catalog/CatalogSourceData.kt +++ b/src/main/java/org/radarbase/management/service/catalog/CatalogSourceData.kt @@ -38,24 +38,27 @@ class CatalogSourceData { class DataField { @JsonProperty val name: String? = null - override fun toString(): String { - return ("DataField{" + "name='" + name + '\'' - + '}') - } - } - override fun toString(): String { - return ("CatalogSourceData{" + "appProvider='" + appProvider + '\'' - + ", processingState='" + processingState + '\'' - + ", type='" + type + '\'' - + ", doc='" + doc + '\'' - + ", sampleRate=" + sampleRate - + ", unit='" + unit + '\'' - + ", fields=" + fields - + ", topic='" + topic + '\'' - + ", keySchema='" + keySchema + '\'' - + ", valueSchema='" + valueSchema + '\'' - + ", tags=" + tags - + '}') + override fun toString(): String = + ( + "DataField{" + "name='" + name + '\'' + + '}' + ) } + + override fun toString(): String = + ( + "CatalogSourceData{" + "appProvider='" + appProvider + '\'' + + ", processingState='" + processingState + '\'' + + ", type='" + type + '\'' + + ", doc='" + doc + '\'' + + ", sampleRate=" + sampleRate + + ", unit='" + unit + '\'' + + ", fields=" + fields + + ", topic='" + topic + '\'' + + ", keySchema='" + keySchema + '\'' + + ", valueSchema='" + valueSchema + '\'' + + ", tags=" + tags + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/catalog/CatalogSourceType.kt b/src/main/java/org/radarbase/management/service/catalog/CatalogSourceType.kt index fe5dfb76a..3c6b38832 100644 --- a/src/main/java/org/radarbase/management/service/catalog/CatalogSourceType.kt +++ b/src/main/java/org/radarbase/management/service/catalog/CatalogSourceType.kt @@ -45,14 +45,30 @@ class CatalogSourceType { return false } val that = other as CatalogSourceType - return assessmentType == that.assessmentType && appProvider == that.appProvider && vendor == that.vendor && model == that.model && version == that.version && name == that.name && doc == that.doc && scope == that.scope && properties == that.properties && data == that.data + return assessmentType == that.assessmentType && + appProvider == that.appProvider && + vendor == that.vendor && + model == that.model && + version == that.version && + name == that.name && + doc == that.doc && + scope == that.scope && + properties == that.properties && + data == that.data } - override fun hashCode(): Int { - return Objects + override fun hashCode(): Int = + Objects .hash( - assessmentType, appProvider, vendor, model, version, name, doc, scope, properties, - data + assessmentType, + appProvider, + vendor, + model, + version, + name, + doc, + scope, + properties, + data, ) - } } diff --git a/src/main/java/org/radarbase/management/service/catalog/SampleRateConfig.kt b/src/main/java/org/radarbase/management/service/catalog/SampleRateConfig.kt index 1bc02797c..b3abe42ee 100644 --- a/src/main/java/org/radarbase/management/service/catalog/SampleRateConfig.kt +++ b/src/main/java/org/radarbase/management/service/catalog/SampleRateConfig.kt @@ -16,11 +16,13 @@ class SampleRateConfig { @JsonProperty val isConfigurable = false - override fun toString(): String { - return ("SampleRateConfig{interval=" + interval - + ", frequency=" + frequency - + ", dynamic=" + isDynamic - + ", configurable=" + isConfigurable - + '}') - } + + override fun toString(): String = + ( + "SampleRateConfig{interval=" + interval + + ", frequency=" + frequency + + ", dynamic=" + isDynamic + + ", configurable=" + isConfigurable + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/AttributeMapDTO.kt b/src/main/java/org/radarbase/management/service/dto/AttributeMapDTO.kt index 1d8a01441..b72865a83 100644 --- a/src/main/java/org/radarbase/management/service/dto/AttributeMapDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/AttributeMapDTO.kt @@ -5,27 +5,30 @@ import java.util.* /** * Created by nivethika on 30-8-17. */ -class AttributeMapDTO @JvmOverloads constructor(var key: String? = null, var value: String? = null) { - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null || javaClass != other.javaClass) { - return false +class AttributeMapDTO + @JvmOverloads + constructor( + var key: String? = null, + var value: String? = null, + ) { + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val attributeMapDto = other as AttributeMapDTO + return key == attributeMapDto.key && value == attributeMapDto.value } - val attributeMapDto = other as AttributeMapDTO - return key == attributeMapDto.key && value == attributeMapDto.value - } - override fun hashCode(): Int { - return Objects.hash(key, value) - } + override fun hashCode(): Int = Objects.hash(key, value) - override fun toString(): String { - return ("AttributeMapDTO{" - + " key='" + key + "'" - + ", value='" + value + "'" - + '}') + override fun toString(): String = + ( + "AttributeMapDTO{" + + " key='" + key + "'" + + ", value='" + value + "'" + + '}' + ) } -} diff --git a/src/main/java/org/radarbase/management/service/dto/ClientDetailsDTO.kt b/src/main/java/org/radarbase/management/service/dto/ClientDetailsDTO.kt index 1992b14e4..b29924fc1 100644 --- a/src/main/java/org/radarbase/management/service/dto/ClientDetailsDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/ClientDetailsDTO.kt @@ -6,7 +6,8 @@ import javax.validation.constraints.NotNull * Created by dverbeec on 7/09/2017. */ class ClientDetailsDTO { - @NotNull var clientId: String? = null + @NotNull + var clientId: String? = null var clientSecret: String? = null var scope: Set? = null var resourceIds: Set? = null diff --git a/src/main/java/org/radarbase/management/service/dto/ClientPairInfoDTO.kt b/src/main/java/org/radarbase/management/service/dto/ClientPairInfoDTO.kt index 6db908179..7b11bd113 100644 --- a/src/main/java/org/radarbase/management/service/dto/ClientPairInfoDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/ClientPairInfoDTO.kt @@ -8,7 +8,12 @@ import java.util.* /** * Created by dverbeec on 29/08/2017. */ -class ClientPairInfoDTO(baseUrl: URL, tokenName: String, tokenUrl: URL?, timeout: Duration) { +class ClientPairInfoDTO( + baseUrl: URL, + tokenName: String, + tokenUrl: URL?, + timeout: Duration, +) { val tokenName: String val tokenUrl: URL val baseUrl: URL @@ -38,19 +43,22 @@ class ClientPairInfoDTO(baseUrl: URL, tokenName: String, tokenUrl: URL?, timeout return false } val that = other as ClientPairInfoDTO - return tokenName == that.tokenName && tokenUrl == that.tokenUrl && baseUrl == that.baseUrl && timeout == that.timeout && timesOutAt == that.timesOutAt + return tokenName == that.tokenName && + tokenUrl == that.tokenUrl && + baseUrl == that.baseUrl && + timeout == that.timeout && + timesOutAt == that.timesOutAt } - override fun hashCode(): Int { - return Objects.hash(baseUrl, tokenName, tokenUrl, timeout, timesOutAt) - } + override fun hashCode(): Int = Objects.hash(baseUrl, tokenName, tokenUrl, timeout, timesOutAt) - override fun toString(): String { - return ("ClientPairInfoDTO{" - + "tokenName='" + tokenName + '\'' - + ", tokenUrl=" + tokenUrl + '\'' - + ", timeout=" + timeout + '\'' - + ", timesOutAt=" + timesOutAt + '\'' - + ", baseUrl=" + baseUrl + '}') - } + override fun toString(): String = + ( + "ClientPairInfoDTO{" + + "tokenName='" + tokenName + '\'' + + ", tokenUrl=" + tokenUrl + '\'' + + ", timeout=" + timeout + '\'' + + ", timesOutAt=" + timesOutAt + '\'' + + ", baseUrl=" + baseUrl + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/GroupDTO.kt b/src/main/java/org/radarbase/management/service/dto/GroupDTO.kt index ca53db0a4..0f90fba49 100644 --- a/src/main/java/org/radarbase/management/service/dto/GroupDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/GroupDTO.kt @@ -11,7 +11,10 @@ import javax.validation.constraints.NotNull class GroupDTO { var id: Long? = null var projectId: Long? = null - @NotNull var name: String? = null + + @NotNull + var name: String? = null + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -22,17 +25,18 @@ class GroupDTO { val groupDto = other as GroupDTO return if (id == null || groupDto.id == null) { false - } else id == groupDto.id + } else { + id == groupDto.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("GroupDTO{" - + "id=" + id - + ", name='" + name + "'" - + '}') - } + override fun toString(): String = + ( + "GroupDTO{" + + "id=" + id + + ", name='" + name + "'" + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/MinimalSourceDetailsDTO.kt b/src/main/java/org/radarbase/management/service/dto/MinimalSourceDetailsDTO.kt index cab15b76d..9fdae8f61 100644 --- a/src/main/java/org/radarbase/management/service/dto/MinimalSourceDetailsDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/MinimalSourceDetailsDTO.kt @@ -17,6 +17,7 @@ class MinimalSourceDetailsDTO { var sourceName: String? = null var isAssigned: Boolean? = null var attributes: MutableMap = HashMap() + fun id(id: Long?): MinimalSourceDetailsDTO { this.id = id return this @@ -62,17 +63,18 @@ class MinimalSourceDetailsDTO { return this } - override fun toString(): String { - return ("MinimalSourceDetailsDTO{" - + "id=" + id - + ", sourceTypeId=" + sourceTypeId - + ", sourceTypeProducer='" + sourceTypeProducer + '\'' - + ", sourceTypeModel='" + sourceTypeModel + '\'' - + ", sourceTypeCatalogVersion='" + sourceTypeCatalogVersion + '\'' - + ", expectedSourceName='" + expectedSourceName + '\'' - + ", sourceId=" + sourceId - + ", sourceName='" + sourceName + '\'' - + ", assigned=" + isAssigned - + ", attributes=" + attributes + '}') - } + override fun toString(): String = + ( + "MinimalSourceDetailsDTO{" + + "id=" + id + + ", sourceTypeId=" + sourceTypeId + + ", sourceTypeProducer='" + sourceTypeProducer + '\'' + + ", sourceTypeModel='" + sourceTypeModel + '\'' + + ", sourceTypeCatalogVersion='" + sourceTypeCatalogVersion + '\'' + + ", expectedSourceName='" + expectedSourceName + '\'' + + ", sourceId=" + sourceId + + ", sourceName='" + sourceName + '\'' + + ", assigned=" + isAssigned + + ", attributes=" + attributes + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/OrganizationDTO.kt b/src/main/java/org/radarbase/management/service/dto/OrganizationDTO.kt index 7fce03b43..1ce122752 100644 --- a/src/main/java/org/radarbase/management/service/dto/OrganizationDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/OrganizationDTO.kt @@ -11,9 +11,15 @@ import javax.validation.constraints.NotNull @JsonInclude(JsonInclude.Include.NON_NULL) class OrganizationDTO : Serializable { var id: Long? = null - @NotNull var name: String? = null - @NotNull var description: String? = null - @NotNull var location: String? = null + + @NotNull + var name: String? = null + + @NotNull + var description: String? = null + + @NotNull + var location: String? = null var projects: List = emptyList() @@ -28,18 +34,17 @@ class OrganizationDTO : Serializable { return name == orgDto.name && description == orgDto.description && location == orgDto.location } - override fun hashCode(): Int { - return Objects.hashCode(name) - } + override fun hashCode(): Int = Objects.hashCode(name) - override fun toString(): String { - return ("OrganizationDTO{" - + "id=" + id - + ", name='" + name + "'" - + ", description='" + description + "'" - + ", location='" + location + "'" - + '}') - } + override fun toString(): String = + ( + "OrganizationDTO{" + + "id=" + id + + ", name='" + name + "'" + + ", description='" + description + "'" + + ", location='" + location + "'" + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/package-info.kt b/src/main/java/org/radarbase/management/service/dto/PackageInfo.kt similarity index 75% rename from src/main/java/org/radarbase/management/service/dto/package-info.kt rename to src/main/java/org/radarbase/management/service/dto/PackageInfo.kt index 7725988f2..a5957d201 100644 --- a/src/main/java/org/radarbase/management/service/dto/package-info.kt +++ b/src/main/java/org/radarbase/management/service/dto/PackageInfo.kt @@ -4,5 +4,6 @@ * * The names of the classes in this package should end in `DTO`. */ -package org.radarbase.management.service.dto +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.service.dto diff --git a/src/main/java/org/radarbase/management/service/dto/ProjectDTO.kt b/src/main/java/org/radarbase/management/service/dto/ProjectDTO.kt index f7376bab0..b8db8fbd2 100644 --- a/src/main/java/org/radarbase/management/service/dto/ProjectDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/ProjectDTO.kt @@ -13,21 +13,26 @@ import javax.validation.constraints.NotNull @JsonInclude(JsonInclude.Include.NON_NULL) class ProjectDTO : Serializable { var id: Long? = null - @NotNull var projectName: String? = null + + @NotNull + var projectName: String? = null var humanReadableProjectName: String? = null - @NotNull var description: String? = null + + @NotNull + var description: String? = null var organization: OrganizationDTO? = null // Defaults to organization name, but if that is not set then we can use the organizationName var organizationName: String? = null get() { - if (organization?.name != null) + if (organization?.name != null) { field = organization?.name + } return field } - - @NotNull var location: String? = null + @NotNull + var location: String? = null var startDate: ZonedDateTime? = null var projectStatus: ProjectStatus? = null var endDate: ZonedDateTime? = null @@ -39,6 +44,7 @@ class ProjectDTO : Serializable { @JsonInclude(JsonInclude.Include.NON_EMPTY) var groups: Set? = null var persistentTokenTimeout: Long? = null + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -49,26 +55,27 @@ class ProjectDTO : Serializable { val projectDto = other as ProjectDTO return if (id == null || projectDto.id == null) { false - } else id == projectDto.id + } else { + id == projectDto.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("ProjectDTO{" - + "id=" + id - + ", projectName='" + projectName + "'" - + ", description='" + description + "'" - + ", organization='" + organization + "'" - + ", organizationName='" + organizationName + "'" - + ", location='" + location + "'" - + ", startDate='" + startDate + "'" - + ", projectStatus='" + projectStatus + "'" - + ", endDate='" + endDate + "'" - + '}') - } + override fun toString(): String = + ( + "ProjectDTO{" + + "id=" + id + + ", projectName='" + projectName + "'" + + ", description='" + description + "'" + + ", organization='" + organization + "'" + + ", organizationName='" + organizationName + "'" + + ", location='" + location + "'" + + ", startDate='" + startDate + "'" + + ", projectStatus='" + projectStatus + "'" + + ", endDate='" + endDate + "'" + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/PublicProjectDTO.kt b/src/main/java/org/radarbase/management/service/dto/PublicProjectDTO.kt index 9fde3d3e7..916229c1b 100644 --- a/src/main/java/org/radarbase/management/service/dto/PublicProjectDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/PublicProjectDTO.kt @@ -1,21 +1,20 @@ package org.radarbase.management.service.dto import com.fasterxml.jackson.annotation.JsonInclude -import reactor.util.annotation.NonNull import java.io.Serializable +import javax.validation.constraints.NotNull @JsonInclude(JsonInclude.Include.NON_NULL) class PublicProjectDTO : Serializable { - @NonNull + @NotNull var projectName: String? = null - @NonNull + @NotNull var description: String? = null @JsonInclude(JsonInclude.Include.NON_EMPTY) var sourceTypes: Set = emptySet() - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -39,7 +38,6 @@ class PublicProjectDTO : Serializable { override fun toString(): String = "PublicProjectDTO(projectName=$projectName, description=$description, sourceTypes=$sourceTypes)" - companion object { private const val serialVersionUID = 1L } diff --git a/src/main/java/org/radarbase/management/service/dto/RevisionInfoDTO.kt b/src/main/java/org/radarbase/management/service/dto/RevisionInfoDTO.kt index 1309b2ee8..c23b6f8ba 100644 --- a/src/main/java/org/radarbase/management/service/dto/RevisionInfoDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/RevisionInfoDTO.kt @@ -31,20 +31,27 @@ class RevisionInfoDTO : Serializable { * @param changes the changes * @return the RevisionInfoDTO object */ - fun from(revisionEntity: CustomRevisionEntity, changes: Map>): RevisionInfoDTO { + fun from( + revisionEntity: CustomRevisionEntity, + changes: Map>, + ): RevisionInfoDTO { val result = RevisionInfoDTO() result.author = revisionEntity.auditor result.timestamp = revisionEntity.timestamp result.id = revisionEntity.id - result.changes = changes.entries - .associateBy ( - { obj -> obj.key }, - { - it.value.groupBy { obj -> - obj.javaClass.getSimpleName().replace("DTO$".toRegex(), "").lowercase() - } - } - ) + result.changes = + changes.entries + .associateBy( + { obj -> obj.key }, + { + it.value.groupBy { obj -> + obj.javaClass + .getSimpleName() + .replace("DTO$".toRegex(), "") + .lowercase() + } + }, + ) return result } diff --git a/src/main/java/org/radarbase/management/service/dto/RoleDTO.kt b/src/main/java/org/radarbase/management/service/dto/RoleDTO.kt index 25b487219..6495306ec 100644 --- a/src/main/java/org/radarbase/management/service/dto/RoleDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/RoleDTO.kt @@ -13,13 +13,15 @@ class RoleDTO { var projectId: Long? = null var projectName: String? = null var authorityName: String? = null - override fun toString(): String { - return ("RoleDTO{" + "id=" + id - + ", organizationId=" + organizationId - + ", projectId=" + projectId - + ", authorityName='" + authorityName + '\'' - + '}') - } + + override fun toString(): String = + ( + "RoleDTO{" + "id=" + id + + ", organizationId=" + organizationId + + ", projectId=" + projectId + + ", authorityName='" + authorityName + '\'' + + '}' + ) override fun equals(other: Any?): Boolean { if (this === other) { @@ -29,10 +31,13 @@ class RoleDTO { return false } val roleDto = other as RoleDTO - return id == roleDto.id && organizationId == roleDto.organizationId && organizationName == roleDto.organizationName && projectId == roleDto.projectId && projectName == roleDto.projectName && authorityName == roleDto.authorityName + return id == roleDto.id && + organizationId == roleDto.organizationId && + organizationName == roleDto.organizationName && + projectId == roleDto.projectId && + projectName == roleDto.projectName && + authorityName == roleDto.authorityName } - override fun hashCode(): Int { - return if (id != null) id.hashCode() else 0 - } + override fun hashCode(): Int = if (id != null) id.hashCode() else 0 } diff --git a/src/main/java/org/radarbase/management/service/dto/SiteSettingsDto.kt b/src/main/java/org/radarbase/management/service/dto/SiteSettingsDto.kt index b4fba3a0d..e50967d76 100644 --- a/src/main/java/org/radarbase/management/service/dto/SiteSettingsDto.kt +++ b/src/main/java/org/radarbase/management/service/dto/SiteSettingsDto.kt @@ -9,6 +9,7 @@ import java.util.* */ class SiteSettingsDto : Serializable { var hiddenSubjectFields = listOf() + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -20,14 +21,13 @@ class SiteSettingsDto : Serializable { return hiddenSubjectFields == that.hiddenSubjectFields } - override fun hashCode(): Int { - return Objects.hash(hiddenSubjectFields) - } + override fun hashCode(): Int = Objects.hash(hiddenSubjectFields) - override fun toString(): String { - return ("SiteSettingsDTO{" - + "hiddenSubjectProperties=" - + hiddenSubjectFields - + '}') - } + override fun toString(): String = + ( + "SiteSettingsDTO{" + + "hiddenSubjectProperties=" + + hiddenSubjectFields + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/SourceDTO.kt b/src/main/java/org/radarbase/management/service/dto/SourceDTO.kt index c54f245d5..6305680a7 100644 --- a/src/main/java/org/radarbase/management/service/dto/SourceDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/SourceDTO.kt @@ -11,15 +11,22 @@ import javax.validation.constraints.NotNull class SourceDTO : Serializable { var id: Long? = null var sourceId: UUID? = null - @NotNull var sourceName: String? = null + + @NotNull + var sourceName: String? = null var expectedSourceName: String? = null - @NotNull var assigned: Boolean? = null - @NotNull var sourceType: SourceTypeDTO? = null + + @NotNull + var assigned: Boolean? = null + + @NotNull + var sourceType: SourceTypeDTO? = null var subjectLogin: String? = null @JsonInclude(JsonInclude.Include.NON_NULL) var project: MinimalProjectDetailsDTO? = null var attributes: Map? = null + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -28,27 +35,42 @@ class SourceDTO : Serializable { return false } val source = other as SourceDTO - return id == source.id && sourceId == source.sourceId && sourceName == source.sourceName && expectedSourceName == source.expectedSourceName && assigned == source.assigned && sourceType == source.sourceType && subjectLogin == source.subjectLogin && project == source.project && attributes == source.attributes + return id == source.id && + sourceId == source.sourceId && + sourceName == source.sourceName && + expectedSourceName == source.expectedSourceName && + assigned == source.assigned && + sourceType == source.sourceType && + subjectLogin == source.subjectLogin && + project == source.project && + attributes == source.attributes } - override fun hashCode(): Int { - return Objects.hash( - id, sourceId, sourceName, expectedSourceName, assigned, sourceType, - subjectLogin, project, attributes + override fun hashCode(): Int = + Objects.hash( + id, + sourceId, + sourceName, + expectedSourceName, + assigned, + sourceType, + subjectLogin, + project, + attributes, ) - } - override fun toString(): String { - return ("SourceDTO{" - + "id=" + id - + ", sourceId='" + sourceId + '\'' - + ", sourceName='" + sourceName + '\'' - + ", assigned=" + assigned - + ", sourceType=" + sourceType - + ", project=" + project - + ", subjectLogin=" + subjectLogin - + '}') - } + override fun toString(): String = + ( + "SourceDTO{" + + "id=" + id + + ", sourceId='" + sourceId + '\'' + + ", sourceName='" + sourceName + '\'' + + ", assigned=" + assigned + + ", sourceType=" + sourceType + + ", project=" + project + + ", subjectLogin=" + subjectLogin + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/SourceDataDTO.kt b/src/main/java/org/radarbase/management/service/dto/SourceDataDTO.kt index 34a3e7dcf..3d748293f 100644 --- a/src/main/java/org/radarbase/management/service/dto/SourceDataDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/SourceDataDTO.kt @@ -16,10 +16,10 @@ class SourceDataDTO : Serializable { var sourceDataName: String? = null - //Default data frequency + // Default data frequency var frequency: String? = null - //Measurement unit. + // Measurement unit. var unit: String? = null // Define if the samples are RAW data or the result of some computation @@ -46,29 +46,30 @@ class SourceDataDTO : Serializable { val sourceDataDto = other as SourceDataDTO return if (sourceDataDto.id == null || id == null) { false - } else id == sourceDataDto.id + } else { + id == sourceDataDto.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("SourceDataDTO{" - + "id=" + id - + ", sourceDataType='" + sourceDataType + '\'' - + ", sourceDataName='" + sourceDataName + '\'' - + ", frequency='" + frequency + '\'' - + ", unit='" + unit + '\'' - + ", processingState=" + processingState - + ", dataClass=" + dataClass - + ", keySchema='" + keySchema + '\'' - + ", valueSchema='" + valueSchema + '\'' - + ", topic='" + topic + '\'' - + ", provider='" + provider + '\'' - + ", enabled=" + isEnabled - + '}') - } + override fun toString(): String = + ( + "SourceDataDTO{" + + "id=" + id + + ", sourceDataType='" + sourceDataType + '\'' + + ", sourceDataName='" + sourceDataName + '\'' + + ", frequency='" + frequency + '\'' + + ", unit='" + unit + '\'' + + ", processingState=" + processingState + + ", dataClass=" + dataClass + + ", keySchema='" + keySchema + '\'' + + ", valueSchema='" + valueSchema + '\'' + + ", topic='" + topic + '\'' + + ", provider='" + provider + '\'' + + ", enabled=" + isEnabled + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/SourceTypeDTO.kt b/src/main/java/org/radarbase/management/service/dto/SourceTypeDTO.kt index 0168170e2..7a278daff 100644 --- a/src/main/java/org/radarbase/management/service/dto/SourceTypeDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/SourceTypeDTO.kt @@ -13,11 +13,21 @@ import javax.validation.constraints.NotNull @JsonInclude(JsonInclude.Include.NON_NULL) class SourceTypeDTO : Serializable { var id: Long? = null - @NotNull var producer: String? = null - @NotNull var model: String? = null - @NotNull var catalogVersion: String? = null - @NotNull var sourceTypeScope: String? = null - @NotNull var canRegisterDynamically: Boolean = false + + @NotNull + var producer: String? = null + + @NotNull + var model: String? = null + + @NotNull + var catalogVersion: String? = null + + @NotNull + var sourceTypeScope: String? = null + + @NotNull + var canRegisterDynamically: Boolean = false var name: String? = null var description: String? = null var assessmentType: String? = null @@ -26,6 +36,7 @@ class SourceTypeDTO : Serializable { @set:JsonSetter(nulls = Nulls.AS_EMPTY) @JsonInclude(JsonInclude.Include.NON_EMPTY) var sourceData: MutableSet = HashSet() + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -36,27 +47,28 @@ class SourceTypeDTO : Serializable { val sourceTypeDto = other as SourceTypeDTO return if (id == null || sourceTypeDto.id == null) { false - } else id == sourceTypeDto.id + } else { + id == sourceTypeDto.id + } } - override fun hashCode(): Int { - return Objects.hashCode(id) - } + override fun hashCode(): Int = Objects.hashCode(id) - override fun toString(): String { - return ("SourceTypeDTO{" - + "id=" + id - + ", producer='" + producer + "'" - + ", model='" + model + "'" - + ", catalogVersion='" + catalogVersion + "'" - + ", sourceTypeScope='" + sourceTypeScope + "'" - + ", canRegisterDynamically='" + canRegisterDynamically + "'" - + ", name='" + name + '\'' - + ", description=" + description - + ", appProvider=" + appProvider - + ", assessmentType=" + assessmentType - + '}') - } + override fun toString(): String = + ( + "SourceTypeDTO{" + + "id=" + id + + ", producer='" + producer + "'" + + ", model='" + model + "'" + + ", catalogVersion='" + catalogVersion + "'" + + ", sourceTypeScope='" + sourceTypeScope + "'" + + ", canRegisterDynamically='" + canRegisterDynamically + "'" + + ", name='" + name + '\'' + + ", description=" + description + + ", appProvider=" + appProvider + + ", assessmentType=" + assessmentType + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/SubjectDTO.kt b/src/main/java/org/radarbase/management/service/dto/SubjectDTO.kt index d9205e33e..78bca6258 100644 --- a/src/main/java/org/radarbase/management/service/dto/SubjectDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/SubjectDTO.kt @@ -22,13 +22,14 @@ class SubjectDTO : Serializable { DISCONTINUED, // activated = false, removed = true - INVALID // activated = true, removed = true (invalid state, makes no sense) + INVALID, // activated = true, removed = true (invalid state, makes no sense) } var id: Long? = null private var _login: String? = null var login: String? - get() = if (_login == null) { + get() = + if (_login == null) { _login = UUID.randomUUID().toString() _login } else { @@ -76,16 +77,26 @@ class SubjectDTO : Serializable { val subjectDto = other as SubjectDTO return if (id == null || subjectDto.id == null) { false - } else id != subjectDto.id - } - - override fun hashCode(): Int { - return Objects.hashCode(id) + } else { + id != subjectDto.id + } } - override fun toString(): String { - return ("SubjectDTO{" + "id=" + id + ", login='" + login + '\'' + ", externalLink='" + externalLink + '\'' + ", externalId='" + externalId + '\'' + ", status=" + status + ", project=" + (project?.projectName) + ", attributes=" + attributes + '}') - } + override fun hashCode(): Int = Objects.hashCode(id) + + override fun toString(): String = + ( + "SubjectDTO{" + "id=" + id + ", login='" + login + '\'' + ", externalLink='" + externalLink + '\'' + ", externalId='" + + externalId + + '\'' + + ", status=" + + status + + ", project=" + + (project?.projectName) + + ", attributes=" + + attributes + + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/service/dto/TokenDTO.kt b/src/main/java/org/radarbase/management/service/dto/TokenDTO.kt index 71f4b2a0f..f7f9ba3f0 100644 --- a/src/main/java/org/radarbase/management/service/dto/TokenDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/TokenDTO.kt @@ -3,15 +3,18 @@ package org.radarbase.management.service.dto import java.net.URL import java.util.* -class TokenDTO /** * Create a meta-token using refreshToken, baseUrl of platform, and privacyPolicyURL for this * token. * @param refreshToken refreshToken. * @param baseUrl baseUrl of the platform * @param privacyPolicyUrl privacyPolicyUrl for this token. - */(val refreshToken: String, val baseUrl: URL, val privacyPolicyUrl: URL) { - + */ +class TokenDTO( + val refreshToken: String, + val baseUrl: URL, + val privacyPolicyUrl: URL, +) { override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -23,15 +26,14 @@ class TokenDTO return refreshToken == that.refreshToken && baseUrl == that.baseUrl && privacyPolicyUrl == that.privacyPolicyUrl } - override fun hashCode(): Int { - return Objects.hash(refreshToken, baseUrl, privacyPolicyUrl) - } + override fun hashCode(): Int = Objects.hash(refreshToken, baseUrl, privacyPolicyUrl) - override fun toString(): String { - return ("TokenDTO{" - + "refreshToken='" + refreshToken - + ", baseUrl=" + baseUrl - + ", privacyPolicyUrl=" + privacyPolicyUrl - + '}') - } + override fun toString(): String = + ( + "TokenDTO{" + + "refreshToken='" + refreshToken + + ", baseUrl=" + baseUrl + + ", privacyPolicyUrl=" + privacyPolicyUrl + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/service/dto/UserDTO.kt b/src/main/java/org/radarbase/management/service/dto/UserDTO.kt index 659e230d6..689143e1d 100644 --- a/src/main/java/org/radarbase/management/service/dto/UserDTO.kt +++ b/src/main/java/org/radarbase/management/service/dto/UserDTO.kt @@ -10,12 +10,24 @@ import javax.validation.constraints.Size */ open class UserDTO { var id: Long? = null - @Pattern(regexp = "^[_'.@A-Za-z0-9- ]*$") @Size(max = 50, min = 1) var login: String? = null - @Size(max = 50) var firstName: String? = null - @Size(max = 50) var lastName: String? = null - @Email @Size(min = 5, max = 100) var email: String? = null + + @Pattern(regexp = "^[_'.@A-Za-z0-9- ]*$") + @Size(max = 50, min = 1) + var login: String? = null + + @Size(max = 50) + var firstName: String? = null + + @Size(max = 50) + var lastName: String? = null + + @Email + @Size(min = 5, max = 100) + var email: String? = null var isActivated = false - @Size(min = 2, max = 5) var langKey: String? = null + + @Size(min = 2, max = 5) + var langKey: String? = null var createdBy: String? = null var createdDate: ZonedDateTime? = null var lastModifiedBy: String? = null @@ -27,18 +39,20 @@ open class UserDTO { /** Identifier for association with the identity service provider. * Null if not linked to an external identity. */ var identity: String? = null - override fun toString(): String { - return ("UserDTO{" - + "login='" + login + '\'' - + ", firstName='" + firstName + '\'' - + ", lastName='" + lastName + '\'' - + ", email='" + email + '\'' - + ", activated=" + isActivated - + ", langKey='" + langKey + '\'' - + ", createdBy=" + createdBy - + ", createdDate=" + createdDate - + ", lastModifiedBy='" + lastModifiedBy + '\'' - + ", lastModifiedDate=" + lastModifiedDate - + "}") - } + + override fun toString(): String = + ( + "UserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", activated=" + isActivated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + "}" + ) } diff --git a/src/main/java/org/radarbase/management/service/mapper/CatalogSourceDataMapper.kt b/src/main/java/org/radarbase/management/service/mapper/CatalogSourceDataMapper.kt index 3e405ffa1..b4d01ecc3 100644 --- a/src/main/java/org/radarbase/management/service/mapper/CatalogSourceDataMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/CatalogSourceDataMapper.kt @@ -16,7 +16,6 @@ interface CatalogSourceDataMapper { @Mapping(target = "enabled", expression = "java(true)") @Mapping(target = "sourceType", ignore = true) fun catalogSourceDataToSourceData(catalogSourceData: CatalogSourceData?): SourceData? - fun catalogSourceDataListToSourceDataList( - catalogSourceType: List? - ): List? + + fun catalogSourceDataListToSourceDataList(catalogSourceType: List?): List? } diff --git a/src/main/java/org/radarbase/management/service/mapper/CatalogSourceTypeMapper.kt b/src/main/java/org/radarbase/management/service/mapper/CatalogSourceTypeMapper.kt index 98a8cfe9b..b2f2657f8 100644 --- a/src/main/java/org/radarbase/management/service/mapper/CatalogSourceTypeMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/CatalogSourceTypeMapper.kt @@ -16,5 +16,6 @@ interface CatalogSourceTypeMapper { @Mapping(target = "projects", ignore = true) @Mapping(target = "canRegisterDynamically", ignore = true) fun catalogSourceTypeToSourceType(catalogSourceType: CatalogSourceType?): SourceType? + fun catalogSourceTypesToSourceTypes(catalogSourceType: List?): List? } diff --git a/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.java b/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.java deleted file mode 100644 index f8a1ddeaa..000000000 --- a/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.radarbase.management.service.mapper; - -import org.mapstruct.DecoratedWith; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.radarbase.management.service.dto.ClientDetailsDTO; -import org.radarbase.management.service.mapper.decorator.ClientDetailsMapperDecorator; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Created by dverbeec on 7/09/2017. - */ -@Mapper(componentModel = "spring", uses = {BaseClientDetails.class}) -@DecoratedWith(ClientDetailsMapperDecorator.class) -public interface ClientDetailsMapper { - - @Mapping(target = "clientSecret", ignore = true) - @Mapping(target = "autoApproveScopes", ignore = true) - ClientDetailsDTO clientDetailsToClientDetailsDTO(ClientDetails details); - - List clientDetailsToClientDetailsDTO(List detailsList); - - BaseClientDetails clientDetailsDTOToClientDetails(ClientDetailsDTO detailsDto); - - List clientDetailsDTOToClientDetails(List detailsDtoList); - - /** - * Map a set of authorities represented as strings to a collection of {@link GrantedAuthority}s. - * - * @param authorities the set of authorities to be mapped - * @return a collection of {@link GrantedAuthority}s - */ - default Collection map(Set authorities) { - if (Objects.isNull(authorities)) { - return Collections.emptySet(); - } - return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); - } - - /** - * Map a collection of authorities represented as {@link GrantedAuthority}s to a set of strings. - * - * @param authorities the collection of {@link GrantedAuthority}s to be mapped - * @return the set of strings - */ - default Set map(Collection authorities) { - if (Objects.isNull(authorities)) { - return Collections.emptySet(); - } - return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); - } - - /** - * Transforms the values in the input map to strings so the result is a - * {@link Map}. - * @param additionalInformation a {@link Map} to be transformed - * @return a new map with the same keys as the input map, but the values are strings - */ - default Map map(Map additionalInformation) { - if (Objects.isNull(additionalInformation)) { - return Collections.emptyMap(); - } - return additionalInformation.entrySet().stream().collect(Collectors.toMap( - Map.Entry::getKey, e -> e.getValue().toString())); - } -} diff --git a/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.kt b/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.kt new file mode 100644 index 000000000..6d818d4eb --- /dev/null +++ b/src/main/java/org/radarbase/management/service/mapper/ClientDetailsMapper.kt @@ -0,0 +1,54 @@ +package org.radarbase.management.service.mapper + +import org.mapstruct.DecoratedWith +import org.mapstruct.Mapper +import org.mapstruct.Mapping +import org.radarbase.management.service.dto.ClientDetailsDTO +import org.radarbase.management.service.mapper.decorator.ClientDetailsMapperDecorator +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.provider.ClientDetails +import org.springframework.security.oauth2.provider.client.BaseClientDetails + +/** + * Created by dverbeec on 7/09/2017. + */ +@Mapper(componentModel = "spring", uses = [BaseClientDetails::class]) +@DecoratedWith(ClientDetailsMapperDecorator::class) +interface ClientDetailsMapper { + @Mapping(target = "clientSecret", ignore = true) + @Mapping(target = "autoApproveScopes", ignore = true) + fun clientDetailsToClientDetailsDTO(details: ClientDetails): ClientDetailsDTO + + fun clientDetailsToClientDetailsDTO(detailsList: List): List + + fun clientDetailsDTOToClientDetails(detailsDto: ClientDetailsDTO): BaseClientDetails + + fun clientDetailsDTOToClientDetails(detailsDtoList: List): List + + /** + * Map a set of authorities represented as strings to a collection of {@link GrantedAuthority}s. + * + * @param authorities the set of authorities to be mapped + * @return a collection of {@link GrantedAuthority}s + */ + fun map(authorities: Set): Collection = authorities.map { SimpleGrantedAuthority(it) } + + /** + * Map a collection of authorities represented as [GrantedAuthority]s to a set of +strings. + * + * @param authorities the collection of [GrantedAuthority]s to be mapped + * @return the set of strings + */ + fun map(authorities: Collection): Set = authorities.map { it.authority }.toSet() + + /** + * Transforms the values in the input map to strings so the result is a + * [Map]. + * + * @param additionalInformation a [Map] to be transformed + * @return a new map with the same keys as the input map, but the values are strings + */ + fun map(additionalInformation: Map): Map = + additionalInformation.map { (key, value) -> key to value.toString() }.toMap() +} diff --git a/src/main/java/org/radarbase/management/service/mapper/package-info.kt b/src/main/java/org/radarbase/management/service/mapper/PackageInfo.kt similarity index 73% rename from src/main/java/org/radarbase/management/service/mapper/package-info.kt rename to src/main/java/org/radarbase/management/service/mapper/PackageInfo.kt index 0f781e947..53a7f4e86 100644 --- a/src/main/java/org/radarbase/management/service/mapper/package-info.kt +++ b/src/main/java/org/radarbase/management/service/mapper/PackageInfo.kt @@ -1,5 +1,6 @@ /** * MapStruct mappers for mapping domain objects and Data Transfer Objects. */ -package org.radarbase.management.service.mapper +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.service.mapper diff --git a/src/main/java/org/radarbase/management/service/mapper/ProjectMapper.kt b/src/main/java/org/radarbase/management/service/mapper/ProjectMapper.kt index 6c0a89aa4..ea720eb75 100644 --- a/src/main/java/org/radarbase/management/service/mapper/ProjectMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/ProjectMapper.kt @@ -16,14 +16,14 @@ import org.radarbase.management.service.mapper.decorator.ProjectMapperDecorator */ @Mapper(componentModel = "spring", uses = [GroupMapper::class, SourceTypeMapper::class, OrganizationMapper::class]) @DecoratedWith( - ProjectMapperDecorator::class + ProjectMapperDecorator::class, ) interface ProjectMapper { @Mapping(target = "humanReadableProjectName", ignore = true) @Mapping( target = "organization", source = "organization", - qualifiedByName = ["organizationToOrganizationDTOWithoutProjects"] + qualifiedByName = ["organizationToOrganizationDTOWithoutProjects"], ) @Mapping(target = "persistentTokenTimeout", ignore = true) @Mapping(target = "groups", qualifiedByName = ["groupToGroupDTO"]) @@ -45,8 +45,11 @@ interface ProjectMapper { @Mapping(target = "groups", ignore = true) @Mapping(target = "organization", ignore = true) fun projectDTOToProject(projectDto: ProjectDTO?): Project? + fun projectDTOsToProjects(projectDtos: List): List + fun projectToMinimalProjectDetailsDTO(project: Project): MinimalProjectDetailsDTO + fun projectsToMinimalProjectDetailsDTOs(projects: List): List fun projectToPublicProjectDTO(project: Project): PublicProjectDTO @@ -63,7 +66,6 @@ interface ProjectMapper { @Mapping(target = "attributes", ignore = true) @Mapping(target = "groups", ignore = true) fun descriptiveDTOToProject(minimalProjectDetailsDto: MinimalProjectDetailsDTO?): Project? - fun descriptiveDTOsToProjects( - minimalProjectDetailsDtos: List - ): List + + fun descriptiveDTOsToProjects(minimalProjectDetailsDtos: List): List } diff --git a/src/main/java/org/radarbase/management/service/mapper/RoleMapper.kt b/src/main/java/org/radarbase/management/service/mapper/RoleMapper.kt index 16606507f..12df8bbb2 100644 --- a/src/main/java/org/radarbase/management/service/mapper/RoleMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/RoleMapper.kt @@ -25,6 +25,8 @@ interface RoleMapper { @Mapping(target = "users", ignore = true) @Mapping(source = "organizationId", target = "organization.id") fun roleDTOToRole(roleDto: RoleDTO?): Role? + fun roleDTOsToRoles(roleDtos: Set): Set + fun rolesToRoleDTOs(roles: Set): Set } diff --git a/src/main/java/org/radarbase/management/service/mapper/SourceDataMapper.kt b/src/main/java/org/radarbase/management/service/mapper/SourceDataMapper.kt index 7413d4a15..cbaf5eaeb 100644 --- a/src/main/java/org/radarbase/management/service/mapper/SourceDataMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/SourceDataMapper.kt @@ -21,7 +21,9 @@ interface SourceDataMapper { @IterableMapping(qualifiedByName = ["sourceDataReducedDTO"]) fun sourceDataToSourceDataDTOs(sourceData: List): List + fun sourceDataDTOToSourceData(sourceDataDto: SourceDataDTO): SourceData + fun sourceDataDTOsToSourceData(sourceDataDtos: List): List /** diff --git a/src/main/java/org/radarbase/management/service/mapper/SourceMapper.kt b/src/main/java/org/radarbase/management/service/mapper/SourceMapper.kt index 097ede263..89d1455df 100644 --- a/src/main/java/org/radarbase/management/service/mapper/SourceMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/SourceMapper.kt @@ -15,7 +15,7 @@ import org.radarbase.management.service.mapper.decorator.SourceMapperDecorator */ @Mapper(componentModel = "spring", uses = [SourceTypeMapper::class, ProjectMapper::class]) @DecoratedWith( - SourceMapperDecorator::class + SourceMapperDecorator::class, ) interface SourceMapper { @Mapping(source = "source.subject.user.login", target = "subjectLogin") @@ -35,6 +35,7 @@ interface SourceMapper { @Mapping(source = "sourceType.catalogVersion", target = "sourceTypeCatalogVersion") @Mapping(source = "assigned", target = "assigned") fun sourceToMinimalSourceDetailsDTO(source: Source): MinimalSourceDetailsDTO + fun sourcesToMinimalSourceDetailsDTOs(sources: List): List @Mapping(target = "sourceType", ignore = true) diff --git a/src/main/java/org/radarbase/management/service/mapper/SourceTypeMapper.kt b/src/main/java/org/radarbase/management/service/mapper/SourceTypeMapper.kt index e32b769e7..33fcd19fb 100644 --- a/src/main/java/org/radarbase/management/service/mapper/SourceTypeMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/SourceTypeMapper.kt @@ -30,11 +30,12 @@ interface SourceTypeMapper { @Mapping(target = "projects", ignore = true) fun sourceTypeDTOToSourceType(sourceTypeDto: SourceTypeDTO): SourceType + fun sourceTypeDTOsToSourceTypes(sourceTypeDtos: List): List + fun sourceTypeToMinimalSourceTypeDetailsDTO(sourceType: SourceType): MinimalSourceTypeDTO - fun sourceTypesToMinimalSourceTypeDetailsDTOs( - sourceTypes: List - ): List + + fun sourceTypesToMinimalSourceTypeDetailsDTOs(sourceTypes: List): List @IterableMapping(qualifiedByName = ["sourceDataReducedDTO"]) fun map(sourceData: Set?): Set? @@ -48,6 +49,7 @@ interface SourceTypeMapper { @Mapping(target = "projects", ignore = true) @Mapping(target = "appProvider", ignore = true) fun minimalDTOToSourceType(minimalSourceTypeDetailsDto: MinimalSourceTypeDTO?): SourceType? + fun minimalDTOsToSourceTypes(minimalProjectDetailsDtos: List?): List? /** diff --git a/src/main/java/org/radarbase/management/service/mapper/SubjectMapper.kt b/src/main/java/org/radarbase/management/service/mapper/SubjectMapper.kt index efeaa0eb1..6ee449b10 100644 --- a/src/main/java/org/radarbase/management/service/mapper/SubjectMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/SubjectMapper.kt @@ -15,10 +15,10 @@ import org.radarbase.management.service.mapper.decorator.SubjectMapperDecorator */ @Mapper( componentModel = "spring", - uses = [UserMapper::class, ProjectMapper::class, SourceMapper::class, RoleMapper::class] + uses = [UserMapper::class, ProjectMapper::class, SourceMapper::class, RoleMapper::class], ) @DecoratedWith( - SubjectMapperDecorator::class + SubjectMapperDecorator::class, ) interface SubjectMapper { @Mapping(source = "user.login", target = "login") @@ -71,7 +71,10 @@ interface SubjectMapper { @Mapping(target = "removed", ignore = true) @Mapping(target = "metaTokens", ignore = true) @Mapping(target = "group", ignore = true) - fun safeUpdateSubjectFromDTO(subjectDto: SubjectDTO?, @MappingTarget subject: Subject?): Subject? + fun safeUpdateSubjectFromDTO( + subjectDto: SubjectDTO?, + @MappingTarget subject: Subject?, + ): Subject? /** * Generating the fromId for all mappers if the databaseType is sql, as the class has diff --git a/src/main/java/org/radarbase/management/service/mapper/UserMapper.kt b/src/main/java/org/radarbase/management/service/mapper/UserMapper.kt index 757c1ff1f..2c7b6b8e1 100644 --- a/src/main/java/org/radarbase/management/service/mapper/UserMapper.kt +++ b/src/main/java/org/radarbase/management/service/mapper/UserMapper.kt @@ -13,7 +13,7 @@ import org.radarbase.management.service.mapper.decorator.UserMapperDecorator */ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = [ProjectMapper::class, RoleMapper::class]) @DecoratedWith( - UserMapperDecorator::class + UserMapperDecorator::class, ) interface UserMapper { @Mapping(target = "createdBy", ignore = true) diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.java b/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.java deleted file mode 100644 index a74d11dab..000000000 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.radarbase.management.service.mapper.decorator; - - -import org.radarbase.management.service.dto.ClientDetailsDTO; -import org.radarbase.management.service.mapper.ClientDetailsMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.oauth2.provider.ClientDetails; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Decorator for ClientDetailsMapper. The ClientDetails interface does not expose a method to get - * all auto-approve scopes, instead it only has a method to check if a given scope is auto-approve. - * This decorator adds the subset of scopes for which isAutoApprove returns true to the DTO. - */ -public abstract class ClientDetailsMapperDecorator implements ClientDetailsMapper { - - @Autowired - @Qualifier("delegate") - private ClientDetailsMapper delegate; - - @Override - public ClientDetailsDTO clientDetailsToClientDetailsDTO(ClientDetails details) { - ClientDetailsDTO clientDetailsDto = delegate.clientDetailsToClientDetailsDTO(details); - // collect the scopes that are auto-approve and set them in our DTO - clientDetailsDto.setAutoApproveScopes(details - .getScope() - .stream() - .filter(details::isAutoApprove).collect(Collectors.toSet())); - return clientDetailsDto; - } - - @Override - public List clientDetailsToClientDetailsDTO(List details) { - if (Objects.isNull(details)) { - return null; - } - return details.stream().map(this::clientDetailsToClientDetailsDTO).toList(); - } -} diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.kt new file mode 100644 index 000000000..5f901ac3d --- /dev/null +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/ClientDetailsMapperDecorator.kt @@ -0,0 +1,32 @@ +package org.radarbase.management.service.mapper.decorator + +import org.radarbase.management.service.dto.ClientDetailsDTO +import org.radarbase.management.service.mapper.ClientDetailsMapper +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.security.oauth2.provider.ClientDetails + +/** + * Decorator for ClientDetailsMapper. The ClientDetails interface does not expose a method to get + * all auto-approve scopes, instead it only has a method to check if a given scope is auto-approve. + * This decorator adds the subset of scopes for which isAutoApprove returns true to the DTO. + */ +abstract class ClientDetailsMapperDecorator : ClientDetailsMapper { + @Autowired + @Qualifier("delegate") + private val delegate: ClientDetailsMapper? = null + + override fun clientDetailsToClientDetailsDTO(details: ClientDetails): ClientDetailsDTO { + val clientDetailsDto = delegate!!.clientDetailsToClientDetailsDTO(details) + // collect the scopes that are auto-approve and set them in our DTO + clientDetailsDto.autoApproveScopes = + details + .scope + .filter { details.isAutoApprove(it) } + .toSet() + return clientDetailsDto + } + + override fun clientDetailsToClientDetailsDTO(details: List): List = + details.map(this::clientDetailsToClientDetailsDTO) +} diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/ProjectMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/ProjectMapperDecorator.kt index 3fae78093..50059810b 100644 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/ProjectMapperDecorator.kt +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/ProjectMapperDecorator.kt @@ -19,11 +19,18 @@ import java.util.* * Created by nivethika on 30-8-17. */ abstract class ProjectMapperDecorator : ProjectMapper { + @Autowired + @Qualifier("delegate") + private lateinit var delegate: ProjectMapper - @Autowired @Qualifier("delegate") private lateinit var delegate: ProjectMapper - @Autowired private lateinit var organizationRepository: OrganizationRepository - @Autowired private lateinit var projectRepository: ProjectRepository - @Autowired private lateinit var metaTokenService: MetaTokenService + @Autowired + private lateinit var organizationRepository: OrganizationRepository + + @Autowired + private lateinit var projectRepository: ProjectRepository + + @Autowired + private lateinit var metaTokenService: MetaTokenService override fun projectToProjectDTO(project: Project?): ProjectDTO? { val dto = delegate.projectToProjectDTO(project) @@ -58,21 +65,23 @@ abstract class ProjectMapperDecorator : ProjectMapper { val name = projectDto.organizationName if (name != null && projectDto.organization != null) { - val org = organizationRepository.findOneByName(name) - ?: throw NotFoundException( + val org = + organizationRepository.findOneByName(name) + ?: throw NotFoundException( "Organization not found with name", EntityName.ORGANIZATION, ErrorConstants.ERR_ORGANIZATION_NAME_NOT_FOUND, - Collections.singletonMap("name", name) + Collections.singletonMap("name", name), ) project!!.organization = org } return project } - override fun descriptiveDTOToProject(minimalProjectDetailsDto: MinimalProjectDetailsDTO?): Project? { - return if (minimalProjectDetailsDto == null) { + override fun descriptiveDTOToProject(minimalProjectDetailsDto: MinimalProjectDetailsDTO?): Project? = + if (minimalProjectDetailsDto == null) { null - } else minimalProjectDetailsDto.id?.let { projectRepository.getById(it) } - } + } else { + minimalProjectDetailsDto.id?.let { projectRepository.getById(it) } + } } diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/RoleMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/RoleMapperDecorator.kt index ad28dad59..aa7764b56 100644 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/RoleMapperDecorator.kt +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/RoleMapperDecorator.kt @@ -10,11 +10,14 @@ import org.springframework.beans.factory.annotation.Qualifier /** * Created by nivethika on 03-8-18. */ -abstract class RoleMapperDecorator() : RoleMapper { +abstract class RoleMapperDecorator : RoleMapper { + // constructor(roleMapper: RoleMapper, authorityRepository: AuthorityRepository?) : this(roleMapper) + @Autowired + @Qualifier("delegate") + private val delegate: RoleMapper? = null -// constructor(roleMapper: RoleMapper, authorityRepository: AuthorityRepository?) : this(roleMapper) - @Autowired @Qualifier("delegate") private val delegate: RoleMapper? = null - @Autowired private var authorityRepository: AuthorityRepository? = null; + @Autowired + private var authorityRepository: AuthorityRepository? = null /** * Overrides standard RoleMapperImpl and loads authority from repository if not specified. diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/SourceMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/SourceMapperDecorator.kt index c19a17760..7b1aa1840 100644 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/SourceMapperDecorator.kt +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/SourceMapperDecorator.kt @@ -15,20 +15,26 @@ import org.springframework.beans.factory.annotation.Qualifier /** * Created by nivethika on 13-6-17. */ -abstract class SourceMapperDecorator() : SourceMapper { +abstract class SourceMapperDecorator : SourceMapper { + @Autowired + @Qualifier("delegate") + private val delegate: SourceMapper? = null - @Autowired @Qualifier("delegate") private val delegate: SourceMapper? = null - @Autowired private val sourceRepository: SourceRepository? = null - @Autowired private val subjectRepository: SubjectRepository? = null + @Autowired + private val sourceRepository: SourceRepository? = null + + @Autowired + private val subjectRepository: SubjectRepository? = null override fun minimalSourceDTOToSource(minimalSourceDetailsDto: MinimalSourceDetailsDTO): Source? { - val source = sourceRepository - ?.findOneBySourceId(minimalSourceDetailsDto.sourceId) - ?: throw - NotFoundException( + val source = + sourceRepository + ?.findOneBySourceId(minimalSourceDetailsDto.sourceId) + ?: throw NotFoundException( "Source ID " + minimalSourceDetailsDto.sourceId + " not found", - EntityName.Companion.SOURCE, ErrorConstants.ERR_SOURCE_NOT_FOUND, - mapOf(Pair("sourceId", minimalSourceDetailsDto.sourceId.toString())) + EntityName.Companion.SOURCE, + ErrorConstants.ERR_SOURCE_NOT_FOUND, + mapOf(Pair("sourceId", minimalSourceDetailsDto.sourceId.toString())), ) source.assigned = minimalSourceDetailsDto.isAssigned return source @@ -37,16 +43,19 @@ abstract class SourceMapperDecorator() : SourceMapper { override fun sourceDTOToSource(sourceDto: SourceDTO): Source { val source = delegate?.sourceDTOToSource(sourceDto) if (sourceDto.id != null) { - val existingSource = sourceDto.id?.let { - sourceRepository?.findById(it) - ?.orElseThrow { - NotFoundException( - "Source ID " + sourceDto.id + " not found", - EntityName.Companion.SOURCE, ErrorConstants.ERR_SOURCE_NOT_FOUND, - mapOf(Pair("sourceId", sourceDto.id.toString())) - ) - } - }!! + val existingSource = + sourceDto.id?.let { + sourceRepository + ?.findById(it) + ?.orElseThrow { + NotFoundException( + "Source ID " + sourceDto.id + " not found", + EntityName.Companion.SOURCE, + ErrorConstants.ERR_SOURCE_NOT_FOUND, + mapOf(Pair("sourceId", sourceDto.id.toString())), + ) + } + }!! if (sourceDto.subjectLogin == null) { source?.subject = existingSource.subject } else { diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/SubjectMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/SubjectMapperDecorator.kt index 9c8d86acb..be77f3794 100644 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/SubjectMapperDecorator.kt +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/SubjectMapperDecorator.kt @@ -19,20 +19,31 @@ import org.springframework.beans.factory.annotation.Qualifier /** * Created by nivethika on 30-8-17. */ -abstract class SubjectMapperDecorator() : SubjectMapper { +abstract class SubjectMapperDecorator : SubjectMapper { + @Autowired + @Qualifier("delegate") + private val delegate: SubjectMapper? = null + + @Autowired + private var groupRepository: GroupRepository? = null + + @Autowired + private var projectRepository: ProjectRepository? = null + + @Autowired + private var revisionService: RevisionService? = null + + @Autowired + private var projectMapper: ProjectMapper? = null - @Autowired @Qualifier("delegate") private val delegate: SubjectMapper? = null - @Autowired private var groupRepository: GroupRepository? = null - @Autowired private var projectRepository: ProjectRepository? = null - @Autowired private var revisionService: RevisionService? = null - @Autowired private var projectMapper: ProjectMapper? = null override fun subjectToSubjectDTO(subject: Subject?): SubjectDTO? { if (subject == null) { return null } val dto = subjectToSubjectWithoutProjectDTO(subject) - val project = subject.activeProject - .let { p -> projectRepository?.findOneWithEagerRelationships(p?.id!!) } + val project = + subject.activeProject + .let { p -> projectRepository?.findOneWithEagerRelationships(p?.id!!) } dto?.project = projectMapper?.projectToProjectDTO(project) addAuditInfo(subject, dto) return dto @@ -48,7 +59,10 @@ abstract class SubjectMapperDecorator() : SubjectMapper { return dto } - private fun addAuditInfo(subject: Subject, dto: SubjectDTO?) { + private fun addAuditInfo( + subject: Subject, + dto: SubjectDTO?, + ) { val auditInfo = revisionService?.getAuditInfo(subject) dto!!.createdDate = auditInfo?.createdAt dto.createdBy = auditInfo?.createdBy @@ -75,31 +89,37 @@ abstract class SubjectMapperDecorator() : SubjectMapper { return subject } - private fun getGroup(subjectDto: SubjectDTO?): Group? { - return if (subjectDto!!.group == null) { + private fun getGroup(subjectDto: SubjectDTO?): Group? = + if (subjectDto!!.group == null) { null } else if (subjectDto.project?.id != null) { groupRepository?.findByProjectIdAndName(subjectDto.project?.id, subjectDto.group) ?: throw BadRequestException( - "Group " + subjectDto.group + " not found in project " - + subjectDto.project?.id, - EntityName.SUBJECT, ErrorConstants.ERR_GROUP_NOT_FOUND) + "Group " + subjectDto.group + " not found in project " + + subjectDto.project?.id, + EntityName.SUBJECT, + ErrorConstants.ERR_GROUP_NOT_FOUND, + ) } else if (subjectDto.project?.projectName != null) { - groupRepository?.findByProjectNameAndName(subjectDto.project?.projectName, subjectDto.group) - ?: throw BadRequestException( - "Group " + subjectDto.group + " not found in project " - + subjectDto.project?.projectName, - EntityName.SUBJECT, ErrorConstants.ERR_GROUP_NOT_FOUND + groupRepository?.findByProjectNameAndName(subjectDto.project?.projectName, subjectDto.group) + ?: throw BadRequestException( + "Group " + subjectDto.group + " not found in project " + + subjectDto.project?.projectName, + EntityName.SUBJECT, + ErrorConstants.ERR_GROUP_NOT_FOUND, ) } else { throw BadRequestException( "Group " + subjectDto.group + " cannot be found without a project", - EntityName.SUBJECT, ErrorConstants.ERR_GROUP_NOT_FOUND + EntityName.SUBJECT, + ErrorConstants.ERR_GROUP_NOT_FOUND, ) } - } - override fun safeUpdateSubjectFromDTO(subjectDto: SubjectDTO?, @MappingTarget subject: Subject?): Subject? { + override fun safeUpdateSubjectFromDTO( + subjectDto: SubjectDTO?, + @MappingTarget subject: Subject?, + ): Subject? { val subjectRetrieved = delegate?.safeUpdateSubjectFromDTO(subjectDto, subject) setSubjectStatus(subjectDto, subjectRetrieved) subject!!.group = getGroup(subjectDto) @@ -117,7 +137,10 @@ abstract class SubjectMapperDecorator() : SubjectMapper { return SubjectStatus.INVALID } - private fun setSubjectStatus(subjectDto: SubjectDTO?, subject: Subject?) { + private fun setSubjectStatus( + subjectDto: SubjectDTO?, + subject: Subject?, + ) { when (subjectDto!!.status) { SubjectStatus.DEACTIVATED -> { subject!!.user!!.activated = false diff --git a/src/main/java/org/radarbase/management/service/mapper/decorator/UserMapperDecorator.kt b/src/main/java/org/radarbase/management/service/mapper/decorator/UserMapperDecorator.kt index a6cc665ed..ffc8ec32f 100644 --- a/src/main/java/org/radarbase/management/service/mapper/decorator/UserMapperDecorator.kt +++ b/src/main/java/org/radarbase/management/service/mapper/decorator/UserMapperDecorator.kt @@ -7,10 +7,13 @@ import org.radarbase.management.service.mapper.UserMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Qualifier -abstract class UserMapperDecorator( -) : UserMapper { - @Autowired @Qualifier("delegate") private lateinit var delegate: UserMapper - @Autowired private lateinit var revisionService: RevisionService +abstract class UserMapperDecorator : UserMapper { + @Autowired + @Qualifier("delegate") + private lateinit var delegate: UserMapper + + @Autowired + private lateinit var revisionService: RevisionService override fun userToUserDTO(user: User?): UserDTO? { if (user == null) { diff --git a/src/main/java/org/radarbase/management/web/rest/AccountResource.kt b/src/main/java/org/radarbase/management/web/rest/AccountResource.kt index ffb423f28..ffc83c1bf 100644 --- a/src/main/java/org/radarbase/management/web/rest/AccountResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/AccountResource.kt @@ -46,9 +46,8 @@ class AccountResource( @Autowired private val userMapper: UserMapper, @Autowired private val managementPortalProperties: ManagementPortalProperties, @Autowired private val authService: AuthService, - @Autowired private val passwordService: PasswordService + @Autowired private val passwordService: PasswordService, ) { - @Autowired(required = false) var token: RadarToken? = null @@ -61,14 +60,15 @@ class AccountResource( */ @GetMapping("/activate") @Timed - fun activateAccount(@RequestParam(value = "key") key: String): ResponseEntity { - return try { + fun activateAccount( + @RequestParam(value = "key") key: String, + ): ResponseEntity = + try { userService.activateRegistration(key) ResponseEntity(HttpStatus.OK) } catch (e: Exception) { ResponseEntity(HttpStatus.NOT_FOUND) } - } /** * POST /login : check if the user is authenticated. @@ -113,10 +113,13 @@ class AccountResource( * (Internal Server Error) if the user couldn't be returned */ get() { - val currentUser = userService.getUserWithAuthorities() - ?: throw RadarWebApplicationException( + val currentUser = + userService.getUserWithAuthorities() + ?: throw RadarWebApplicationException( HttpStatus.FORBIDDEN, - "Cannot get account without user", EntityName.Companion.USER, ErrorConstants.ERR_ACCESS_DENIED + "Cannot get account without user", + EntityName.Companion.USER, + ErrorConstants.ERR_ACCESS_DENIED, ) val userDto = userMapper.userToUserDTO(currentUser) if (managementPortalProperties.account.enableExposeToken) { @@ -137,13 +140,17 @@ class AccountResource( @Throws(NotAuthorizedException::class) suspend fun saveAccount( @RequestBody @Valid userDto: UserDTO, - authentication: Authentication + authentication: Authentication, ): ResponseEntity { authService.checkPermission(Permission.USER_UPDATE, { e: EntityDetails -> - e.user(userDto.login) }) + e.user(userDto.login) + }) userService.updateUser( - authentication.name, userDto.firstName, - userDto.lastName, userDto.email, userDto.langKey + authentication.name, + userDto.firstName, + userDto.lastName, + userDto.email, + userDto.langKey, ) return ResponseEntity(HttpStatus.NO_CONTENT) } @@ -157,7 +164,9 @@ class AccountResource( */ @PostMapping(path = ["/account/change_password"], produces = [MediaType.TEXT_PLAIN_VALUE]) @Timed - fun changePassword(@RequestBody password: String): ResponseEntity { + fun changePassword( + @RequestBody password: String, + ): ResponseEntity { passwordService.checkPasswordStrength(password) userService.changePassword(password) return ResponseEntity(HttpStatus.NO_CONTENT) @@ -173,16 +182,22 @@ class AccountResource( */ @PostMapping(path = ["/account/reset-activation/init"]) @Timed - fun requestActivationReset(@RequestBody login: String): ResponseEntity { - val user = userService.requestActivationReset(login) - ?: throw BadRequestException( + fun requestActivationReset( + @RequestBody login: String, + ): ResponseEntity { + val user = + userService.requestActivationReset(login) + ?: throw BadRequestException( "Cannot find a deactivated user with login $login", - EntityName.Companion.USER, ErrorConstants.ERR_EMAIL_NOT_REGISTERED + EntityName.Companion.USER, + ErrorConstants.ERR_EMAIL_NOT_REGISTERED, ) mailService.sendCreationEmail( - user, managementPortalProperties.common - .activationKeyTimeoutInSeconds.toLong() + user, + managementPortalProperties.common + .activationKeyTimeoutInSeconds + .toLong(), ) return ResponseEntity(HttpStatus.NO_CONTENT) } @@ -196,11 +211,15 @@ class AccountResource( */ @PostMapping(path = ["/account/reset_password/init"]) @Timed - fun requestPasswordReset(@RequestBody mail: String): ResponseEntity { - val user = userService.requestPasswordReset(mail) - ?: throw BadRequestException( + fun requestPasswordReset( + @RequestBody mail: String, + ): ResponseEntity { + val user = + userService.requestPasswordReset(mail) + ?: throw BadRequestException( "email address not registered", - EntityName.Companion.USER, ErrorConstants.ERR_EMAIL_NOT_REGISTERED + EntityName.Companion.USER, + ErrorConstants.ERR_EMAIL_NOT_REGISTERED, ) mailService.sendPasswordResetMail(user) return ResponseEntity(HttpStatus.NO_CONTENT) @@ -216,7 +235,7 @@ class AccountResource( @PostMapping(path = ["/account/reset_password/finish"], produces = [MediaType.TEXT_PLAIN_VALUE]) @Timed fun finishPasswordReset( - @RequestBody keyAndPassword: KeyAndPasswordVM + @RequestBody keyAndPassword: KeyAndPasswordVM, ): ResponseEntity { passwordService.checkPasswordStrength(keyAndPassword.newPassword) userService.completePasswordReset(keyAndPassword.newPassword, keyAndPassword.key) diff --git a/src/main/java/org/radarbase/management/web/rest/AuditResource.kt b/src/main/java/org/radarbase/management/web/rest/AuditResource.kt index cd7ca7502..0eb68d9b4 100644 --- a/src/main/java/org/radarbase/management/web/rest/AuditResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/AuditResource.kt @@ -23,7 +23,10 @@ import java.time.LocalDate */ @RestController @RequestMapping("/management/audits") -class AuditResource(private val auditEventService: AuditEventService, private val authService: AuthService) { +class AuditResource( + private val auditEventService: AuditEventService, + private val authService: AuthService, +) { /** * GET /audits : get a page of AuditEvents. * @@ -32,7 +35,9 @@ class AuditResource(private val auditEventService: AuditEventService, private va */ @GetMapping @Throws(NotAuthorizedException::class) - fun getAll(@Parameter pageable: Pageable): ResponseEntity> { + fun getAll( + @Parameter pageable: Pageable, + ): ResponseEntity> { authService.checkPermission(Permission.AUDIT_READ) val page = auditEventService.findAll(pageable) val headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits") @@ -52,11 +57,12 @@ class AuditResource(private val auditEventService: AuditEventService, private va fun getByDates( @RequestParam(value = "fromDate") fromDate: LocalDate, @RequestParam(value = "toDate") toDate: LocalDate, - @Parameter pageable: Pageable? + @Parameter pageable: Pageable?, ): ResponseEntity> { authService.checkPermission(Permission.AUDIT_READ) - val page = auditEventService - .findByDates(fromDate.atTime(0, 0), toDate.atTime(23, 59), pageable) + val page = + auditEventService + .findByDates(fromDate.atTime(0, 0), toDate.atTime(23, 59), pageable) val headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits") return ResponseEntity(page.content, headers, HttpStatus.OK) } @@ -70,7 +76,9 @@ class AuditResource(private val auditEventService: AuditEventService, private va */ @GetMapping("/{id:.+}") @Throws(NotAuthorizedException::class) - operator fun get(@PathVariable id: Long): ResponseEntity { + operator fun get( + @PathVariable id: Long, + ): ResponseEntity { authService.checkPermission(Permission.AUDIT_READ) return ResponseUtil.wrapOrNotFound(auditEventService.find(id)) } diff --git a/src/main/java/org/radarbase/management/web/rest/AuthorityResource.kt b/src/main/java/org/radarbase/management/web/rest/AuthorityResource.kt index 231e4395b..c0f75a159 100644 --- a/src/main/java/org/radarbase/management/web/rest/AuthorityResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/AuthorityResource.kt @@ -39,19 +39,19 @@ class AuthorityResource { companion object { private val log = LoggerFactory.getLogger(AuthorityResource::class.java) - private val ALL_AUTHORITIES = Stream.of( - RoleAuthority.SYS_ADMIN, - RoleAuthority.ORGANIZATION_ADMIN, - RoleAuthority.PROJECT_ADMIN, - RoleAuthority.PROJECT_OWNER, - RoleAuthority.PROJECT_AFFILIATE, - RoleAuthority.PROJECT_ANALYST - ) - .map { role: RoleAuthority? -> - AuthorityDTO( - role!! - ) - } - .toList() + private val ALL_AUTHORITIES = + Stream + .of( + RoleAuthority.SYS_ADMIN, + RoleAuthority.ORGANIZATION_ADMIN, + RoleAuthority.PROJECT_ADMIN, + RoleAuthority.PROJECT_OWNER, + RoleAuthority.PROJECT_AFFILIATE, + RoleAuthority.PROJECT_ANALYST, + ).map { role: RoleAuthority? -> + AuthorityDTO( + role!!, + ) + }.toList() } } diff --git a/src/main/java/org/radarbase/management/web/rest/GroupResource.kt b/src/main/java/org/radarbase/management/web/rest/GroupResource.kt index 709eb1476..ade749e2a 100644 --- a/src/main/java/org/radarbase/management/web/rest/GroupResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/GroupResource.kt @@ -38,9 +38,8 @@ import javax.validation.Valid @RequestMapping("/api/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}/groups") class GroupResource( @Autowired private val groupService: GroupService, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * Create group. * @param projectName project name @@ -52,15 +51,18 @@ class GroupResource( @Throws(NotAuthorizedException::class) fun createGroup( @PathVariable projectName: String?, - @RequestBody @Valid groupDto: GroupDTO? + @RequestBody @Valid groupDto: GroupDTO?, ): ResponseEntity { authService.checkPermission(Permission.PROJECT_UPDATE, { e: EntityDetails -> e.project(projectName) }) val groupDtoResult = groupService.createGroup(projectName!!, groupDto!!) - val location = MvcUriComponentsBuilder.fromController(javaClass) - .path("/{groupName}") - .buildAndExpand(projectName, groupDtoResult.name) - .toUri() - return ResponseEntity.created(location) + val location = + MvcUriComponentsBuilder + .fromController(javaClass) + .path("/{groupName}") + .buildAndExpand(projectName, groupDtoResult.name) + .toUri() + return ResponseEntity + .created(location) .body(groupDtoResult) } @@ -73,7 +75,7 @@ class GroupResource( @GetMapping @Throws(NotAuthorizedException::class) fun listGroups( - @PathVariable projectName: String? + @PathVariable projectName: String?, ): List { authService.checkPermission(Permission.PROJECT_READ, { e: EntityDetails -> e.project(projectName) }) return groupService.listGroups(projectName!!) @@ -88,11 +90,11 @@ class GroupResource( */ @GetMapping("/{groupName:" + Constants.ENTITY_ID_REGEX + "}") @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getGroup( @PathVariable projectName: String?, - @PathVariable groupName: String? + @PathVariable groupName: String?, ): GroupDTO { authService.checkPermission(Permission.PROJECT_READ, { e: EntityDetails -> e.project(projectName) }) return groupService.getGroup(projectName!!, groupName!!) @@ -106,12 +108,12 @@ class GroupResource( */ @DeleteMapping("/{groupName:" + Constants.ENTITY_ID_REGEX + "}") @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun deleteGroup( @RequestParam(defaultValue = "false") unlinkSubjects: Boolean?, @PathVariable projectName: String?, - @PathVariable groupName: String? + @PathVariable groupName: String?, ): ResponseEntity<*> { authService.checkPermission(Permission.PROJECT_UPDATE, { e: EntityDetails -> e.project(projectName) }) groupService.deleteGroup(projectName!!, groupName!!, unlinkSubjects!!) @@ -127,12 +129,12 @@ class GroupResource( */ @PatchMapping("/{groupName:" + Constants.ENTITY_ID_REGEX + "}/subjects") @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun changeGroupSubjects( @PathVariable projectName: String?, @PathVariable groupName: String?, - @RequestBody patchOperations: List + @RequestBody patchOperations: List, ): ResponseEntity<*> { // Technically, this request modifies subjects, // so it would make sense to check permissions per subject, @@ -147,11 +149,17 @@ class GroupResource( "remove" -> operation.value?.let { removedItems.addAll(it) } else -> throw BadRequestException( "Group patch operation '$opCode' is not supported", - EntityName.Companion.GROUP, ErrorConstants.ERR_VALIDATION + EntityName.Companion.GROUP, + ErrorConstants.ERR_VALIDATION, ) } } - groupService.updateGroupSubjects(projectName!!, groupName!!, addedItems.filterNotNull(), removedItems.filterNotNull()) + groupService.updateGroupSubjects( + projectName!!, + groupName!!, + addedItems.filterNotNull(), + removedItems.filterNotNull(), + ) return ResponseEntity.noContent().build() } } diff --git a/src/main/java/org/radarbase/management/web/rest/LoginEndpoint.kt b/src/main/java/org/radarbase/management/web/rest/LoginEndpoint.kt index 12017d8ad..8852c813b 100644 --- a/src/main/java/org/radarbase/management/web/rest/LoginEndpoint.kt +++ b/src/main/java/org/radarbase/management/web/rest/LoginEndpoint.kt @@ -19,7 +19,7 @@ class LoginEndpoint fun loginRedirect(): RedirectView { val redirectView = RedirectView() redirectView.url = managementPortalProperties.identityServer.loginUrl + - "/login?return_to=" + managementPortalProperties.common.managementPortalBaseUrl + "/login?return_to=" + managementPortalProperties.common.managementPortalBaseUrl return redirectView } diff --git a/src/main/java/org/radarbase/management/web/rest/LogsResource.kt b/src/main/java/org/radarbase/management/web/rest/LogsResource.kt index d5d8a93ad..8497bb6a1 100644 --- a/src/main/java/org/radarbase/management/web/rest/LogsResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/LogsResource.kt @@ -31,8 +31,19 @@ class LogsResource { * @return the logger configurations */ get() { + val noopLogMessages = + listOf( + LoggerVM().apply { + name = "During testing there is no logback impl. dependency present" + level = "DEBUG" + }, + ) + if (LoggerFactory.getILoggerFactory() !is LoggerContext) { + return noopLogMessages + } val context = LoggerFactory.getILoggerFactory() as LoggerContext - return context.getLoggerList() + return context + .getLoggerList() .stream() .map { logger: Logger -> LoggerVM(logger) } .toList() @@ -46,8 +57,12 @@ class LogsResource { @ResponseStatus(HttpStatus.NO_CONTENT) @Timed @Secured(RoleAuthority.SYS_ADMIN_AUTHORITY) - fun changeLevel(@RequestBody jsonLogger: LoggerVM) { - val context = LoggerFactory.getILoggerFactory() as LoggerContext - context.getLogger(jsonLogger.name).setLevel(Level.valueOf(jsonLogger.level)) + fun changeLevel( + @RequestBody jsonLogger: LoggerVM, + ) { + if (LoggerFactory.getILoggerFactory() is LoggerContext) { + val context = LoggerFactory.getILoggerFactory() as LoggerContext + context.getLogger(jsonLogger.name).setLevel(Level.valueOf(jsonLogger.level)) + } } } diff --git a/src/main/java/org/radarbase/management/web/rest/MetaTokenResource.kt b/src/main/java/org/radarbase/management/web/rest/MetaTokenResource.kt index bbf0a0b2b..786539ff3 100644 --- a/src/main/java/org/radarbase/management/web/rest/MetaTokenResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/MetaTokenResource.kt @@ -8,7 +8,6 @@ import org.radarbase.management.security.NotAuthorizedException import org.radarbase.management.service.AuthService import org.radarbase.management.service.MetaTokenService import org.radarbase.management.service.dto.TokenDTO -import org.radarbase.management.web.rest.OAuthClientsResource import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity @@ -41,9 +40,11 @@ class MetaTokenResource { @GetMapping("/meta-token/{tokenName:" + Constants.TOKEN_NAME_REGEX + "}") @Timed @Throws( - MalformedURLException::class + MalformedURLException::class, ) - fun getTokenByTokenName(@PathVariable("tokenName") tokenName: String?): ResponseEntity { + fun getTokenByTokenName( + @PathVariable("tokenName") tokenName: String?, + ): ResponseEntity { log.info("Requesting token with tokenName {}", tokenName) return ResponseEntity.ok().body(tokenName?.let { metaTokenService!!.fetchToken(it) }) } @@ -60,31 +61,36 @@ class MetaTokenResource { @DeleteMapping("/meta-token/{tokenName:" + Constants.TOKEN_NAME_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteTokenByTokenName(@PathVariable("tokenName") tokenName: String?): ResponseEntity { + fun deleteTokenByTokenName( + @PathVariable("tokenName") tokenName: String?, + ): ResponseEntity { log.info("Requesting token with tokenName {}", tokenName) val metaToken = tokenName?.let { metaTokenService!!.getToken(it) } val subject = metaToken?.subject - val project: String = subject!! - .activeProject - ?.projectName - ?: - throw NotAuthorizedException( - "Cannot establish authority of subject without active project affiliation." - ) + val project: String = + subject!! + .activeProject + ?.projectName + ?: throw NotAuthorizedException( + "Cannot establish authority of subject without active project affiliation.", + ) val user = subject.user!!.login authService!!.checkPermission( Permission.SUBJECT_UPDATE, - { e: EntityDetails -> e.project(project).subject(user) }) + { e: EntityDetails -> e.project(project).subject(user) }, + ) metaTokenService?.delete(metaToken) return ResponseEntity.noContent().build() } companion object { private val log = LoggerFactory.getLogger(OAuthClientsResource::class.java) + @JvmField val DEFAULT_META_TOKEN_TIMEOUT = Duration.ofHours(1) + @JvmField val DEFAULT_PERSISTENT_META_TOKEN_TIMEOUT = Duration.ofDays(31) } diff --git a/src/main/java/org/radarbase/management/web/rest/OAuthClientsResource.kt b/src/main/java/org/radarbase/management/web/rest/OAuthClientsResource.kt index ef7417585..7d4240586 100644 --- a/src/main/java/org/radarbase/management/web/rest/OAuthClientsResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/OAuthClientsResource.kt @@ -50,12 +50,8 @@ class OAuthClientsResource( @Autowired private val subjectService: SubjectService, @Autowired private val userService: UserService, @Autowired private val eventRepository: AuditEventRepository, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - - @Throws(NotAuthorizedException::class) - @Timed - @GetMapping("/oauth-clients") /** * GET /api/oauth-clients. * @@ -64,6 +60,9 @@ class OAuthClientsResource( * * @return the list of registered clients as a list of [ClientDetailsDTO] */ + @Throws(NotAuthorizedException::class) + @Timed + @GetMapping("/oauth-clients") fun oAuthClients(): ResponseEntity> { authService.checkScope(Permission.OAUTHCLIENTS_READ) val clients = clientDetailsMapper.clientDetailsToClientDetailsDTO(oAuthClientService.findAllOAuthClients()) @@ -82,9 +81,11 @@ class OAuthClientsResource( @GetMapping("/oauth-clients/{id:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getOAuthClientById(@PathVariable("id") id: String?): ResponseEntity { + fun getOAuthClientById( + @PathVariable("id") id: String?, + ): ResponseEntity { authService.checkPermission(Permission.OAUTHCLIENTS_READ) val client = oAuthClientService.findOneByClientId(id) @@ -106,19 +107,21 @@ class OAuthClientsResource( @PutMapping("/oauth-clients") @Timed @Throws(NotAuthorizedException::class) - fun updateOAuthClient(@RequestBody @Valid clientDetailsDto: ClientDetailsDTO?): ResponseEntity { + fun updateOAuthClient( + @RequestBody @Valid clientDetailsDto: ClientDetailsDTO?, + ): ResponseEntity { authService.checkPermission(Permission.OAUTHCLIENTS_UPDATE) // getOAuthClient checks if the id exists OAuthClientService.checkProtected(oAuthClientService.findOneByClientId(clientDetailsDto!!.clientId)) val updated = oAuthClientService.updateOauthClient(clientDetailsDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers( HeaderUtil.createEntityUpdateAlert( EntityName.OAUTH_CLIENT, - clientDetailsDto.clientId - ) - ) - .body(clientDetailsMapper.clientDetailsToClientDetailsDTO(updated)) + clientDetailsDto.clientId, + ), + ).body(clientDetailsMapper.clientDetailsToClientDetailsDTO(updated)) } /** @@ -133,14 +136,18 @@ class OAuthClientsResource( @DeleteMapping("/oauth-clients/{id:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteOAuthClient(@PathVariable id: String?): ResponseEntity { + fun deleteOAuthClient( + @PathVariable id: String?, + ): ResponseEntity { authService.checkPermission(Permission.OAUTHCLIENTS_DELETE) // getOAuthClient checks if the id exists OAuthClientService.checkProtected(oAuthClientService.findOneByClientId(id)) oAuthClientService.deleteClientDetails(id) - return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(EntityName.OAUTH_CLIENT, id)) + return ResponseEntity + .ok() + .headers(HeaderUtil.createEntityDeletionAlert(EntityName.OAUTH_CLIENT, id)) .build() } @@ -157,10 +164,13 @@ class OAuthClientsResource( @PostMapping("/oauth-clients") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createOAuthClient(@RequestBody clientDetailsDto: @Valid ClientDetailsDTO): ResponseEntity { + fun createOAuthClient( + @RequestBody clientDetailsDto: @Valid ClientDetailsDTO, + ): ResponseEntity { authService.checkPermission(Permission.OAUTHCLIENTS_CREATE) val created = oAuthClientService.createClientDetail(clientDetailsDto) - return ResponseEntity.created(ResourceUriService.getUri(clientDetailsDto)) + return ResponseEntity + .created(ResourceUriService.getUri(clientDetailsDto)) .headers(HeaderUtil.createEntityCreationAlert(EntityName.OAUTH_CLIENT, created.clientId)) .body(clientDetailsMapper.clientDetailsToClientDetailsDTO(created)) } @@ -183,40 +193,48 @@ class OAuthClientsResource( fun getRefreshToken( @RequestParam login: String, @RequestParam(value = "clientId") clientId: String, - @RequestParam(value = "persistent", defaultValue = "false") persistent: Boolean? + @RequestParam(value = "persistent", defaultValue = "false") persistent: Boolean?, ): ResponseEntity { authService.checkScope(Permission.SUBJECT_UPDATE) val currentUser = userService.getUserWithAuthorities() // We only allow this for actual logged in users for now, not for client_credentials ?: throw AccessDeniedException( - "You must be a logged in user to access this resource" - ) + "You must be a logged in user to access this resource", + ) // lookup the subject val subject = subjectService.findOneByLogin(login) - val projectName: String = subject.activeProject - ?.projectName - ?: throw NotFoundException( - "Project for subject $login not found", EntityName.SUBJECT, - ErrorConstants.ERR_SUBJECT_NOT_FOUND + val projectName: String = + subject.activeProject + ?.projectName + ?: throw NotFoundException( + "Project for subject $login not found", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, ) - // Users who can update a subject can also generate a refresh token for that subject authService.checkPermission( Permission.SUBJECT_UPDATE, - { e: EntityDetails -> e.project(projectName).subject(login) }) + { e: EntityDetails -> e.project(projectName).subject(login) }, + ) val cpi = metaTokenService.createMetaToken(subject, clientId, persistent!!) // generate audit event eventRepository.add( AuditEvent( - currentUser.login, "PAIR_CLIENT_REQUEST", - "client_id=$clientId", "subject_login=$login" - ) + currentUser.login, + "PAIR_CLIENT_REQUEST", + "client_id=$clientId", + "subject_login=$login", + ), ) log.info( - "[{}] by {}: client_id={}, subject_login={}", "PAIR_CLIENT_REQUEST", currentUser - .login, clientId, login + "[{}] by {}: client_id={}, subject_login={}", + "PAIR_CLIENT_REQUEST", + currentUser + .login, + clientId, + login, ) return ResponseEntity(cpi, HttpStatus.OK) } diff --git a/src/main/java/org/radarbase/management/web/rest/OrganizationResource.kt b/src/main/java/org/radarbase/management/web/rest/OrganizationResource.kt index ea2acdbe1..ea5f461f8 100644 --- a/src/main/java/org/radarbase/management/web/rest/OrganizationResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/OrganizationResource.kt @@ -36,9 +36,9 @@ import javax.validation.Valid @RestController @RequestMapping("/api") class OrganizationResource( - @Autowired private val organizationService: OrganizationService, @Autowired private val authService: AuthService + @Autowired private val organizationService: OrganizationService, + @Autowired private val authService: AuthService, ) { - /** * POST /organizations : Create a new organization. * @@ -52,7 +52,7 @@ class OrganizationResource( @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) fun createOrganization( - @RequestBody @Valid organizationDto: OrganizationDTO? + @RequestBody @Valid organizationDto: OrganizationDTO?, ): ResponseEntity { log.debug("REST request to save Organization : {}", organizationDto) authService.checkPermission(Permission.ORGANIZATION_CREATE) @@ -71,7 +71,7 @@ class OrganizationResource( return result?.let { getUri(it) }?.let { ResponseEntity.created(it).headers(createEntityCreationAlert(ENTITY_NAME, result.name)).body(result) } - //TODO handle better + // TODO handle better ?: ResponseEntity.badRequest().body(null) } @@ -105,7 +105,7 @@ class OrganizationResource( @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) fun updateOrganization( - @RequestBody @Valid organizationDto: OrganizationDTO? + @RequestBody @Valid organizationDto: OrganizationDTO?, ): ResponseEntity { log.debug("REST request to update Organization : {}", organizationDto) if (organizationDto!!.id == null) { @@ -128,20 +128,21 @@ class OrganizationResource( @GetMapping("/organizations/{name:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getOrganization( - @PathVariable name: String + @PathVariable name: String, ): ResponseEntity { log.debug("REST request to get Organization : {}", name) authService.checkPermission(Permission.ORGANIZATION_READ, { e: EntityDetails -> e.organization(name) }) val org = organizationService.findByName(name) - val dto = org ?: throw NotFoundException( - "Organization not found with name $name", - EntityName.ORGANIZATION, - ErrorConstants.ERR_ORGANIZATION_NAME_NOT_FOUND, - Collections.singletonMap("name", name) - ) + val dto = + org ?: throw NotFoundException( + "Organization not found with name $name", + EntityName.ORGANIZATION, + ErrorConstants.ERR_ORGANIZATION_NAME_NOT_FOUND, + Collections.singletonMap("name", name), + ) return ResponseEntity.ok(dto) } @@ -157,10 +158,10 @@ class OrganizationResource( @GetMapping("/organizations/{name:" + Constants.ENTITY_ID_REGEX + "}/projects") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getOrganizationProjects( - @PathVariable name: String? + @PathVariable name: String?, ): ResponseEntity> { log.debug("REST request to get Projects of the Organization : {}", name) authService.checkPermission(Permission.PROJECT_READ, { e: EntityDetails -> e.organization(name) }) diff --git a/src/main/java/org/radarbase/management/web/rest/package-info.kt b/src/main/java/org/radarbase/management/web/rest/PackageInfo.kt similarity index 63% rename from src/main/java/org/radarbase/management/web/rest/package-info.kt rename to src/main/java/org/radarbase/management/web/rest/PackageInfo.kt index 3dfc41d29..8fd7f94f3 100644 --- a/src/main/java/org/radarbase/management/web/rest/package-info.kt +++ b/src/main/java/org/radarbase/management/web/rest/PackageInfo.kt @@ -1,5 +1,6 @@ /** * Spring MVC REST controllers. */ -package org.radarbase.management.web.rest +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.web.rest diff --git a/src/main/java/org/radarbase/management/web/rest/ProfileInfoResource.java b/src/main/java/org/radarbase/management/web/rest/ProfileInfoResource.java index 694fdfac2..c056ed4a1 100644 --- a/src/main/java/org/radarbase/management/web/rest/ProfileInfoResource.java +++ b/src/main/java/org/radarbase/management/web/rest/ProfileInfoResource.java @@ -20,6 +20,7 @@ public class ProfileInfoResource { /** * Get profile info. + * * @return profile info. */ @GetMapping("/profile-info") diff --git a/src/main/java/org/radarbase/management/web/rest/ProjectResource.kt b/src/main/java/org/radarbase/management/web/rest/ProjectResource.kt index 73ebf0f1e..3ad4f76b1 100644 --- a/src/main/java/org/radarbase/management/web/rest/ProjectResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/ProjectResource.kt @@ -60,9 +60,8 @@ class ProjectResource( @Autowired private val roleService: RoleService, @Autowired private val subjectService: SubjectService, @Autowired private val sourceService: SourceService, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /projects : Create a new project. * @@ -74,43 +73,52 @@ class ProjectResource( @PostMapping("/projects") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createProject(@RequestBody @Valid projectDto: ProjectDTO?): ResponseEntity { + fun createProject( + @RequestBody @Valid projectDto: ProjectDTO?, + ): ResponseEntity { log.debug("REST request to save Project : {}", projectDto) - val org = projectDto?.organizationName - ?: throw BadRequestException( - "Organization must be provided", - ENTITY_NAME, ErrorConstants.ERR_VALIDATION - ) + val org = + projectDto?.organizationName + ?: throw BadRequestException( + "Organization must be provided", + ENTITY_NAME, + ErrorConstants.ERR_VALIDATION, + ) authService.checkPermission( Permission.PROJECT_CREATE, - { e: EntityDetails -> e.organization(org) }) + { e: EntityDetails -> e.organization(org) }, + ) if (projectDto.id != null) { - return ResponseEntity.badRequest() + return ResponseEntity + .badRequest() .headers( createFailureAlert( - ENTITY_NAME, "idexists", "A new project cannot already have an ID" - ) - ) - .body(null) + ENTITY_NAME, + "idexists", + "A new project cannot already have an ID", + ), + ).body(null) } if (projectRepository.findOneWithEagerRelationshipsByName(projectDto.projectName) != null) { - return ResponseEntity.badRequest() + return ResponseEntity + .badRequest() .headers( createFailureAlert( - ENTITY_NAME, "nameexists", "A project with this name already exists" - ) - ) - .body(null) + ENTITY_NAME, + "nameexists", + "A project with this name already exists", + ), + ).body(null) } val result = projectService.save(projectDto) - return ResponseEntity.created(ResourceUriService.getUri(result)) + return ResponseEntity + .created(ResourceUriService.getUri(result)) .headers( createEntityCreationAlert( ENTITY_NAME, - result.projectName - ) - ) - .body(result) + result.projectName, + ), + ).body(result) } /** @@ -125,7 +133,10 @@ class ProjectResource( @PutMapping("/projects") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun updateProject(@RequestBody @Valid projectDto: ProjectDTO?): ResponseEntity {log.debug("REST request to update Project : {}", projectDto) + fun updateProject( + @RequestBody @Valid projectDto: ProjectDTO?, + ): ResponseEntity { + log.debug("REST request to update Project : {}", projectDto) if (projectDto?.id == null) { return createProject(projectDto) } @@ -135,7 +146,8 @@ class ProjectResource( if (org?.name == null) { throw BadRequestException( "Organization must be provided", - ENTITY_NAME, ErrorConstants.ERR_VALIDATION + ENTITY_NAME, + ErrorConstants.ERR_VALIDATION, ) } // When clients want to transfer a project, @@ -143,8 +155,9 @@ class ProjectResource( val existingProject = projectService.findOne(projectDto.id!!) if (existingProject?.projectName != projectDto.projectName) { throw BadRequestException( - "The project name cannot be modified.", ENTITY_NAME, - ErrorConstants.ERR_VALIDATION + "The project name cannot be modified.", + ENTITY_NAME, + ErrorConstants.ERR_VALIDATION, ) } val newOrgName = org.name @@ -153,22 +166,25 @@ class ProjectResource( { e: EntityDetails -> e.organization(newOrgName) e.project(existingProject?.projectName) - }) + }, + ) val oldOrgName = existingProject?.organizationName if (newOrgName != oldOrgName) { authService.checkPermission( Permission.PROJECT_UPDATE, - { e: EntityDetails -> e.organization(oldOrgName) }) + { e: EntityDetails -> e.organization(oldOrgName) }, + ) authService.checkPermission( Permission.PROJECT_UPDATE, - { e: EntityDetails -> e.organization(newOrgName) }) + { e: EntityDetails -> e.organization(newOrgName) }, + ) } val result = projectService.save(projectDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers( - createEntityUpdateAlert(ENTITY_NAME, projectDto.projectName) - ) - .body(result) + createEntityUpdateAlert(ENTITY_NAME, projectDto.projectName), + ).body(result) } /** @@ -181,13 +197,14 @@ class ProjectResource( @Throws(NotAuthorizedException::class) fun getAllProjects( @PageableDefault(size = Int.MAX_VALUE) pageable: Pageable, - @RequestParam(name = "minimized", required = false, defaultValue = "false") minimized: Boolean + @RequestParam(name = "minimized", required = false, defaultValue = "false") minimized: Boolean, ): ResponseEntity<*> { log.debug("REST request to get Projects") authService.checkScope(Permission.PROJECT_READ) val page = projectService.findAll(minimized, pageable) - val headers = PaginationUtil - .generatePaginationHttpHeaders(page, "/api/projects") + val headers = + PaginationUtil + .generatePaginationHttpHeaders(page, "/api/projects") return ResponseEntity(page.content, headers, HttpStatus.OK) } @@ -201,9 +218,11 @@ class ProjectResource( @GetMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getProject(@PathVariable projectName: String?): ResponseEntity { + fun getProject( + @PathVariable projectName: String?, + ): ResponseEntity { authService.checkScope(Permission.PROJECT_READ) log.debug("REST request to get Project : {}", projectName) val projectDto = projectService.findOneByName(projectName!!) @@ -224,9 +243,11 @@ class ProjectResource( @GetMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}/source-types") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getSourceTypesOfProject(@PathVariable projectName: String?): List { + fun getSourceTypesOfProject( + @PathVariable projectName: String?, + ): List { authService.checkScope(Permission.PROJECT_READ) log.debug("REST request to get Project : {}", projectName) val projectDto = projectService.findOneByName(projectName!!) @@ -246,9 +267,11 @@ class ProjectResource( @DeleteMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteProject(@PathVariable projectName: String?): ResponseEntity<*> { + fun deleteProject( + @PathVariable projectName: String?, + ): ResponseEntity<*> { authService.checkScope(Permission.PROJECT_DELETE) log.debug("REST request to delete Project : {}", projectName) val projectDto = projectService.findOneByName(projectName!!) @@ -258,11 +281,13 @@ class ProjectResource( }) return try { projectService.delete(projectDto.id!!) - ResponseEntity.ok() + ResponseEntity + .ok() .headers(createEntityDeletionAlert(ENTITY_NAME, projectName)) .build() } catch (ex: DataIntegrityViolationException) { - ResponseEntity.badRequest() + ResponseEntity + .badRequest() .body(ErrorVM(ErrorConstants.ERR_PROJECT_NOT_EMPTY, ex.message)) } } @@ -275,9 +300,11 @@ class ProjectResource( @GetMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}/roles") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getRolesByProject(@PathVariable projectName: String?): ResponseEntity> { + fun getRolesByProject( + @PathVariable projectName: String?, + ): ResponseEntity> { authService.checkScope(Permission.ROLE_READ) log.debug("REST request to get all Roles for project {}", projectName) val projectDto = projectService.findOneByName(projectName!!) @@ -296,17 +323,17 @@ class ProjectResource( @GetMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}/sources") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getAllSourcesForProject( @Parameter pageable: Pageable, @PathVariable projectName: String, @RequestParam(value = "assigned", required = false) assigned: Boolean?, - @RequestParam(name = "minimized", required = false, defaultValue = "false") minimized: Boolean + @RequestParam(name = "minimized", required = false, defaultValue = "false") minimized: Boolean, ): ResponseEntity<*> { authService.checkScope(Permission.SOURCE_READ) log.debug("REST request to get all Sources") - val projectDto = projectService.findOneByName(projectName) //?: throw NoSuchElementException() + val projectDto = projectService.findOneByName(projectName) // ?: throw NoSuchElementException() authService.checkPermission(Permission.SOURCE_READ, { e: EntityDetails -> e.organization(projectDto.organizationName) @@ -317,37 +344,48 @@ class ProjectResource( ResponseEntity.ok( sourceService .findAllMinimalSourceDetailsByProjectAndAssigned( - projectDto.id, assigned - ) + projectDto.id, + assigned, + ), ) } else { ResponseEntity.ok( sourceService - .findAllByProjectAndAssigned(projectDto.id, assigned) + .findAllByProjectAndAssigned(projectDto.id, assigned), ) } } else { if (minimized) { - val page = sourceService - .findAllMinimalSourceDetailsByProject(projectDto.id!!, pageable) - val headers = PaginationUtil - .generatePaginationHttpHeaders( - page, buildPath( - "api", - "projects", projectName, "sources" + val page = + sourceService + .findAllMinimalSourceDetailsByProject(projectDto.id!!, pageable) + val headers = + PaginationUtil + .generatePaginationHttpHeaders( + page, + buildPath( + "api", + "projects", + projectName, + "sources", + ), ) - ) ResponseEntity(page.content, headers, HttpStatus.OK) } else { - val page = sourceService - .findAllByProjectId(projectDto.id!!, pageable) - val headers = PaginationUtil - .generatePaginationHttpHeaders( - page, buildPath( - "api", - "projects", projectName, "sources" + val page = + sourceService + .findAllByProjectId(projectDto.id!!, pageable) + val headers = + PaginationUtil + .generatePaginationHttpHeaders( + page, + buildPath( + "api", + "projects", + projectName, + "sources", + ), ) - ) ResponseEntity(page.content, headers, HttpStatus.OK) } } @@ -361,14 +399,13 @@ class ProjectResource( @GetMapping("/projects/{projectName:" + Constants.ENTITY_ID_REGEX + "}/subjects") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getAllSubjects( - @Valid subjectCriteria: SubjectCriteria? + @Valid subjectCriteria: SubjectCriteria?, ): ResponseEntity> { authService.checkScope(Permission.SUBJECT_READ) - val projectName = subjectCriteria!!.projectName ?: throw NoSuchElementException() // this checks if the project exists val projectDto = projectName.let { projectService.findOneByName(it) } @@ -380,18 +417,23 @@ class ProjectResource( // this checks if the project exists projectService.findOneByName(projectName) - subjectCriteria.projectName = projectName log.debug( - "REST request to get all subjects for project {} using criteria {}", projectName, - subjectCriteria + "REST request to get all subjects for project {} using criteria {}", + projectName, + subjectCriteria, ) - val page = subjectService.findAll(subjectCriteria) - .map { subject: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(subject) } + val page = + subjectService + .findAll(subjectCriteria) + .map { subject: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(subject) } val baseUri = buildPath("api", "projects", projectName, "subjects") - val headers = PaginationUtil.generateSubjectPaginationHttpHeaders( - page, baseUri, subjectCriteria - ) + val headers = + PaginationUtil.generateSubjectPaginationHttpHeaders( + page, + baseUri, + subjectCriteria, + ) return ResponseEntity(page.content.filterNotNull(), headers, HttpStatus.OK) } diff --git a/src/main/java/org/radarbase/management/web/rest/PublicResource.kt b/src/main/java/org/radarbase/management/web/rest/PublicResource.kt index 7fcdf6ea2..f9762ee06 100644 --- a/src/main/java/org/radarbase/management/web/rest/PublicResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/PublicResource.kt @@ -16,9 +16,8 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/public") class PublicResource( - @Autowired private val projectService: ProjectService + @Autowired private val projectService: ProjectService, ) { - /** * GET /public/projects : get all the projects for public endpoint. * @@ -31,8 +30,9 @@ class PublicResource( ): ResponseEntity<*> { log.debug("REST request to get Projects for public endpoint") val page = projectService.getPublicProjects(pageable) - val headers = PaginationUtil - .generatePaginationHttpHeaders(page, "/api/public/projects") + val headers = + PaginationUtil + .generatePaginationHttpHeaders(page, "/api/public/projects") return ResponseEntity(page.content, headers, HttpStatus.OK) } diff --git a/src/main/java/org/radarbase/management/web/rest/RevisionResource.kt b/src/main/java/org/radarbase/management/web/rest/RevisionResource.kt index 235c2f6fe..c472dfec3 100644 --- a/src/main/java/org/radarbase/management/web/rest/RevisionResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/RevisionResource.kt @@ -33,7 +33,7 @@ class RevisionResource { @Timed @Secured(RoleAuthority.SYS_ADMIN_AUTHORITY) fun getRevisions( - @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable? + @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable?, ): ResponseEntity> { log.debug("REST request to get page of revisions") val page = revisionService!!.getRevisions(pageable!!) @@ -49,7 +49,9 @@ class RevisionResource { @GetMapping("/revisions/{id}") @Timed @Secured(RoleAuthority.SYS_ADMIN_AUTHORITY) - fun getRevision(@PathVariable("id") id: Int): ResponseEntity { + fun getRevision( + @PathVariable("id") id: Int, + ): ResponseEntity { log.debug("REST request to get single revision: {}", id.toString()) return ResponseEntity.ok(revisionService!!.getRevision(id)) } diff --git a/src/main/java/org/radarbase/management/web/rest/RoleResource.kt b/src/main/java/org/radarbase/management/web/rest/RoleResource.kt index 930f5dcf1..7a6bc919b 100644 --- a/src/main/java/org/radarbase/management/web/rest/RoleResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/RoleResource.kt @@ -34,9 +34,8 @@ import javax.validation.Valid @RequestMapping("/api") class RoleResource( @Autowired private val roleService: RoleService, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /Roles : Create a new role. * @@ -49,23 +48,30 @@ class RoleResource( @Timed @Secured(RoleAuthority.SYS_ADMIN_AUTHORITY) @Throws( - URISyntaxException::class, NotAuthorizedException::class + URISyntaxException::class, + NotAuthorizedException::class, ) - fun createRole(@RequestBody @Valid roleDto: RoleDTO): ResponseEntity { + fun createRole( + @RequestBody @Valid roleDto: RoleDTO, + ): ResponseEntity { log.debug("REST request to save Role : {}", roleDto) authService.checkPermission(Permission.ROLE_CREATE, { e: EntityDetails -> e.project = roleDto.projectName }) if (roleDto.id != null) { - return ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, - "idexists", "A new role cannot already have an ID" - ) - ).body(null) + return ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "idexists", + "A new role cannot already have an ID", + ), + ).body(null) } val result = roleService.save(roleDto) - return ResponseEntity.created(ResourceUriService.getUri(result)) + return ResponseEntity + .created(ResourceUriService.getUri(result)) .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, displayName(result))) .body(result) } @@ -83,9 +89,12 @@ class RoleResource( @Timed @Secured(RoleAuthority.SYS_ADMIN_AUTHORITY) @Throws( - URISyntaxException::class, NotAuthorizedException::class + URISyntaxException::class, + NotAuthorizedException::class, ) - fun updateRole(@RequestBody @Valid roleDto: RoleDTO): ResponseEntity { + fun updateRole( + @RequestBody @Valid roleDto: RoleDTO, + ): ResponseEntity { log.debug("REST request to update Role : {}", roleDto) if (roleDto.id == null) { return createRole(roleDto) @@ -94,7 +103,8 @@ class RoleResource( e.project = roleDto.projectName }) val result = roleService.save(roleDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, displayName(roleDto))) .body(result) } @@ -138,21 +148,21 @@ class RoleResource( * (Not Found) */ @GetMapping( - "/roles/{projectName:" + Constants.ENTITY_ID_REGEX + "}/{authorityName:" - + Constants.ENTITY_ID_REGEX + "}" + "/roles/{projectName:" + Constants.ENTITY_ID_REGEX + "}/{authorityName:" + + Constants.ENTITY_ID_REGEX + "}", ) @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getRole( @PathVariable projectName: String?, - @PathVariable authorityName: String? + @PathVariable authorityName: String?, ): ResponseEntity { log.debug("REST request to get all Roles") authService.checkPermission(Permission.ROLE_READ, { e: EntityDetails -> e.project(projectName) }) return ResponseUtil.wrapOrNotFound( - Optional.ofNullable(roleService.findOneByProjectNameAndAuthorityName(projectName, authorityName)) + Optional.ofNullable(roleService.findOneByProjectNameAndAuthorityName(projectName, authorityName)), ) } @@ -163,9 +173,7 @@ class RoleResource( * @param role The role to create a user-friendly display for * @return the display name */ - private fun displayName(role: RoleDTO?): String { - return role?.projectName + ": " + role?.authorityName - } + private fun displayName(role: RoleDTO?): String = role?.projectName + ": " + role?.authorityName companion object { private val log = LoggerFactory.getLogger(RoleResource::class.java) diff --git a/src/main/java/org/radarbase/management/web/rest/SessionResource.kt b/src/main/java/org/radarbase/management/web/rest/SessionResource.kt index 2d292d942..5c6ba5edf 100644 --- a/src/main/java/org/radarbase/management/web/rest/SessionResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SessionResource.kt @@ -1,13 +1,10 @@ package org.radarbase.management.web.rest -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json import io.micrometer.core.annotation.Timed import kotlinx.serialization.json.Json import org.radarbase.auth.exception.IdpException @@ -23,32 +20,36 @@ import org.springframework.web.bind.annotation.RestController import java.time.Duration import javax.servlet.http.HttpServletRequest - /** * REST controller for managing Sessions. */ @RestController @RequestMapping("/api") -class SessionResource(managementPortalProperties: ManagementPortalProperties) { +class SessionResource( + managementPortalProperties: ManagementPortalProperties, +) { private lateinit var sessionService: SessionService init { - sessionService = SessionService(managementPortalProperties.identityServer.publicUrl()) + sessionService = SessionService(managementPortalProperties.identityServer.publicUrl() ?: "undefined") } - private val httpClient = HttpClient(CIO).config { - install(HttpTimeout) { - connectTimeoutMillis = Duration.ofSeconds(10).toMillis() - socketTimeoutMillis = Duration.ofSeconds(10).toMillis() - requestTimeoutMillis = Duration.ofSeconds(300).toMillis() - } - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - coerceInputValues = true - }) + private val httpClient = + HttpClient(CIO).config { + install(HttpTimeout) { + connectTimeoutMillis = Duration.ofSeconds(10).toMillis() + socketTimeoutMillis = Duration.ofSeconds(10).toMillis() + requestTimeoutMillis = Duration.ofSeconds(300).toMillis() + } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + coerceInputValues = true + }, + ) + } } - } /** * GET /logout-url : Gets a kratos logout url for the current session. @@ -59,8 +60,11 @@ class SessionResource(managementPortalProperties: ManagementPortalProperties) { @Timed @Throws(IdpException::class) suspend fun getLogoutUrl(httpRequest: HttpServletRequest): ResponseEntity { - val sessionToken = HeaderUtil.parseCookies(httpRequest.getHeader(HttpHeaders.COOKIE)).find { it.name == "ory_kratos_session" } - ?.value + val sessionToken = + HeaderUtil + .parseCookies(httpRequest.getHeader(HttpHeaders.COOKIE)) + .find { it.name == "ory_kratos_session" } + ?.value return try { sessionToken ?: throw IdpException("no ory_kratos_session could be parsed from the headers") @@ -69,12 +73,17 @@ class SessionResource(managementPortalProperties: ManagementPortalProperties) { .ok() .body(sessionService.getLogoutUrl(sessionToken)) } catch (e: Throwable) { - ResponseEntity.badRequest() - .headers(e.message?.let { - HeaderUtil.createFailureAlert("NoSession", - it, "could not create logout url") - }) - .body("") + ResponseEntity + .badRequest() + .headers( + e.message?.let { + HeaderUtil.createFailureAlert( + "NoSession", + it, + "could not create logout url", + ) + }, + ).body("") } } diff --git a/src/main/java/org/radarbase/management/web/rest/SiteSettingsResource.kt b/src/main/java/org/radarbase/management/web/rest/SiteSettingsResource.kt index 9e5c96883..b422ea8b6 100644 --- a/src/main/java/org/radarbase/management/web/rest/SiteSettingsResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SiteSettingsResource.kt @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api") class SiteSettingsResource( - @Autowired private val siteSettingsService: SiteSettingsService + @Autowired private val siteSettingsService: SiteSettingsService, ) { @get:Timed @get:GetMapping("/sitesettings") diff --git a/src/main/java/org/radarbase/management/web/rest/SourceDataResource.kt b/src/main/java/org/radarbase/management/web/rest/SourceDataResource.kt index f982fec0e..8aed9a733 100644 --- a/src/main/java/org/radarbase/management/web/rest/SourceDataResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SourceDataResource.kt @@ -41,9 +41,8 @@ import javax.validation.Valid @RequestMapping("/api") class SourceDataResource( @Autowired private val sourceDataService: SourceDataService, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /source-data : Create a new sourceData. * @@ -55,27 +54,34 @@ class SourceDataResource( @PostMapping("/source-data") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createSourceData(@RequestBody @Valid sourceDataDto: SourceDataDTO): ResponseEntity { + fun createSourceData( + @RequestBody @Valid sourceDataDto: SourceDataDTO, + ): ResponseEntity { log.debug("REST request to save SourceData : {}", sourceDataDto) authService.checkPermission(Permission.SOURCEDATA_CREATE) if (sourceDataDto.id != null) { - return ResponseEntity.badRequest().headers( - createFailureAlert( - EntityName.SOURCE_DATA, - "idexists", "A new sourceData cannot already have an ID" - ) - ).build() + return ResponseEntity + .badRequest() + .headers( + createFailureAlert( + EntityName.SOURCE_DATA, + "idexists", + "A new sourceData cannot already have an ID", + ), + ).build() } val name = sourceDataDto.sourceDataName if (sourceDataService.findOneBySourceDataName(name) != null) { throw ConflictException( "SourceData already available with source-name", - EntityName.SOURCE_DATA, "error.sourceDataNameAvailable", - Collections.singletonMap("sourceDataName", name) + EntityName.SOURCE_DATA, + "error.sourceDataNameAvailable", + Collections.singletonMap("sourceDataName", name), ) } val result = sourceDataService.save(sourceDataDto) - return ResponseEntity.created(getUri(sourceDataDto)) + return ResponseEntity + .created(getUri(sourceDataDto)) .headers(createEntityCreationAlert(EntityName.SOURCE_DATA, name)) .body(result) } @@ -92,14 +98,17 @@ class SourceDataResource( @PutMapping("/source-data") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun updateSourceData(@RequestBody @Valid sourceDataDto: SourceDataDTO?): ResponseEntity { + fun updateSourceData( + @RequestBody @Valid sourceDataDto: SourceDataDTO?, + ): ResponseEntity { log.debug("REST request to update SourceData : {}", sourceDataDto) if (sourceDataDto!!.id == null) { return createSourceData(sourceDataDto) } authService.checkPermission(Permission.SOURCEDATA_UPDATE) val result = sourceDataService.save(sourceDataDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(createEntityUpdateAlert(EntityName.SOURCE_DATA, sourceDataDto.sourceDataName)) .body(result) } @@ -114,7 +123,7 @@ class SourceDataResource( @Timed @Throws(NotAuthorizedException::class) fun getAllSourceData( - @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable + @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable, ): ResponseEntity> { log.debug("REST request to get all SourceData") authService.checkScope(Permission.SOURCEDATA_READ) @@ -133,15 +142,17 @@ class SourceDataResource( @GetMapping("/source-data/{sourceDataName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getSourceData(@PathVariable sourceDataName: String?): ResponseEntity { + fun getSourceData( + @PathVariable sourceDataName: String?, + ): ResponseEntity { authService.checkScope(Permission.SOURCEDATA_READ) return ResponseUtil.wrapOrNotFound( Optional.ofNullable( sourceDataService - .findOneBySourceDataName(sourceDataName) - ) + .findOneBySourceDataName(sourceDataName), + ), ) } @@ -154,12 +165,15 @@ class SourceDataResource( @DeleteMapping("/source-data/{sourceDataName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteSourceData(@PathVariable sourceDataName: String?): ResponseEntity { + fun deleteSourceData( + @PathVariable sourceDataName: String?, + ): ResponseEntity { authService.checkPermission(Permission.SOURCEDATA_DELETE) - val sourceDataDto = sourceDataService - .findOneBySourceDataName(sourceDataName) ?: return ResponseEntity.notFound().build() + val sourceDataDto = + sourceDataService + .findOneBySourceDataName(sourceDataName) ?: return ResponseEntity.notFound().build() sourceDataService.delete(sourceDataDto.id) return ResponseEntity.ok().headers(createEntityDeletionAlert(EntityName.SOURCE_DATA, sourceDataName)).build() } diff --git a/src/main/java/org/radarbase/management/web/rest/SourceResource.kt b/src/main/java/org/radarbase/management/web/rest/SourceResource.kt index bcfcad5d0..b8c009aa5 100644 --- a/src/main/java/org/radarbase/management/web/rest/SourceResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SourceResource.kt @@ -41,9 +41,8 @@ import javax.validation.Valid class SourceResource( @Autowired private val sourceService: SourceService, @Autowired private val sourceRepository: SourceRepository, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /sources : Create a new source. * @@ -55,7 +54,9 @@ class SourceResource( @PostMapping("/sources") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createSource(@RequestBody @Valid sourceDto: SourceDTO?): ResponseEntity { + fun createSource( + @RequestBody @Valid sourceDto: SourceDTO?, + ): ResponseEntity { log.debug("REST request to save Source : {}", sourceDto) val project = sourceDto!!.project authService.checkPermission(Permission.SOURCE_CREATE, { e: EntityDetails -> @@ -65,35 +66,54 @@ class SourceResource( }) return if (sourceDto.id != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, "idexists", "A new source cannot already have an ID" - ) - ).build() - } else if (sourceDto.sourceId != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, "sourceIdExists", "A new source cannot already have a Source ID" - ) - ).build() + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "idexists", + "A new source cannot already have an ID", + ), + ).build() + } else if (sourceDto.sourceId != null) { + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "sourceIdExists", + "A new source cannot already have a Source ID", + ), + ).build() } else if (sourceRepository.findOneBySourceName(sourceDto.sourceName) != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, "sourceNameExists", "Source name already in use" - ) - ).build() + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "sourceNameExists", + "Source name already in use", + ), + ).build() } else if (sourceDto.assigned == null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, "sourceAssignedRequired", "A new source must have the 'assigned' field specified" - ) - ).body(null) + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "sourceAssignedRequired", + "A new source must have the 'assigned' field specified", + ), + ).body(null) } else { val result = sourceService.save(sourceDto) - ResponseEntity.created(ResourceUriService.getUri(result)).headers( + ResponseEntity + .created(ResourceUriService.getUri(result)) + .headers( HeaderUtil.createEntityCreationAlert( - ENTITY_NAME, result.sourceName - ) + ENTITY_NAME, + result.sourceName, + ), ).body(result) } } @@ -110,7 +130,9 @@ class SourceResource( @PutMapping("/sources") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun updateSource(@RequestBody @Valid sourceDto: SourceDTO): ResponseEntity { + fun updateSource( + @RequestBody @Valid sourceDto: SourceDTO, + ): ResponseEntity { log.debug("REST request to update Source : {}", sourceDto) if (sourceDto.id == null) { return createSource(sourceDto) @@ -122,11 +144,13 @@ class SourceResource( } }) val updatedSource: SourceDTO? = sourceService.updateSource(sourceDto) - return ResponseEntity.ok().headers( - HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, sourceDto.sourceName) - ).body( - updatedSource - ) + return ResponseEntity + .ok() + .headers( + HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, sourceDto.sourceName), + ).body( + updatedSource, + ) } /** @@ -138,7 +162,7 @@ class SourceResource( @Timed @Throws(NotAuthorizedException::class) fun getAllSources( - @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable? + @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable?, ): ResponseEntity> { authService.checkPermission(Permission.SUBJECT_READ) log.debug("REST request to get all Sources") @@ -157,9 +181,11 @@ class SourceResource( @GetMapping("/sources/{sourceName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getSource(@PathVariable sourceName: String): ResponseEntity { + fun getSource( + @PathVariable sourceName: String, + ): ResponseEntity { log.debug("REST request to get Source : {}", sourceName) authService.checkScope(Permission.SOURCE_READ) val source = sourceService.findOneByName(sourceName) @@ -183,44 +209,60 @@ class SourceResource( @DeleteMapping("/sources/{sourceName:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteSource(@PathVariable sourceName: String): ResponseEntity { + fun deleteSource( + @PathVariable sourceName: String, + ): ResponseEntity { log.debug("REST request to delete Source : {}", sourceName) authService.checkScope(Permission.SOURCE_DELETE) - val sourceDto = sourceService.findOneByName(sourceName) - ?: return ResponseEntity.notFound().build() + val sourceDto = + sourceService.findOneByName(sourceName) + ?: return ResponseEntity.notFound().build() authService.checkPermission(Permission.SOURCE_DELETE, { e: EntityDetails -> if (sourceDto.project != null) { - e.project(sourceDto.project!!.projectName); + e.project(sourceDto.project!!.projectName) } - e.subject(sourceDto.subjectLogin) + e + .subject(sourceDto.subjectLogin) .source(sourceDto.sourceName) }) if (sourceDto.assigned == true) { - return ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - ENTITY_NAME, "sourceIsAssigned", "Cannot delete an assigned source" - ) - ).build() + return ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "sourceIsAssigned", + "Cannot delete an assigned source", + ), + ).build() } val sourceId = sourceDto.id val sourceHistory = sourceId?.let { sourceRepository.findRevisions(it) } val sources = - sourceHistory?.mapNotNull { obj: Revision -> obj.entity }?.filter { it.assigned == true } + sourceHistory + ?.mapNotNull { obj: Revision -> obj.entity } + ?.filter { it.assigned == true } ?.toList() if (sources?.isNotEmpty() == true) { - val failureAlert = HeaderUtil.createFailureAlert( - ENTITY_NAME, "sourceRevisionIsAssigned", "Cannot delete a previously assigned source" - ) + val failureAlert = + HeaderUtil.createFailureAlert( + ENTITY_NAME, + "sourceRevisionIsAssigned", + "Cannot delete a previously assigned source", + ) return ResponseEntity.status(HttpStatus.CONFLICT).headers(failureAlert).build() } sourceId?.let { sourceService.delete(it) } - return ResponseEntity.ok().headers( - HeaderUtil.createEntityDeletionAlert( - ENTITY_NAME, sourceName - ) - ).build() + return ResponseEntity + .ok() + .headers( + HeaderUtil.createEntityDeletionAlert( + ENTITY_NAME, + sourceName, + ), + ).build() } companion object { diff --git a/src/main/java/org/radarbase/management/web/rest/SourceTypeResource.kt b/src/main/java/org/radarbase/management/web/rest/SourceTypeResource.kt index 1cb524223..e39ebacb5 100644 --- a/src/main/java/org/radarbase/management/web/rest/SourceTypeResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SourceTypeResource.kt @@ -45,9 +45,8 @@ import javax.validation.Valid class SourceTypeResource( @Autowired private val sourceTypeService: SourceTypeService, @Autowired private val sourceTypeRepository: SourceTypeRepository, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /source-types : Create a new sourceType. * @@ -59,37 +58,49 @@ class SourceTypeResource( @PostMapping("/source-types") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createSourceType(@RequestBody @Valid sourceTypeDto: SourceTypeDTO?): ResponseEntity { + fun createSourceType( + @RequestBody @Valid sourceTypeDto: SourceTypeDTO?, + ): ResponseEntity { log.debug("REST request to save SourceType : {}", sourceTypeDto) authService.checkPermission(Permission.SOURCETYPE_CREATE) if (sourceTypeDto!!.id != null) { - return ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - EntityName.SOURCE_TYPE, - "idexists", "A new sourceType cannot already have an ID" - ) - ).build() + return ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + EntityName.SOURCE_TYPE, + "idexists", + "A new sourceType cannot already have an ID", + ), + ).build() } - val existing: SourceType? = sourceTypeRepository - .findOneWithEagerRelationshipsByProducerAndModelAndVersion( - sourceTypeDto.producer!!, sourceTypeDto.model!!, - sourceTypeDto.catalogVersion!! - ) + val existing: SourceType? = + sourceTypeRepository + .findOneWithEagerRelationshipsByProducerAndModelAndVersion( + sourceTypeDto.producer!!, + sourceTypeDto.model!!, + sourceTypeDto.catalogVersion!!, + ) if (existing != null) { val errorParams: MutableMap = HashMap() - errorParams["message"] = ("A SourceType with the specified producer, model and " - + "version already exists. This combination needs to be unique.") + errorParams["message"] = ( + "A SourceType with the specified producer, model and " + + "version already exists. This combination needs to be unique." + ) errorParams["producer"] = sourceTypeDto.producer errorParams["model"] = sourceTypeDto.model errorParams["catalogVersion"] = sourceTypeDto.catalogVersion throw ConflictException( - "A SourceType with the specified producer, model and" - + "version already exists. This combination needs to be unique.", EntityName.SOURCE_TYPE, - ErrorConstants.ERR_SOURCE_TYPE_EXISTS, errorParams + "A SourceType with the specified producer, model and" + + "version already exists. This combination needs to be unique.", + EntityName.SOURCE_TYPE, + ErrorConstants.ERR_SOURCE_TYPE_EXISTS, + errorParams, ) } val result = sourceTypeService.save(sourceTypeDto) - return ResponseEntity.created(getUri(result)) + return ResponseEntity + .created(getUri(result)) .headers(HeaderUtil.createEntityCreationAlert(EntityName.SOURCE_TYPE, displayName(result))) .body(result) } @@ -106,18 +117,20 @@ class SourceTypeResource( @PutMapping("/source-types") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun updateSourceType(@RequestBody @Valid sourceTypeDto: SourceTypeDTO?): ResponseEntity { + fun updateSourceType( + @RequestBody @Valid sourceTypeDto: SourceTypeDTO?, + ): ResponseEntity { log.debug("REST request to update SourceType : {}", sourceTypeDto) if (sourceTypeDto!!.id == null) { return createSourceType(sourceTypeDto) } authService.checkPermission(Permission.SOURCETYPE_UPDATE) val result = sourceTypeService.save(sourceTypeDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers( - HeaderUtil.createEntityUpdateAlert(EntityName.SOURCE_TYPE, displayName(sourceTypeDto)) - ) - .body(result) + HeaderUtil.createEntityUpdateAlert(EntityName.SOURCE_TYPE, displayName(sourceTypeDto)), + ).body(result) } /** @@ -130,12 +143,13 @@ class SourceTypeResource( @Timed @Throws(NotAuthorizedException::class) fun getAllSourceTypes( - @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable? + @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable?, ): ResponseEntity> { authService.checkPermission(Permission.SOURCETYPE_READ) val page = sourceTypeService.findAll(pageable!!) - val headers = PaginationUtil - .generatePaginationHttpHeaders(page, "/api/source-types") + val headers = + PaginationUtil + .generatePaginationHttpHeaders(page, "/api/source-types") return ResponseEntity(page.content, headers, HttpStatus.OK) } @@ -148,9 +162,11 @@ class SourceTypeResource( @GetMapping("/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getSourceTypes(@PathVariable producer: String?): ResponseEntity> { + fun getSourceTypes( + @PathVariable producer: String?, + ): ResponseEntity> { authService.checkPermission(Permission.SOURCETYPE_READ) return ResponseEntity.ok(sourceTypeService.findByProducer(producer!!)) } @@ -164,22 +180,23 @@ class SourceTypeResource( * @return A list of objects matching the producer and model */ @GetMapping( - "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" - + Constants.ENTITY_ID_REGEX + "}" + "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" + + Constants.ENTITY_ID_REGEX + "}", ) @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getSourceTypes( @PathVariable producer: String?, - @PathVariable model: String? + @PathVariable model: String?, ): ResponseEntity> { authService.checkPermission(Permission.SOURCETYPE_READ) return ResponseEntity.ok( sourceTypeService.findByProducerAndModel( - producer!!, model!! - ) + producer!!, + model!!, + ), ) } @@ -192,22 +209,23 @@ class SourceTypeResource( * @return A single SourceType object matching the producer, model and version */ @GetMapping( - "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" - + Constants.ENTITY_ID_REGEX + "}/{version:" + Constants.ENTITY_ID_REGEX + "}" + "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" + + Constants.ENTITY_ID_REGEX + "}/{version:" + Constants.ENTITY_ID_REGEX + "}", ) @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getSourceTypes( @PathVariable producer: String?, - @PathVariable model: String?, @PathVariable version: String? + @PathVariable model: String?, + @PathVariable version: String?, ): ResponseEntity { authService.checkPermission(Permission.SOURCETYPE_READ) return ResponseUtil.wrapOrNotFound( Optional.ofNullable( - sourceTypeService.findByProducerAndModelAndVersion(producer!!, model!!, version!!) - ) + sourceTypeService.findByProducerAndModelAndVersion(producer!!, model!!, version!!), + ), ) } @@ -221,54 +239,64 @@ class SourceTypeResource( * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping( - "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" - + Constants.ENTITY_ID_REGEX + "}/{version:" + Constants.ENTITY_ID_REGEX + "}" + "/source-types/{producer:" + Constants.ENTITY_ID_REGEX + "}/{model:" + + Constants.ENTITY_ID_REGEX + "}/{version:" + Constants.ENTITY_ID_REGEX + "}", ) @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun deleteSourceType( @PathVariable producer: String?, - @PathVariable model: String?, @PathVariable version: String? + @PathVariable model: String?, + @PathVariable version: String?, ): ResponseEntity { authService.checkPermission(Permission.SOURCETYPE_DELETE) - val sourceTypeDto = sourceTypeService - .findByProducerAndModelAndVersion(producer!!, model!!, version!!) + val sourceTypeDto = + sourceTypeService + .findByProducerAndModelAndVersion(producer!!, model!!, version!!) if (Objects.isNull(sourceTypeDto)) { return ResponseEntity.notFound().build() } - val projects = sourceTypeService.findProjectsBySourceType( - producer, model, - version - ) + val projects = + sourceTypeService.findProjectsBySourceType( + producer, + model, + version, + ) if (projects.isNotEmpty()) { - throw InvalidRequestException( // we know the list is not empty so calling get() is safe here - "Cannot delete a source-type that " + "is being used by project(s)", EntityName.SOURCE_TYPE, - ErrorConstants.ERR_SOURCE_TYPE_IN_USE, Collections.singletonMap( + throw InvalidRequestException( + // we know the list is not empty so calling get() is safe here + "Cannot delete a source-type that " + "is being used by project(s)", + EntityName.SOURCE_TYPE, + ErrorConstants.ERR_SOURCE_TYPE_IN_USE, + Collections.singletonMap( "project-names", projects .stream() .map(ProjectDTO::projectName) - .collect(Collectors.joining("-")) - ) + .collect(Collectors.joining("-")), + ), ) } sourceTypeService.delete(sourceTypeDto.id!!) - return ResponseEntity.ok().headers( - HeaderUtil.createEntityDeletionAlert( - EntityName.SOURCE_TYPE, - displayName(sourceTypeDto) - ) - ).build() + return ResponseEntity + .ok() + .headers( + HeaderUtil.createEntityDeletionAlert( + EntityName.SOURCE_TYPE, + displayName(sourceTypeDto), + ), + ).build() } - private fun displayName(sourceType: SourceTypeDTO?): String { - return java.lang.String.join( - " ", sourceType!!.producer, sourceType.model, - sourceType.catalogVersion + private fun displayName(sourceType: SourceTypeDTO?): String = + java.lang.String.join( + " ", + sourceType!!.producer, + sourceType.model, + sourceType.catalogVersion, ) - } companion object { private val log = LoggerFactory.getLogger(SourceTypeResource::class.java) diff --git a/src/main/java/org/radarbase/management/web/rest/SubjectResource.kt b/src/main/java/org/radarbase/management/web/rest/SubjectResource.kt index 3bba61551..8f8a97646 100644 --- a/src/main/java/org/radarbase/management/web/rest/SubjectResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/SubjectResource.kt @@ -70,9 +70,8 @@ class SubjectResource( @Autowired private val eventRepository: AuditEventRepository, @Autowired private val revisionService: RevisionService, @Autowired private val sourceService: SourceService, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /subjects : Create a new subject. * @@ -84,40 +83,47 @@ class SubjectResource( @PostMapping("/subjects") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun createSubject(@RequestBody subjectDto: SubjectDTO): ResponseEntity { + fun createSubject( + @RequestBody subjectDto: SubjectDTO, + ): ResponseEntity { log.debug("REST request to save Subject : {}", subjectDto) val projectName = getProjectName(subjectDto) authService.checkPermission(Permission.SUBJECT_CREATE, { e: EntityDetails -> e.project(projectName) }) if (subjectDto.id != null) { throw BadRequestException( "A new subject cannot already have an ID", - EntityName.SUBJECT, "idexists" + EntityName.SUBJECT, + "idexists", ) } - if (!subjectDto.externalId.isNullOrEmpty() - && subjectRepository.findOneByProjectNameAndExternalId( - projectName, subjectDto.externalId + if (!subjectDto.externalId.isNullOrEmpty() && + subjectRepository.findOneByProjectNameAndExternalId( + projectName, + subjectDto.externalId, ) != null ) { throw BadRequestException( - "A subject with given project-id and" - + "external-id already exists", EntityName.SUBJECT, "subjectExists" + "A subject with given project-id and" + + "external-id already exists", + EntityName.SUBJECT, + "subjectExists", ) } val result = subjectService.createSubject(subjectDto) - return ResponseEntity.created(ResourceUriService.getUri(subjectDto)) + return ResponseEntity + .created(ResourceUriService.getUri(subjectDto)) .headers(HeaderUtil.createEntityCreationAlert(EntityName.SUBJECT, result?.login)) .body(result) } - private fun getProjectName(subjectDto: SubjectDTO): String { // not ideal, because only name is needed. however, id is checked to verify the project is in the database // this does prevent calls to the database? if (subjectDto.project == null || subjectDto.project!!.id == null || subjectDto.project!!.projectName == null) { throw BadRequestException( - "A subject should be assigned to a project", EntityName.SUBJECT, - "projectrequired" + "A subject should be assigned to a project", + EntityName.SUBJECT, + "projectrequired", ) } return subjectDto.project!!.projectName!! @@ -135,7 +141,9 @@ class SubjectResource( @PutMapping("/subjects") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - fun updateSubject(@RequestBody subjectDto: SubjectDTO): ResponseEntity { + fun updateSubject( + @RequestBody subjectDto: SubjectDTO, + ): ResponseEntity { log.debug("REST request to update Subject : {}", subjectDto) if (subjectDto.id == null) { return createSubject(subjectDto) @@ -147,7 +155,8 @@ class SubjectResource( .subject(subjectDto.login) }) val result = subjectService.updateSubject(subjectDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(HeaderUtil.createEntityUpdateAlert(EntityName.SUBJECT, subjectDto.login)) .body(result) } @@ -164,7 +173,9 @@ class SubjectResource( @PutMapping("/subjects/discontinue") @Timed @Throws(NotAuthorizedException::class) - fun discontinueSubject(@RequestBody subjectDto: SubjectDTO): ResponseEntity { + fun discontinueSubject( + @RequestBody subjectDto: SubjectDTO, + ): ResponseEntity { log.debug("REST request to update Subject : {}", subjectDto) if (subjectDto.id == null) { throw BadRequestException("No subject found", EntityName.SUBJECT, "subjectNotAvailable") @@ -181,11 +192,13 @@ class SubjectResource( eventRepository.add( AuditEvent( SecurityUtils.currentUserLogin, - "SUBJECT_DISCONTINUE", "subject_login=" + subjectDto.login - ) + "SUBJECT_DISCONTINUE", + "subject_login=" + subjectDto.login, + ), ) val result = subjectService.discontinueSubject(subjectDto) - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(HeaderUtil.createEntityUpdateAlert(EntityName.SUBJECT, subjectDto.login)) .body(result) } @@ -199,40 +212,55 @@ class SubjectResource( @Timed @Throws(NotAuthorizedException::class) fun getAllSubjects( - @Valid subjectCriteria: SubjectCriteria? + @Valid subjectCriteria: SubjectCriteria?, ): ResponseEntity>? { val projectName = subjectCriteria!!.projectName authService.checkPermission(Permission.SUBJECT_READ, { e: EntityDetails -> e.project(projectName) }) val externalId = subjectCriteria.externalId log.debug("ProjectName {} and external {}", projectName, externalId) // if not specified do not include inactive patients - val authoritiesToInclude = subjectCriteria.authority - .map { obj: SubjectAuthority -> obj.name } - .toList() + val authoritiesToInclude = + subjectCriteria.authority + .map { obj: SubjectAuthority -> obj.name } + .toList() return if (projectName != null && externalId != null) { - val subject = Optional.ofNullable(subjectRepository - .findOneByProjectNameAndExternalIdAndAuthoritiesIn( - projectName, externalId, authoritiesToInclude + val subject = + Optional.ofNullable( + subjectRepository + .findOneByProjectNameAndExternalIdAndAuthoritiesIn( + projectName, + externalId, + authoritiesToInclude, + )?.let { s: Subject? -> + listOf( + subjectMapper.subjectToSubjectReducedProjectDTO(s), + ) + }, ) - ?.let { s: Subject? -> - listOf( - subjectMapper.subjectToSubjectReducedProjectDTO(s) - ) - }) ResponseUtil.wrapOrNotFound(subject) } else if (projectName == null && externalId != null) { - val page = subjectService.findAll(subjectCriteria) - .map { s: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(s) } - val headers = PaginationUtil.generateSubjectPaginationHttpHeaders( - page, "/api/subjects", subjectCriteria - ) + val page = + subjectService + .findAll(subjectCriteria) + .map { s: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(s) } + val headers = + PaginationUtil.generateSubjectPaginationHttpHeaders( + page, + "/api/subjects", + subjectCriteria, + ) ResponseEntity(page.content, headers, HttpStatus.OK) } else { - val page = subjectService.findAll(subjectCriteria) - .map { subject: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(subject) } - val headers = PaginationUtil.generateSubjectPaginationHttpHeaders( - page, "/api/subjects", subjectCriteria - ) + val page = + subjectService + .findAll(subjectCriteria) + .map { subject: Subject -> subjectMapper.subjectToSubjectWithoutProjectDTO(subject) } + val headers = + PaginationUtil.generateSubjectPaginationHttpHeaders( + page, + "/api/subjects", + subjectCriteria, + ) ResponseEntity(page.content, headers, HttpStatus.OK) } } @@ -247,14 +275,17 @@ class SubjectResource( @GetMapping("/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getSubject(@PathVariable login: String?): ResponseEntity { + fun getSubject( + @PathVariable login: String?, + ): ResponseEntity { log.debug("REST request to get Subject : {}", login) authService.checkScope(Permission.SUBJECT_READ) val subject = subjectService.findOneByLogin(login) - val project: Project? = subject.activeProject - ?.let { p -> p.id?.let { projectRepository.findOneWithEagerRelationships(it) } } + val project: Project? = + subject.activeProject + ?.let { p -> p.id?.let { projectRepository.findOneWithEagerRelationships(it) } } authService.checkPermission(Permission.SUBJECT_READ, { e: EntityDetails -> if (project != null) { e.project(project.projectName) @@ -275,11 +306,11 @@ class SubjectResource( @GetMapping("/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}/revisions") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getSubjectRevisions( @Parameter pageable: Pageable?, - @PathVariable login: String + @PathVariable login: String, ): ResponseEntity> { authService.checkScope(Permission.SUBJECT_READ) log.debug("REST request to get revisions for Subject : {}", login) @@ -289,14 +320,14 @@ class SubjectResource( e.subject(login) }) val page = pageable?.let { revisionService.getRevisionsForEntity(it, subject) } - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers( PaginationUtil.generatePaginationHttpHeaders( page, - HeaderUtil.buildPath("subjects", login, "revisions") - ) - ) - .body(page?.content) + HeaderUtil.buildPath("subjects", login, "revisions"), + ), + ).body(page?.content) } /** @@ -308,20 +339,21 @@ class SubjectResource( * 404 (Not Found) */ @GetMapping( - "/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}" - + "/revisions/{revisionNb:^[0-9]*$}" + "/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}" + + "/revisions/{revisionNb:^[0-9]*$}", ) @Timed @Throws(NotAuthorizedException::class) fun getSubjectRevision( @PathVariable login: String, - @PathVariable revisionNb: Int? + @PathVariable revisionNb: Int?, ): ResponseEntity { authService.checkScope(Permission.SUBJECT_READ) log.debug("REST request to get Subject : {}, for revisionNb: {}", login, revisionNb) val subjectDto = subjectService.findRevision(login, revisionNb) authService.checkPermission(Permission.SUBJECT_READ, { e: EntityDetails -> - e.project(subjectDto.project?.projectName) + e + .project(subjectDto.project?.projectName) .subject(subjectDto.login) }) return ResponseEntity.ok(subjectDto) @@ -336,9 +368,11 @@ class SubjectResource( @DeleteMapping("/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun deleteSubject(@PathVariable login: String?): ResponseEntity { + fun deleteSubject( + @PathVariable login: String?, + ): ResponseEntity { log.debug("REST request to delete Subject : {}", login) authService.checkScope(Permission.SUBJECT_DELETE) val subject = subjectService.findOneByLogin(login) @@ -347,8 +381,10 @@ class SubjectResource( e.subject(login) }) subjectService.deleteSubject(login) - return ResponseEntity.ok() - .headers(HeaderUtil.createEntityDeletionAlert(EntityName.SUBJECT, login)).build() + return ResponseEntity + .ok() + .headers(HeaderUtil.createEntityDeletionAlert(EntityName.SUBJECT, login)) + .build() } /** @@ -371,23 +407,27 @@ class SubjectResource( */ @PostMapping("/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}/sources") @ApiResponses( - ApiResponse(responseCode = "200", description = "An existing source was assigned"), ApiResponse( - responseCode = "201", description = "A new source was created and" - + " assigned" - ), ApiResponse( - responseCode = "400", description = "You must supply either a" - + " Source Type ID, or the combination of (sourceTypeProducer, sourceTypeModel," - + " catalogVersion)" - ), ApiResponse( - responseCode = "404", description = "Either the subject or the source type" - + " was not found." - ) + ApiResponse(responseCode = "200", description = "An existing source was assigned"), + ApiResponse( + responseCode = "201", + description = "A new source was created and assigned", + ), + ApiResponse( + responseCode = "400", + description = + "You must supply either a Source Type ID, or the combination " + + "of (sourceTypeProducer, sourceTypeModel, catalogVersion)", + ), + ApiResponse( + responseCode = "404", + description = "Either the subject or the source type was not found.", + ), ) @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) fun assignSources( @PathVariable login: String?, - @RequestBody sourceDto: MinimalSourceDetailsDTO + @RequestBody sourceDto: MinimalSourceDetailsDTO, ): ResponseEntity { authService.checkScope(Permission.SUBJECT_UPDATE) @@ -397,16 +437,19 @@ class SubjectResource( // check if combination (producer, model, version) is present if (sourceDto.sourceTypeProducer == null || sourceDto.sourceTypeModel == null || sourceDto.sourceTypeCatalogVersion == null) { throw BadRequestException( - "Producer or model or version value for the " - + "source-type is null", EntityName.SOURCE_TYPE, ErrorConstants.ERR_VALIDATION + "Producer or model or version value for the " + + "source-type is null", + EntityName.SOURCE_TYPE, + ErrorConstants.ERR_VALIDATION, ) } - sourceTypeId = sourceTypeService - .findByProducerAndModelAndVersion( - sourceDto.sourceTypeProducer!!, - sourceDto.sourceTypeModel!!, - sourceDto.sourceTypeCatalogVersion!! - ).id + sourceTypeId = + sourceTypeService + .findByProducerAndModelAndVersion( + sourceDto.sourceTypeProducer!!, + sourceDto.sourceTypeModel!!, + sourceDto.sourceTypeCatalogVersion!!, + ).id // also update the sourceDto, since we pass it on to SubjectService later sourceDto.sourceTypeId = sourceTypeId } @@ -415,11 +458,13 @@ class SubjectResource( val sub = subjectService.findOneByLogin(login) // find the actively assigned project for this subject - val currentProject: Project = projectRepository.findByIdWithOrganization(sub.activeProject?.id) - ?: throw InvalidRequestException( - "Requested subject does not have an active project", - EntityName.SUBJECT, ErrorConstants.ERR_ACTIVE_PARTICIPANT_PROJECT_NOT_FOUND - ) + val currentProject: Project = + projectRepository.findByIdWithOrganization(sub.activeProject?.id) + ?: throw InvalidRequestException( + "Requested subject does not have an active project", + EntityName.SUBJECT, + ErrorConstants.ERR_ACTIVE_PARTICIPANT_PROJECT_NOT_FOUND, + ) authService.checkPermission(Permission.SUBJECT_UPDATE, { e: EntityDetails -> e @@ -428,47 +473,52 @@ class SubjectResource( }) // find whether the relevant source-type is available in the subject's project - val sourceType = projectRepository - .findSourceTypeByProjectIdAndSourceTypeId(currentProject.id, sourceTypeId) - ?: throw BadRequestException( - "No valid source-type found for project." - + " You must provide either valid source-type id or producer, model," - + " version of a source-type that is assigned to project", - EntityName.SUBJECT, ErrorConstants.ERR_SOURCE_TYPE_NOT_PROVIDED + val sourceType = + projectRepository + .findSourceTypeByProjectIdAndSourceTypeId(currentProject.id, sourceTypeId) + ?: throw BadRequestException( + "No valid source-type found for project." + + " You must provide either valid source-type id or producer, model," + + " version of a source-type that is assigned to project", + EntityName.SUBJECT, + ErrorConstants.ERR_SOURCE_TYPE_NOT_PROVIDED, ) // check if any of id, sourceID, sourceName were non-null - val existing = Stream.of( - sourceDto.id, sourceDto.sourceName, - sourceDto.sourceId - ) - .anyMatch { obj: Serializable? -> Objects.nonNull(obj) } + val existing = + Stream + .of( + sourceDto.id, + sourceDto.sourceName, + sourceDto.sourceId, + ).anyMatch { obj: Serializable? -> Objects.nonNull(obj) } // handle the source registration - val sourceRegistered = subjectService - .assignOrUpdateSource(sub, sourceType, currentProject, sourceDto) + val sourceRegistered = + subjectService + .assignOrUpdateSource(sub, sourceType, currentProject, sourceDto) // Return the correct response type, either created if a new source was created, or ok if // an existing source was provided. If an existing source was given but not found, the // assignOrUpdateSource would throw an error, and we would not reach this point. return if (!existing) { - ResponseEntity.created(ResourceUriService.getUri(sourceRegistered)) + ResponseEntity + .created(ResourceUriService.getUri(sourceRegistered)) .headers( HeaderUtil.createEntityCreationAlert( EntityName.SOURCE, - sourceRegistered.sourceName - ) - ) - .body(sourceRegistered) + sourceRegistered.sourceName, + ), + ).body(sourceRegistered) } else { - ResponseEntity.ok() + ResponseEntity + .ok() .headers( HeaderUtil.createEntityUpdateAlert( EntityName.SOURCE, - sourceRegistered.sourceName - ) - ) - .body(sourceRegistered) + sourceRegistered.sourceName, + ), + ).body(sourceRegistered) } } @@ -481,17 +531,18 @@ class SubjectResource( @GetMapping("/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}/sources") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) fun getSubjectSources( @PathVariable login: String?, - @RequestParam(value = "withInactiveSources", required = false) withInactiveSourcesParam: Boolean? + @RequestParam(value = "withInactiveSources", required = false) withInactiveSourcesParam: Boolean?, ): ResponseEntity> { authService.checkScope(Permission.SUBJECT_READ) val withInactiveSources = withInactiveSourcesParam != null && withInactiveSourcesParam // check the subject id - val subject = subjectRepository.findOneWithEagerBySubjectLogin(login) - ?: throw NoSuchElementException() + val subject = + subjectRepository.findOneWithEagerBySubjectLogin(login) + ?: throw NoSuchElementException() authService.checkPermission(Permission.SUBJECT_READ, { e: EntityDetails -> e .project(subject.associatedProject?.projectName) @@ -522,32 +573,37 @@ class SubjectResource( * @throws NotFoundException if the subject or the source not found using given ids. */ @PostMapping( - "/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}/sources/{sourceName:" - + Constants.ENTITY_ID_REGEX + "}" + "/subjects/{login:" + Constants.ENTITY_ID_REGEX + "}/sources/{sourceName:" + + Constants.ENTITY_ID_REGEX + "}", ) @ApiResponses( ApiResponse(responseCode = "200", description = "An existing source was updated"), ApiResponse(responseCode = "400", description = "You must supply existing sourceId)"), ApiResponse( - responseCode = "404", description = "Either the subject or the source was" - + " not found." - ) + responseCode = "404", + description = + "Either the subject or the source was" + + " not found.", + ), ) @Timed @Throws(NotFoundException::class, NotAuthorizedException::class) fun updateSubjectSource( @PathVariable login: String, - @PathVariable sourceName: String, @RequestBody attributes: Map? + @PathVariable sourceName: String, + @RequestBody attributes: Map?, ): ResponseEntity { authService.checkScope(Permission.SUBJECT_UPDATE) // check the subject id - val subject = subjectRepository.findOneWithEagerBySubjectLogin(login) - ?: throw NotFoundException( - "Subject ID not found", - EntityName.SUBJECT, ErrorConstants.ERR_SUBJECT_NOT_FOUND, - Collections.singletonMap("subjectLogin", login) - ) + val subject = + subjectRepository.findOneWithEagerBySubjectLogin(login) + ?: throw NotFoundException( + "Subject ID not found", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, + Collections.singletonMap("subjectLogin", login), + ) authService.checkPermission(Permission.SUBJECT_UPDATE, { e: EntityDetails -> e .project(subject.associatedProject?.projectName) @@ -555,19 +611,23 @@ class SubjectResource( }) // find source under subject - val source = subject.sources.stream() - .filter { s: Source -> s.sourceName == sourceName } - .findAny() - .orElseThrow { - val errorParams: MutableMap = HashMap() - errorParams["subjectLogin"] = login - errorParams["sourceName"] = sourceName - NotFoundException( - "Source not found under assigned sources of " - + "subject", EntityName.SUBJECT, ErrorConstants.ERR_SUBJECT_NOT_FOUND, - errorParams - ) - } + val source = + subject.sources + .stream() + .filter { s: Source -> s.sourceName == sourceName } + .findAny() + .orElseThrow { + val errorParams: MutableMap = HashMap() + errorParams["subjectLogin"] = login + errorParams["sourceName"] = sourceName + NotFoundException( + "Source not found under assigned sources of " + + "subject", + EntityName.SUBJECT, + ErrorConstants.ERR_SUBJECT_NOT_FOUND, + errorParams, + ) + } // there should be only one source under a source-name. return ResponseEntity.ok(sourceService.safeUpdateOfAttributes(source, attributes)) diff --git a/src/main/java/org/radarbase/management/web/rest/TokenKeyEndpoint.kt b/src/main/java/org/radarbase/management/web/rest/TokenKeyEndpoint.kt index c4fc357ee..9c1ae458e 100644 --- a/src/main/java/org/radarbase/management/web/rest/TokenKeyEndpoint.kt +++ b/src/main/java/org/radarbase/management/web/rest/TokenKeyEndpoint.kt @@ -9,24 +9,26 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @RestController -class TokenKeyEndpoint @Autowired constructor( - private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler -) { - @get:Timed - @get:GetMapping("/oauth/token_key") - val key: JsonWebKeySet - /** - * Get the verification key for the token signatures. The principal has to - * be provided only if the key is secret - * - * @return the key used to verify tokens - */ - get() { - logger.debug("Requesting verifier public keys...") - return keyStoreHandler.loadJwks() - } +class TokenKeyEndpoint + @Autowired + constructor( + private val keyStoreHandler: ManagementPortalOauthKeyStoreHandler, + ) { + @get:Timed + @get:GetMapping("/oauth/token_key") + val key: JsonWebKeySet + /** + * Get the verification key for the token signatures. The principal has to + * be provided only if the key is secret + * + * @return the key used to verify tokens + */ + get() { + logger.debug("Requesting verifier public keys...") + return keyStoreHandler.loadJwks() + } - companion object { - private val logger = LoggerFactory.getLogger(TokenKeyEndpoint::class.java) + companion object { + private val logger = LoggerFactory.getLogger(TokenKeyEndpoint::class.java) + } } -} diff --git a/src/main/java/org/radarbase/management/web/rest/UserResource.kt b/src/main/java/org/radarbase/management/web/rest/UserResource.kt index 455402df7..f3bfa9c55 100644 --- a/src/main/java/org/radarbase/management/web/rest/UserResource.kt +++ b/src/main/java/org/radarbase/management/web/rest/UserResource.kt @@ -78,9 +78,8 @@ class UserResource( @Autowired private val userService: UserService, @Autowired private val subjectRepository: SubjectRepository, @Autowired private val managementPortalProperties: ManagementPortalProperties, - @Autowired private val authService: AuthService + @Autowired private val authService: AuthService, ) { - /** * POST /users : Creates a new user. * @@ -96,30 +95,44 @@ class UserResource( @PostMapping("/users") @Timed @Throws(URISyntaxException::class, NotAuthorizedException::class) - suspend fun createUser(@RequestBody managedUserVm: ManagedUserVM): ResponseEntity { + suspend fun createUser( + @RequestBody managedUserVm: ManagedUserVM, + ): ResponseEntity { log.debug("REST request to save User : {}", managedUserVm) authService.checkPermission(Permission.USER_CREATE) return if (managedUserVm.id != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - EntityName.USER, "idexists", "A new user cannot already have an ID" - ) - ).body(null) + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + EntityName.USER, + "idexists", + "A new user cannot already have an ID", + ), + ).body(null) // Lowercase the user login before comparing with database } else if (managedUserVm.login?.lowercase().let { userRepository.findOneByLogin(it) } != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - EntityName.USER, "userexists", "Login already in use" - ) - ).body(null) + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + EntityName.USER, + "userexists", + "Login already in use", + ), + ).body(null) } else if (managedUserVm.email?.let { userRepository.findOneByEmail(it) } != null) { - ResponseEntity.badRequest().headers( - HeaderUtil.createFailureAlert( - EntityName.USER, "emailexists", "Email already in use" - ) - ).body(null) + ResponseEntity + .badRequest() + .headers( + HeaderUtil.createFailureAlert( + EntityName.USER, + "emailexists", + "Email already in use", + ), + ).body(null) } else { - val newUser: User; + val newUser: User newUser = userService.createUser(managedUserVm) val recoveryLink = userService.getRecoveryLink(newUser) @@ -128,18 +141,21 @@ class UserResource( newUser.email, "Account Activation", "Please click the link to activate your account:\n\n" + - "$recoveryLink \n\n" + - "Please activate your account before the link expires in 24 hours, and activate 2FA to enable" + - " access to the managementportal", + "$recoveryLink \n\n" + + "Please activate your account before the link expires in 24 hours, and activate 2FA to enable" + + " access to the managementportal", + false, false, - false ) - ResponseEntity.created(ResourceUriService.getUri(newUser)).headers( - HeaderUtil.createAlert( - "userManagement.created", newUser.login - ) - ).body(newUser) + ResponseEntity + .created(ResourceUriService.getUri(newUser)) + .headers( + HeaderUtil.createAlert( + "userManagement.created", + newUser.login, + ), + ).body(newUser) } } @@ -154,18 +170,21 @@ class UserResource( @PutMapping("/users") @Timed @Throws(NotAuthorizedException::class) - suspend fun updateUser(@RequestBody managedUserVm: ManagedUserVM): ResponseEntity { + suspend fun updateUser( + @RequestBody managedUserVm: ManagedUserVM, + ): ResponseEntity { log.debug("REST request to update User : {}", managedUserVm) authService.checkPermission(Permission.USER_UPDATE, { e: EntityDetails -> e.user(managedUserVm.login) }) var existingUser = managedUserVm.email?.let { userRepository.findOneByEmail(it) } if (existingUser != null && existingUser.id != managedUserVm.id) { throw BadRequestException("Email already in use", EntityName.USER, "emailexists") } - existingUser = managedUserVm.login?.lowercase().let { - userRepository.findOneByLogin( - it - ) - } + existingUser = + managedUserVm.login?.lowercase().let { + userRepository.findOneByLogin( + it, + ) + } if (existingUser != null && existingUser.id != managedUserVm.id) { throw BadRequestException("Login already in use", EntityName.USER, "emailexists") } @@ -173,16 +192,20 @@ class UserResource( if (subject != null && managedUserVm.isActivated && subject.removed) { // if the subject is also a user, check if the removed/activated states are valid throw InvalidRequestException( - "Subject cannot be the user to request " + "this changes", EntityName.USER, "error.invalidsubjectstate" + "Subject cannot be the user to request " + "this changes", + EntityName.USER, + "error.invalidsubjectstate", ) } val updatedUser: UserDTO? updatedUser = userService.updateUser(managedUserVm) - return ResponseEntity.ok().headers( - HeaderUtil.createAlert("userManagement.updated", managedUserVm.login) - ).body( - updatedUser - ) + return ResponseEntity + .ok() + .headers( + HeaderUtil.createAlert("userManagement.updated", managedUserVm.login), + ).body( + updatedUser, + ) } /** @@ -202,13 +225,15 @@ class UserResource( fun getUsers( @PageableDefault(page = 0, size = Int.MAX_VALUE) pageable: Pageable?, userFilter: UserFilter, - @RequestParam(defaultValue = "true") includeProvenance: Boolean + @RequestParam(defaultValue = "true") includeProvenance: Boolean, ): ResponseEntity> { authService.checkScope(Permission.USER_READ) val page = userService.findUsers(userFilter, pageable, includeProvenance) return ResponseEntity( - page!!.content, PaginationUtil.generatePaginationHttpHeaders(page, "/api/users"), HttpStatus.OK + page!!.content, + PaginationUtil.generatePaginationHttpHeaders(page, "/api/users"), + HttpStatus.OK, ) } @@ -222,13 +247,15 @@ class UserResource( @GetMapping("/users/{login:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getUser(@PathVariable login: String): ResponseEntity { + fun getUser( + @PathVariable login: String, + ): ResponseEntity { log.debug("REST request to get User : {}", login) authService.checkPermission(Permission.USER_READ, { e: EntityDetails -> e.user(login) }) return ResponseUtil.wrapOrNotFound( - Optional.ofNullable(userService.getUserWithAuthoritiesByLogin(login)) + Optional.ofNullable(userService.getUserWithAuthoritiesByLogin(login)), ) } @@ -241,9 +268,11 @@ class UserResource( @DeleteMapping("/users/{login:" + Constants.ENTITY_ID_REGEX + "}") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - suspend fun deleteUser(@PathVariable login: String): ResponseEntity { + suspend fun deleteUser( + @PathVariable login: String, + ): ResponseEntity { log.debug("REST request to delete User: {}", login) authService.checkPermission(Permission.USER_DELETE, { e: EntityDetails -> e.user(login) }) userService.deleteUser(login) @@ -259,13 +288,15 @@ class UserResource( @GetMapping("/users/{login:" + Constants.ENTITY_ID_REGEX + "}/roles") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) - fun getUserRoles(@PathVariable login: String): ResponseEntity> { + fun getUserRoles( + @PathVariable login: String, + ): ResponseEntity> { log.debug("REST request to read User roles: {}", login) authService.checkPermission(Permission.ROLE_READ, { e: EntityDetails -> e.user(login) }) return ResponseUtil.wrapOrNotFound( - Optional.ofNullable(userService.getUserWithAuthoritiesByLogin(login).let { obj: UserDTO? -> obj?.roles }) + Optional.ofNullable(userService.getUserWithAuthoritiesByLogin(login).let { obj: UserDTO? -> obj?.roles }), ) } @@ -278,10 +309,11 @@ class UserResource( @PutMapping("/users/{login:" + Constants.ENTITY_ID_REGEX + "}/roles") @Timed @Throws( - NotAuthorizedException::class + NotAuthorizedException::class, ) suspend fun putUserRoles( - @PathVariable login: String?, @RequestBody roleDtos: Set? + @PathVariable login: String?, + @RequestBody roleDtos: Set?, ): ResponseEntity { log.debug("REST request to update User roles: {} to {}", login, roleDtos) authService.checkPermission(Permission.ROLE_UPDATE, { e: EntityDetails -> e.user(login) }) diff --git a/src/main/java/org/radarbase/management/web/rest/criteria/CriteriaRange.kt b/src/main/java/org/radarbase/management/web/rest/criteria/CriteriaRange.kt index 29ee1b1fc..80f973e01 100644 --- a/src/main/java/org/radarbase/management/web/rest/criteria/CriteriaRange.kt +++ b/src/main/java/org/radarbase/management/web/rest/criteria/CriteriaRange.kt @@ -8,16 +8,14 @@ */ package org.radarbase.management.web.rest.criteria -open class CriteriaRange?>(var from: T? = null, var to: T? = null, - var `is`: T? = null +open class CriteriaRange?>( + var from: T? = null, + var to: T? = null, + var `is`: T? = null, ) { - fun from(): T? { - return if (this.`is` == null) from else null - } + fun from(): T? = if (this.`is` == null) from else null - fun to(): T? { - return if (this.`is` == null) to else null - } + fun to(): T? = if (this.`is` == null) to else null val isEmpty: Boolean /** @@ -29,13 +27,16 @@ open class CriteriaRange?>(var from: T? = null, var to: T? = n * Validate this criteria range whether the from and to ranges are in order. */ fun validate() { - require(!(`is` == null && from != null && to != null && from!!.compareTo(to!!) > 0)) { "CriteriaRange must have a from range that is smaller then the to range." } + require(!(`is` == null && from != null && to != null && from!!.compareTo(to!!) > 0)) { + "CriteriaRange must have a from range that is smaller then the to range." + } } - override fun toString(): String { - return ("CriteriaRange{" + "from=" + from() - + ", to=" + to() - + ", is=" + this.`is` - + '}') - } + override fun toString(): String = + ( + "CriteriaRange{" + "from=" + from() + + ", to=" + to() + + ", is=" + this.`is` + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectAuthority.kt b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectAuthority.kt index 3b19818d2..1d5d89d91 100644 --- a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectAuthority.kt +++ b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectAuthority.kt @@ -10,5 +10,5 @@ package org.radarbase.management.web.rest.criteria enum class SubjectAuthority { ROLE_PARTICIPANT, - ROLE_INACTIVE_PARTICIPANT + ROLE_INACTIVE_PARTICIPANT, } diff --git a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectCriteria.kt b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectCriteria.kt index 76b0d6b08..0c40bc252 100644 --- a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectCriteria.kt +++ b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectCriteria.kt @@ -18,8 +18,12 @@ class SubjectCriteria { var groupId: Long? = null var humanReadableIdentifier: String? = null var last: SubjectCriteriaLast? = null - var page: @Min(0) Int = 0 - var size: @Min(1) Int = 20 + var page: + @Min(0) + Int = 0 + var size: + @Min(1) + Int = 20 var sort: List? = null var personName: String? = null var projectName: String? = null @@ -32,9 +36,11 @@ class SubjectCriteria { private set private fun parseSort(): List { - val flatSort = sort?.flatMap { s: String -> - s.split(",") - }?.toList() ?: listOf() + val flatSort = + sort + ?.flatMap { s: String -> + s.split(",") + }?.toList() ?: listOf() val parsedSort: MutableList = ArrayList(flatSort.size) var hasDirection = true var previous: SubjectSortOrder? = null @@ -57,15 +63,40 @@ class SubjectCriteria { return parsedSort } - @get:NotNull val pageable: Pageable /** Get the criteria paging settings, excluding sorting. */ get() = PageRequest.of(page, size) - override fun toString(): String { - return ("SubjectCriteria{" + "authority=" + authority + ", dateOfBirth=" + dateOfBirth + ", enrollmentDate=" + enrollmentDate + ", groupId='" + groupId + '\'' + ", humanReadableIdentifier='" + humanReadableIdentifier + '\'' + ", last=" + last + ", page=" + page + ", sort=" + sort + ", personName='" + personName + '\'' + ", projectName='" + projectName + '\'' + ", externalId='" + externalId + '\'' + ", login='" + login + '\'' + '}') - } + override fun toString(): String = + ( + "SubjectCriteria{" + "authority=" + authority + ", dateOfBirth=" + dateOfBirth + ", enrollmentDate=" + enrollmentDate + + ", groupId='" + + groupId + + '\'' + + ", humanReadableIdentifier='" + + humanReadableIdentifier + + '\'' + + ", last=" + + last + + ", page=" + + page + + ", sort=" + + sort + + ", personName='" + + personName + + '\'' + + ", projectName='" + + projectName + + '\'' + + ", externalId='" + + externalId + + '\'' + + ", login='" + + login + + '\'' + + '}' + ) companion object { /** @@ -73,9 +104,10 @@ class SubjectCriteria { * @param sort modifiable ordered sort collection. */ private fun optimizeSortList(sort: MutableCollection) { - val seenSortBy: EnumSet? = EnumSet.noneOf( - SubjectSortBy::class.java - ) + val seenSortBy: EnumSet? = + EnumSet.noneOf( + SubjectSortBy::class.java, + ) var hasUnique = false val iterator = sort.iterator() while (iterator.hasNext()) { @@ -92,16 +124,17 @@ class SubjectCriteria { } } - private fun getSubjectSortBy(param: String): SubjectSortBy { - return Arrays.stream(SubjectSortBy.values()) - .filter { s: SubjectSortBy -> s.queryParam.equals(param, ignoreCase = true) }.findAny() + private fun getSubjectSortBy(param: String): SubjectSortBy = + Arrays + .stream(SubjectSortBy.values()) + .filter { s: SubjectSortBy -> s.queryParam.equals(param, ignoreCase = true) } + .findAny() .orElseThrow { BadRequestException( "Cannot convert sort parameter " + param + " to subject property", EntityName.Companion.SUBJECT, - ErrorConstants.ERR_VALIDATION + ErrorConstants.ERR_VALIDATION, ) } - } } } diff --git a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortBy.kt b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortBy.kt index d9c85e74f..b383c8d49 100644 --- a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortBy.kt +++ b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortBy.kt @@ -12,10 +12,9 @@ enum class SubjectSortBy( /** Query parameter name. */ val queryParam: String, /** Whether this property is unique across all subjects. */ - val isUnique: Boolean + val isUnique: Boolean, ) { ID("id", true), EXTERNAL_ID("externalId", false), - USER_LOGIN("login", true) - + USER_LOGIN("login", true), } diff --git a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortOrder.kt b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortOrder.kt index 69932d4fa..32a38d17b 100644 --- a/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortOrder.kt +++ b/src/main/java/org/radarbase/management/web/rest/criteria/SubjectSortOrder.kt @@ -12,27 +12,24 @@ import org.springframework.data.domain.Sort import java.util.* import javax.validation.constraints.NotNull -class SubjectSortOrder @JvmOverloads constructor( - @NotNull val sortBy: SubjectSortBy, - @NotNull var direction: Sort.Direction = Sort.Direction.ASC -) { +class SubjectSortOrder + @JvmOverloads + constructor( + @NotNull val sortBy: SubjectSortBy, + @NotNull var direction: Sort.Direction = Sort.Direction.ASC, + ) { + override fun toString(): String = sortBy.name + ',' + direction.name - override fun toString(): String { - return sortBy.name + ',' + direction.name - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val that = other as SubjectSortOrder + return sortBy == that.sortBy && direction == that.direction } - if (other == null || javaClass != other.javaClass) { - return false - } - val that = other as SubjectSortOrder - return sortBy == that.sortBy && direction == that.direction - } - override fun hashCode(): Int { - return Objects.hash(direction, sortBy) + override fun hashCode(): Int = Objects.hash(direction, sortBy) } -} diff --git a/src/main/java/org/radarbase/management/web/rest/errors/BadRequestException.kt b/src/main/java/org/radarbase/management/web/rest/errors/BadRequestException.kt index b69f5a6e2..bf7768a38 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/BadRequestException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/BadRequestException.kt @@ -20,7 +20,7 @@ class BadRequestException : RadarWebApplicationException { HttpStatus.BAD_REQUEST, message, entityName, - errorCode + errorCode, ) /** @@ -32,7 +32,9 @@ class BadRequestException : RadarWebApplicationException { * @param paramMap map of additional information. */ constructor( - message: String?, entityName: String, errorCode: String?, - paramMap: Map? + message: String?, + entityName: String, + errorCode: String?, + paramMap: Map?, ) : super(HttpStatus.BAD_REQUEST, message, entityName, errorCode, paramMap) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/ConflictException.kt b/src/main/java/org/radarbase/management/web/rest/errors/ConflictException.kt index 2dcbc7949..4eb3b2a2c 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/ConflictException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/ConflictException.kt @@ -26,7 +26,7 @@ class ConflictException : RadarWebApplicationException { HttpStatus.CONFLICT, message, entityName, - errorCode + errorCode, ) /** @@ -38,7 +38,9 @@ class ConflictException : RadarWebApplicationException { * @param paramMap map of additional information. */ constructor( - message: String?, entityName: String, errorCode: String?, - paramMap: Map? + message: String?, + entityName: String, + errorCode: String?, + paramMap: Map?, ) : super(HttpStatus.CONFLICT, message, entityName, errorCode, paramMap) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/ErrorConstants.kt b/src/main/java/org/radarbase/management/web/rest/errors/ErrorConstants.kt index 3073a1102..865216078 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/ErrorConstants.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/ErrorConstants.kt @@ -21,8 +21,10 @@ object ErrorConstants { const val ERR_GROUP_EXISTS = "error.groupExists" const val ERR_INVALID_AUTHORITY = "error.invalidAuthority" const val ERR_EMAIL_EXISTS = "error.emailexists" - const val ERR_ORGANIZATION_NAME_NOT_FOUND = ("error" - + ".organizationNameNotFound") + const val ERR_ORGANIZATION_NAME_NOT_FOUND = ( + "error" + + ".organizationNameNotFound" + ) const val ERR_PROJECT_ID_NOT_FOUND = "error.projectIdNotFound" const val ERR_PROJECT_NAME_NOT_FOUND = "error.projectNameNotFound" const val ERR_REVISIONS_NOT_FOUND = "error.revisionsNotFound" @@ -30,10 +32,14 @@ object ErrorConstants { const val ERR_TOKEN_NOT_FOUND = "error.tokenNotFound" const val ERR_SOURCE_TYPE_NOT_PROVIDED = "error.sourceTypeNotProvided" const val ERR_PERSISTENT_TOKEN_DISABLED = "error.persistentTokenDisabled" - const val ERR_ACTIVE_PARTICIPANT_PROJECT_NOT_FOUND = ("error" - + ".activeParticipantProjectNotFound") - const val ERR_NO_VALID_PRIVACY_POLICY_URL_CONFIGURED = ("error" - + ".noValidPrivacyPolicyUrl") + const val ERR_ACTIVE_PARTICIPANT_PROJECT_NOT_FOUND = ( + "error" + + ".activeParticipantProjectNotFound" + ) + const val ERR_NO_VALID_PRIVACY_POLICY_URL_CONFIGURED = ( + "error" + + ".noValidPrivacyPolicyUrl" + ) const val ERR_NO_SUCH_CLIENT = "error.noSuchClient" const val ERR_PROJECT_NOT_EMPTY = "error.projectNotEmpty" const val ERR_PASSWORD_TOO_LONG = "error.longPassword" diff --git a/src/main/java/org/radarbase/management/web/rest/errors/ErrorVM.kt b/src/main/java/org/radarbase/management/web/rest/errors/ErrorVM.kt index 39ae5c5cd..d851542b1 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/ErrorVM.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/ErrorVM.kt @@ -5,27 +5,34 @@ import java.io.Serializable /** * View Model for transferring error message with a list of field errors. */ -class ErrorVM @JvmOverloads constructor(val message: String?, val description: String? = null) : Serializable { - private var fieldErrors: MutableList? = null +class ErrorVM + @JvmOverloads + constructor( + val message: String?, + val description: String? = null, + ) : Serializable { + private var fieldErrors: MutableList? = null - /** - * Add a field error. - * @param objectName the object name - * @param field the field name - * @param message the error message - */ - fun add(objectName: String?, field: String?, message: String?) { - if (fieldErrors == null) { - fieldErrors = ArrayList() + /** + * Add a field error. + * @param objectName the object name + * @param field the field name + * @param message the error message + */ + fun add( + objectName: String?, + field: String?, + message: String?, + ) { + if (fieldErrors == null) { + fieldErrors = ArrayList() + } + fieldErrors!!.add(FieldErrorVM(objectName, field, message)) } - fieldErrors!!.add(FieldErrorVM(objectName, field, message)) - } - fun getFieldErrors(): List? { - return fieldErrors - } + fun getFieldErrors(): List? = fieldErrors - companion object { - private const val serialVersionUID = 1L + companion object { + private const val serialVersionUID = 1L + } } -} diff --git a/src/main/java/org/radarbase/management/web/rest/errors/ExceptionTranslator.kt b/src/main/java/org/radarbase/management/web/rest/errors/ExceptionTranslator.kt index 76b2b392c..606cb21df 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/ExceptionTranslator.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/ExceptionTranslator.kt @@ -32,9 +32,8 @@ class ExceptionTranslator { @ExceptionHandler(ConcurrencyFailureException::class) @ResponseStatus(HttpStatus.CONFLICT) @ResponseBody - fun processConcurrencyError(ex: ConcurrencyFailureException?): ErrorVM { - return ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE) - } + fun processConcurrencyError(ex: ConcurrencyFailureException?): ErrorVM = + ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE) /** * Translate a [TransactionSystemException]. @@ -64,8 +63,9 @@ class ExceptionTranslator { val dto = ErrorVM(ErrorConstants.ERR_VALIDATION) for (fieldError in fieldErrors) { dto.add( - fieldError.objectName, fieldError.field, - (fieldError.code?.plus(": ") ?: "undefined.error.code") + fieldError.defaultMessage + fieldError.objectName, + fieldError.field, + (fieldError.code?.plus(": ") ?: "undefined.error.code") + fieldError.defaultMessage, ) } return dto @@ -73,48 +73,35 @@ class ExceptionTranslator { @ExceptionHandler(MethodArgumentTypeMismatchException::class) @ResponseBody - fun processValidationError(ex: MethodArgumentTypeMismatchException): ErrorVM { - return ErrorVM( + fun processValidationError(ex: MethodArgumentTypeMismatchException): ErrorVM = + ErrorVM( ErrorConstants.ERR_VALIDATION, - ex.name + ": " + ex.message + ex.name + ": " + ex.message, ) - } @ExceptionHandler(RadarWebApplicationException::class) - fun processParameterizedValidationError( - ex: RadarWebApplicationException - ): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processParameterizedValidationError(ex: RadarWebApplicationException): ResponseEntity = + processRadarWebApplicationException(ex) @ExceptionHandler(NotFoundException::class) - fun processNotFound(ex: NotFoundException): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processNotFound(ex: NotFoundException): ResponseEntity = + processRadarWebApplicationException(ex) @ExceptionHandler(InvalidStateException::class) - fun processNotFound( - ex: InvalidStateException - ): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processNotFound(ex: InvalidStateException): ResponseEntity = + processRadarWebApplicationException(ex) @ExceptionHandler(RequestGoneException::class) - fun processNotFound(ex: RequestGoneException): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processNotFound(ex: RequestGoneException): ResponseEntity = + processRadarWebApplicationException(ex) @ExceptionHandler(BadRequestException::class) - fun processNotFound(ex: BadRequestException): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processNotFound(ex: BadRequestException): ResponseEntity = + processRadarWebApplicationException(ex) @ExceptionHandler(InvalidRequestException::class) - fun processNotFound( - ex: InvalidRequestException - ): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processNotFound(ex: InvalidRequestException): ResponseEntity = + processRadarWebApplicationException(ex) /** * Translate a [ConflictException]. @@ -122,46 +109,39 @@ class ExceptionTranslator { * @return the view-model for the translated exception */ @ExceptionHandler(ConflictException::class) - fun processConflict( - ex: ConflictException - ): ResponseEntity { - return processRadarWebApplicationException(ex) - } + fun processConflict(ex: ConflictException): ResponseEntity = + processRadarWebApplicationException(ex) private fun processRadarWebApplicationException( - exception: RadarWebApplicationException - ): ResponseEntity { - return ResponseEntity + exception: RadarWebApplicationException, + ): ResponseEntity = + ResponseEntity .status(exception.status) .headers( HeaderUtil.createExceptionAlert( exception.entityName, - exception.errorCode, exception.message - ) - ) - .body(exception.exceptionVM) - } + exception.errorCode, + exception.message, + ), + ).body(exception.exceptionVM) @ExceptionHandler(AccessDeniedException::class) @ResponseStatus(HttpStatus.FORBIDDEN) @ResponseBody - fun processAccessDeniedException(e: AccessDeniedException): ErrorVM { - return ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.message) - } + fun processAccessDeniedException(e: AccessDeniedException): ErrorVM = + ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.message) @ExceptionHandler(NotAuthorizedException::class) @ResponseStatus(HttpStatus.FORBIDDEN) @ResponseBody - fun processRadarNotAuthorizedException(e: NotAuthorizedException): ErrorVM { - return ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.message) - } + fun processRadarNotAuthorizedException(e: NotAuthorizedException): ErrorVM = + ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.message) @ExceptionHandler(HttpRequestMethodNotSupportedException::class) @ResponseBody @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) - fun processMethodNotSupportedException(ex: HttpRequestMethodNotSupportedException): ErrorVM { - return ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, ex.message) - } + fun processMethodNotSupportedException(ex: HttpRequestMethodNotSupportedException): ErrorVM = + ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, ex.message) /** * If a client tries to initiate an OAuth flow with a non-existing client, this will @@ -174,15 +154,14 @@ class ExceptionTranslator { @ExceptionHandler(NoSuchClientException::class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) - fun processNoSuchClientException(ex: NoSuchClientException): ErrorVM { - return ErrorVM(ErrorConstants.ERR_NO_SUCH_CLIENT, ex.message) - } + fun processNoSuchClientException(ex: NoSuchClientException): ErrorVM = + ErrorVM(ErrorConstants.ERR_NO_SUCH_CLIENT, ex.message) @ExceptionHandler(ResponseStatusException::class) - fun responseStatusResponse(ex: ResponseStatusException): ResponseEntity { - return ResponseEntity.status(ex.status) + fun responseStatusResponse(ex: ResponseStatusException): ResponseEntity = + ResponseEntity + .status(ex.status) .body(ErrorVM(null, ex.message)) - } /** * Generic exception translator. @@ -194,22 +173,25 @@ class ExceptionTranslator { val builder: ResponseEntity.BodyBuilder val errorVm: ErrorVM logger.error("Failed to process message", ex) - val responseStatus = AnnotationUtils.findAnnotation( - ex.javaClass, - ResponseStatus::class.java - ) + val responseStatus = + AnnotationUtils.findAnnotation( + ex.javaClass, + ResponseStatus::class.java, + ) if (responseStatus != null) { builder = ResponseEntity.status(responseStatus.value) - errorVm = ErrorVM( - "error." + responseStatus.value.value(), - responseStatus.reason - ) + errorVm = + ErrorVM( + "error." + responseStatus.value.value(), + responseStatus.reason, + ) } else { builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - errorVm = ErrorVM( - ErrorConstants.ERR_INTERNAL_SERVER_ERROR, - "Internal server error" - ) + errorVm = + ErrorVM( + ErrorConstants.ERR_INTERNAL_SERVER_ERROR, + "Internal server error", + ) } return builder.body(errorVm) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/FieldErrorVM.kt b/src/main/java/org/radarbase/management/web/rest/errors/FieldErrorVM.kt index ca4f5cc3d..c6b5494fa 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/FieldErrorVM.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/FieldErrorVM.kt @@ -2,14 +2,17 @@ package org.radarbase.management.web.rest.errors import java.io.Serializable -class FieldErrorVM /** * Create a new field error view-model. * @param dto the object name * @param field the field name * @param message the message - */(val objectName: String?, val field: String?, val message: String?) : Serializable { - + */ +class FieldErrorVM( + val objectName: String?, + val field: String?, + val message: String?, +) : Serializable { companion object { private const val serialVersionUID = 1L } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/InvalidRequestException.kt b/src/main/java/org/radarbase/management/web/rest/errors/InvalidRequestException.kt index 85e344ef4..3fbcf7856 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/InvalidRequestException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/InvalidRequestException.kt @@ -18,7 +18,7 @@ class InvalidRequestException : RadarWebApplicationException { HttpStatus.FORBIDDEN, message, entityName, - errorCode + errorCode, ) /** @@ -30,7 +30,9 @@ class InvalidRequestException : RadarWebApplicationException { * @param params map of additional information. */ constructor( - message: String?, entityName: String, errorCode: String?, - params: Map? + message: String?, + entityName: String, + errorCode: String?, + params: Map?, ) : super(HttpStatus.FORBIDDEN, message, entityName, errorCode, params) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/InvalidStateException.kt b/src/main/java/org/radarbase/management/web/rest/errors/InvalidStateException.kt index 4bf9d316d..cd5f3482c 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/InvalidStateException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/InvalidStateException.kt @@ -17,7 +17,7 @@ class InvalidStateException : RadarWebApplicationException { HttpStatus.INTERNAL_SERVER_ERROR, message, entityName, - errorCode + errorCode, ) /** @@ -29,7 +29,9 @@ class InvalidStateException : RadarWebApplicationException { * @param params map of additional information. */ constructor( - message: String?, entityName: String, errorCode: String?, - params: Map? + message: String?, + entityName: String, + errorCode: String?, + params: Map?, ) : super(HttpStatus.INTERNAL_SERVER_ERROR, message, entityName, errorCode, params) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/NotFoundException.kt b/src/main/java/org/radarbase/management/web/rest/errors/NotFoundException.kt index 4e071e791..31cf5f6f4 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/NotFoundException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/NotFoundException.kt @@ -22,7 +22,7 @@ class NotFoundException : RadarWebApplicationException { HttpStatus.NOT_FOUND, message, entityName, - errorCode + errorCode, ) /** @@ -34,7 +34,9 @@ class NotFoundException : RadarWebApplicationException { * @param paramMap map of additional information. */ constructor( - message: String, entityName: String, errorCode: String, - paramMap: Map? + message: String, + entityName: String, + errorCode: String, + paramMap: Map?, ) : super(HttpStatus.NOT_FOUND, message, entityName, errorCode, paramMap) } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationException.kt b/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationException.kt index 088340c08..473d52cdb 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationException.kt @@ -21,40 +21,44 @@ import java.util.* * * `"error.myCustomError" : "The server says {{param0}} to {{param1}}"` */ -open class RadarWebApplicationException @JvmOverloads constructor( - status: HttpStatus?, message: String?, entityName: String, - errorCode: String?, params: Map? = emptyMap() -) : ResponseStatusException(status, message, null) { - override val message: String? - val entityName: String - val errorCode: String? - private val paramMap: MutableMap = HashMap() - /** - * A base parameterized exception, which can be translated on the client side. - * @param status [HttpStatus] code. - * @param message message to client. - * @param entityName entityRelated from [EntityName] - * @param errorCode errorCode from [ErrorConstants] - * @param params map of optional information. - */ - /** - * Create an exception with the given parameters. This will be used to to create response - * body of the request. - * - * @param message Error message to the client - * @param entityName Entity related to the exception - * @param errorCode error code defined in MP if relevant. - */ - init { - // add default timestamp first, so a timestamp key in the paramMap will overwrite it - paramMap["timestamp"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) - .format(Date()) - paramMap.putAll(params!!) - this.message = message - this.entityName = entityName - this.errorCode = errorCode - } +open class RadarWebApplicationException + @JvmOverloads + constructor( + status: HttpStatus, + reason: String?, + entityName: String, + errorCode: String?, + params: Map? = emptyMap(), + ) : ResponseStatusException(status, reason, null) { + val entityName: String + val errorCode: String? + private val paramMap: MutableMap = HashMap() + + /** + * A base parameterized exception, which can be translated on the client side. + * @param status [HttpStatus] code. + * @param reason message to client. + * @param entityName entityRelated from [EntityName] + * @param errorCode errorCode from [ErrorConstants] + * @param params map of optional information. - val exceptionVM: RadarWebApplicationExceptionVM - get() = RadarWebApplicationExceptionVM(message, entityName, errorCode, paramMap) -} + * Create an exception with the given parameters. This will be used to to create response + * body of the request. + * + * @param reason Error message to the client + * @param entityName Entity related to the exception + * @param errorCode error code defined in MP if relevant. + */ + init { + // add default timestamp first, so a timestamp key in the paramMap will overwrite it + paramMap["timestamp"] = + SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) + .format(Date()) + if (params != null) paramMap.putAll(params) + this.entityName = entityName + this.errorCode = errorCode + } + + val exceptionVM: RadarWebApplicationExceptionVM + get() = RadarWebApplicationExceptionVM(message, entityName, errorCode, paramMap) + } diff --git a/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationExceptionVM.kt b/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationExceptionVM.kt index 6fc2c2404..298d0bacf 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationExceptionVM.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/RadarWebApplicationExceptionVM.kt @@ -6,22 +6,20 @@ import java.util.* /** * View Model for sending a [RadarWebApplicationException]. - */ -class RadarWebApplicationExceptionVM -/** + * Creates an error view model with message, entityName and errorCode. * * @param message message to client. * @param entityName entityRelated from [EntityName] * @param errorCode errorCode from [ErrorConstants] * @param params map of optional information. - */( + */ +class RadarWebApplicationExceptionVM( @field:JsonProperty val message: String?, @field:JsonProperty val entityName: String, @field:JsonProperty val errorCode: String?, - @field:JsonProperty val params: Map + @field:JsonProperty val params: Map, ) : Serializable { - /** * Creates an error view model with message, entityName and errorCode. * @@ -33,7 +31,7 @@ class RadarWebApplicationExceptionVM message, entityName, errorCode, - emptyMap() + emptyMap(), ) override fun equals(other: Any?): Boolean { @@ -47,15 +45,14 @@ class RadarWebApplicationExceptionVM return entityName == that.entityName && errorCode == that.errorCode && message == that.message && params == that.params } - override fun hashCode(): Int { - return Objects.hash(entityName, errorCode, message, params) - } + override fun hashCode(): Int = Objects.hash(entityName, errorCode, message, params) - override fun toString(): String { - return ("RadarWebApplicationExceptionVM{" + "entityName='" + entityName + '\'' - + ", errorCode='" + errorCode + '\'' + ", message='" + message + '\'' + ", params=" - + params + '}') - } + override fun toString(): String = + ( + "RadarWebApplicationExceptionVM{" + "entityName='" + entityName + '\'' + + ", errorCode='" + errorCode + '\'' + ", message='" + message + '\'' + ", params=" + + params + '}' + ) companion object { private const val serialVersionUID = 1L diff --git a/src/main/java/org/radarbase/management/web/rest/errors/RequestGoneException.kt b/src/main/java/org/radarbase/management/web/rest/errors/RequestGoneException.kt index 622aff31c..2b67cb767 100644 --- a/src/main/java/org/radarbase/management/web/rest/errors/RequestGoneException.kt +++ b/src/main/java/org/radarbase/management/web/rest/errors/RequestGoneException.kt @@ -19,7 +19,7 @@ class RequestGoneException : RadarWebApplicationException { HttpStatus.GONE, message, entityName, - errorCode + errorCode, ) /** @@ -31,7 +31,9 @@ class RequestGoneException : RadarWebApplicationException { * @param paramMap map of additional information. */ constructor( - message: String?, entityName: String, errorCode: String?, - paramMap: Map? + message: String?, + entityName: String, + errorCode: String?, + paramMap: Map?, ) : super(HttpStatus.GONE, message, entityName, errorCode, paramMap) } diff --git a/src/main/java/org/radarbase/management/web/rest/util/HeaderUtil.kt b/src/main/java/org/radarbase/management/web/rest/util/HeaderUtil.kt index f37849c23..96e6eb6a3 100644 --- a/src/main/java/org/radarbase/management/web/rest/util/HeaderUtil.kt +++ b/src/main/java/org/radarbase/management/web/rest/util/HeaderUtil.kt @@ -1,6 +1,6 @@ package org.radarbase.management.web.rest.util -import io.ktor.http.* +import io.ktor.http.Cookie import org.slf4j.LoggerFactory import org.springframework.http.HttpHeaders import java.io.UnsupportedEncodingException @@ -19,24 +19,30 @@ object HeaderUtil { * @param param the message parameters * @return the [HttpHeaders] */ - fun createAlert(message: String?, param: String?): HttpHeaders { + fun createAlert( + message: String?, + param: String?, + ): HttpHeaders { val headers = HttpHeaders() headers.add("X-managementPortalApp-alert", message) headers.add("X-managementPortalApp-params", param) return headers } - fun createEntityCreationAlert(entityName: String, param: String?): HttpHeaders { - return createAlert(APPLICATION_NAME + "." + entityName + ".created", param) - } + fun createEntityCreationAlert( + entityName: String, + param: String?, + ): HttpHeaders = createAlert(APPLICATION_NAME + "." + entityName + ".created", param) - fun createEntityUpdateAlert(entityName: String, param: String?): HttpHeaders { - return createAlert(APPLICATION_NAME + "." + entityName + ".updated", param) - } + fun createEntityUpdateAlert( + entityName: String, + param: String?, + ): HttpHeaders = createAlert(APPLICATION_NAME + "." + entityName + ".updated", param) - fun createEntityDeletionAlert(entityName: String, param: String?): HttpHeaders { - return createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param) - } + fun createEntityDeletionAlert( + entityName: String, + param: String?, + ): HttpHeaders = createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param) /** * Create headers to display a failure alert in the frontend. @@ -46,8 +52,9 @@ object HeaderUtil { * @return the [HttpHeaders] */ fun createFailureAlert( - entityName: String, errorKey: String, - defaultMessage: String? + entityName: String, + errorKey: String, + defaultMessage: String?, ): HttpHeaders { log.error("Entity creation failed, {}", defaultMessage) val headers = HttpHeaders() @@ -64,10 +71,11 @@ object HeaderUtil { * @return the [HttpHeaders] */ fun createExceptionAlert( - entityName: String?, errorKey: String?, - defaultMessage: String? + entityName: String?, + errorKey: String?, + defaultMessage: String?, ): HttpHeaders { - //TODO: Replace createFailureAlert with error. addition + // TODO: Replace createFailureAlert with error. addition log.error("Entity creation failed, {}", defaultMessage) val headers = HttpHeaders() headers.add("X-managementPortalApp-error", errorKey) @@ -86,39 +94,41 @@ object HeaderUtil { * @return A String where the components are URLEncoded and joined by forward slashes. */ fun buildPath(vararg components: String?): String { - return "/" + components - .filterNotNull() - .filter { it != "" } - .map { c: String? -> - // try-catch needs to be inside the lambda - try { - return@map URLEncoder.encode(c, "UTF-8") - } catch (ex: UnsupportedEncodingException) { - log.error(ex.message) - return@map "" - } - } - .reduce { a: String, b: String -> java.lang.String.join("/", a, b) } + return "/" + + components + .filterNotNull() + .filter { it != "" } + .map { c: String? -> + // try-catch needs to be inside the lambda + try { + return@map URLEncoder.encode(c, "UTF-8") + } catch (ex: UnsupportedEncodingException) { + log.error(ex.message) + return@map "" + } + }.reduce { a: String, b: String -> java.lang.String.join("/", a, b) } } - - /** * Custom cookie parser as the httprequest.cookies method cuts off '='. */ fun parseCookies(cookieHeader: String?): List { val result: List = listOf() if (cookieHeader != null) { - val cookiesRaw = cookieHeader.split("; ".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - return cookiesRaw.map{ - val parts = it.split("=".toRegex(), limit = 2).toTypedArray() - var value = if (parts.size > 1) parts[1] else "" - if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) { - value = value.substring(1, value.length - 1) - } - Cookie(name = parts[0], value = parts[1]) - }.toList() + val cookiesRaw = + cookieHeader + .split("; ".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + return cookiesRaw + .map { + val parts = it.split("=".toRegex(), limit = 2).toTypedArray() + var value = if (parts.size > 1) parts[1] else "" + if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length - 1) + } + Cookie(name = parts[0], value = parts[1]) + }.toList() } return result } diff --git a/src/main/java/org/radarbase/management/web/rest/util/PaginationUtil.kt b/src/main/java/org/radarbase/management/web/rest/util/PaginationUtil.kt index ad8ca3eb5..234706be4 100644 --- a/src/main/java/org/radarbase/management/web/rest/util/PaginationUtil.kt +++ b/src/main/java/org/radarbase/management/web/rest/util/PaginationUtil.kt @@ -27,18 +27,23 @@ object PaginationUtil { * @param baseUrl the base URL * @return the [HttpHeaders] */ - fun generatePaginationHttpHeaders(page: Page<*>?, baseUrl: String): HttpHeaders { + fun generatePaginationHttpHeaders( + page: Page<*>?, + baseUrl: String, + ): HttpHeaders { val headers = HttpHeaders() headers.add("X-Total-Count", page!!.totalElements.toString()) val link = StringBuilder(256) if (page.number + 1 < page.totalPages) { - link.append('<') + link + .append('<') .append(generateUri(baseUrl, page.number + 1, page.size)) .append(">; rel=\"next\",") } // prev link if (page.number > 0) { - link.append('<') + link + .append('<') .append(generateUri(baseUrl, page.number - 1, page.size)) .append(">; rel=\"prev\",") } @@ -47,7 +52,8 @@ object PaginationUtil { if (page.totalPages > 0) { lastPage = page.totalPages - 1 } - link.append('<') + link + .append('<') .append(generateUri(baseUrl, lastPage, page.size)) .append(">; rel=\"last\",<") .append(generateUri(baseUrl, 0, page.size)) @@ -64,29 +70,38 @@ object PaginationUtil { * @return the [HttpHeaders] */ fun generateSubjectPaginationHttpHeaders( - page: Page, baseUrl: String, criteria: SubjectCriteria + page: Page, + baseUrl: String, + criteria: SubjectCriteria, ): HttpHeaders { val headers = HttpHeaders() headers.add("X-Total-Count", page.totalElements.toString()) if (!page.isEmpty) { - val link = ('<' - .toString() + generateUri(page, baseUrl, criteria) - + ">; rel=\"next\"") + val link = ( + '<' + .toString() + generateUri(page, baseUrl, criteria) + + ">; rel=\"next\"" + ) headers.add(HttpHeaders.LINK, link) } return headers } - private fun generateUri(baseUrl: String, page: Int, size: Int): String { - return UriComponentsBuilder.fromUriString(baseUrl) + private fun generateUri( + baseUrl: String, + page: Int, + size: Int, + ): String = + UriComponentsBuilder + .fromUriString(baseUrl) .queryParam("page", page) .queryParam("size", size) .toUriString() - } private fun generateUri( - page: Page, baseUrl: String, - criteria: SubjectCriteria + page: Page, + baseUrl: String, + criteria: SubjectCriteria, ): String { val builder = UriComponentsBuilder.fromUriString(baseUrl) generateUriCriteriaRange(builder, "dateOfBirth", criteria.dateOfBirth) @@ -95,27 +110,34 @@ object PaginationUtil { generateUriParam(builder, "groupId", criteria.groupId) generateUriParam(builder, "personName", criteria.personName) generateUriParam( - builder, "humanReadableIdentifier", - criteria.humanReadableIdentifier + builder, + "humanReadableIdentifier", + criteria.humanReadableIdentifier, ) generateUriParam(builder, "projectName", criteria.projectName) generateUriParam(builder, "login", criteria.login) - criteria.authority.forEach(Consumer { a: SubjectAuthority? -> - generateUriParam( - builder, - "authority", a - ) - }) + criteria.authority.forEach( + Consumer { a: SubjectAuthority? -> + generateUriParam( + builder, + "authority", + a, + ) + }, + ) generateUriParam(builder, "size", criteria.size) generateUriParam(builder, "page", criteria.page) if (criteria.sort != null) { - criteria.parsedSort!!.forEach(Consumer { order: SubjectSortOrder? -> - generateUriParam( - builder, "sort", - order?.sortBy?.queryParam + ',' - + order?.direction?.name?.lowercase() - ) - }) + criteria.parsedSort!!.forEach( + Consumer { order: SubjectSortOrder? -> + generateUriParam( + builder, + "sort", + order?.sortBy?.queryParam + ',' + + order?.direction?.name?.lowercase(), + ) + }, + ) } val lastSubject = page.content[page.numberOfElements - 1] generateUriParam(builder, "last.id", lastSubject?.id) @@ -127,8 +149,9 @@ object PaginationUtil { } private fun generateUriCriteriaRange( - builder: UriComponentsBuilder, prefix: String, - range: CriteriaRange<*>? + builder: UriComponentsBuilder, + prefix: String, + range: CriteriaRange<*>?, ) { if (range == null) { return @@ -139,8 +162,9 @@ object PaginationUtil { } private fun generateUriParam( - builder: UriComponentsBuilder, name: String, - value: Any? + builder: UriComponentsBuilder, + name: String, + value: Any?, ) { if (value != null) { builder.queryParam(name, value) diff --git a/src/main/java/org/radarbase/management/web/rest/vm/LoggerVM.kt b/src/main/java/org/radarbase/management/web/rest/vm/LoggerVM.kt index 596752026..42e4cf684 100644 --- a/src/main/java/org/radarbase/management/web/rest/vm/LoggerVM.kt +++ b/src/main/java/org/radarbase/management/web/rest/vm/LoggerVM.kt @@ -18,10 +18,11 @@ class LoggerVM { @JsonCreator constructor() - override fun toString(): String { - return ("LoggerVM{" - + "name='" + name + '\'' - + ", level='" + level + '\'' - + '}') - } + override fun toString(): String = + ( + "LoggerVM{" + + "name='" + name + '\'' + + ", level='" + level + '\'' + + '}' + ) } diff --git a/src/main/java/org/radarbase/management/web/rest/vm/ManagedUserVM.kt b/src/main/java/org/radarbase/management/web/rest/vm/ManagedUserVM.kt index 831165595..4e906fb24 100644 --- a/src/main/java/org/radarbase/management/web/rest/vm/ManagedUserVM.kt +++ b/src/main/java/org/radarbase/management/web/rest/vm/ManagedUserVM.kt @@ -7,10 +7,11 @@ import javax.validation.constraints.Size * View Model extending the UserDTO, which is meant to be used in the user management UI. */ class ManagedUserVM : UserDTO() { - var password: @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) String? = null - override fun toString(): String { - return "ManagedUserVM{} " + super.toString() - } + var password: + @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) + String? = null + + override fun toString(): String = "ManagedUserVM{} " + super.toString() companion object { const val PASSWORD_MIN_LENGTH = 4 diff --git a/src/main/java/org/radarbase/management/web/rest/vm/package-info.kt b/src/main/java/org/radarbase/management/web/rest/vm/PackageInfo.kt similarity index 68% rename from src/main/java/org/radarbase/management/web/rest/vm/package-info.kt rename to src/main/java/org/radarbase/management/web/rest/vm/PackageInfo.kt index 52bd63ff5..76eb6085e 100644 --- a/src/main/java/org/radarbase/management/web/rest/vm/package-info.kt +++ b/src/main/java/org/radarbase/management/web/rest/vm/PackageInfo.kt @@ -1,5 +1,6 @@ /** * View Models used by Spring MVC REST controllers. */ -package org.radarbase.management.web.rest.vm +@file:Suppress("ktlint:standard:no-empty-file") +package org.radarbase.management.web.rest.vm diff --git a/src/main/resources/config/application-api-docs.yml b/src/main/resources/config/application-api-docs.yml index 0d1b821e8..814aecd39 100644 --- a/src/main/resources/config/application-api-docs.yml +++ b/src/main/resources/config/application-api-docs.yml @@ -14,5 +14,5 @@ # =================================================================== springdoc: - api-docs.enabled: true - swagger-ui.enabled: true + api-docs.enabled: true + swagger-ui.enabled: true diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 87e51ec0d..364e73e43 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -14,49 +14,49 @@ # =================================================================== spring: - devtools: - restart: - enabled: true - exclude: .h2.server.properties,static/**,templates/** - livereload: - enabled: false # we use gulp + BrowserSync for livereload - jackson: - serialization.indent_output: true - datasource: - url: jdbc:hsqldb:mem:managementportal;DB_CLOSE_DELAY=-1 - username: ManagementPortal - password: - h2: - console: - enabled: false - jpa: - database-platform: org.hibernate.dialect.HSQLDialect - database: HSQL - show-sql: true - properties: - hibernate.id.new_generator_mappings: true - hibernate.cache.use_second_level_cache: true - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: true - hibernate.cache.region.factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory - hibernate.cache.hazelcast.instance_name: ManagementPortal - hibernate.cache.use_minimal_puts: true - hibernate.cache.hazelcast.use_lite_member: true - hibernate.ddl-auto: none - mail: - host: # for hotmail - port: - username: user@example.com - password: XXXXXXXX - protocol: smtp - properties.mail.smtp: - auth: true - starttls.enable: true - ssl.trust: # for hotmail - thymeleaf: - cache: false - liquibase: - contexts: dev + devtools: + restart: + enabled: true + exclude: .h2.server.properties,static/**,templates/** + livereload: + enabled: false # we use gulp + BrowserSync for livereload + jackson: + serialization.indent_output: true + datasource: + url: jdbc:hsqldb:mem:managementportal;DB_CLOSE_DELAY=-1 + username: ManagementPortal + password: + h2: + console: + enabled: false + jpa: + database-platform: org.hibernate.dialect.HSQLDialect + database: HSQL + show-sql: true + properties: + hibernate.id.new_generator_mappings: true + hibernate.cache.use_second_level_cache: true + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: true + hibernate.cache.region.factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory + hibernate.cache.hazelcast.instance_name: ManagementPortal + hibernate.cache.use_minimal_puts: true + hibernate.cache.hazelcast.use_lite_member: true + hibernate.ddl-auto: none + mail: + host: # for hotmail + port: + username: user@example.com + password: XXXXXXXX + protocol: smtp + properties.mail.smtp: + auth: true + starttls.enable: true + ssl.trust: # for hotmail + thymeleaf: + cache: false + liquibase: + contexts: dev # =================================================================== # To enable SSL, generate a certificate using: @@ -76,13 +76,13 @@ spring: # keyAlias: ManagementPortal # =================================================================== server: - port: 8080 - servlet: - session.cookie.secure: false - context-path: /managementportal - session: - cookie: - path: / + port: 8080 + servlet: + session.cookie.secure: false + context-path: /managementportal + session: + cookie: + path: / @@ -91,34 +91,34 @@ server: # # =================================================================== managementportal: - common: - baseUrl: http://localhost:8080 # Modify according to your server's URL - managementPortalBaseUrl: http://localhost:8080/managementportal - privacyPolicyUrl: http://info.thehyve.nl/radar-cns-privacy-policy - adminPassword: admin - activationKeyTimeoutInSeconds: 86400 # 1 day - mail: # specific ManagementPortal mail property, for standard properties see MailProperties - from: ManagementPortal@localhost - frontend: - clientId: ManagementPortalapp - clientSecret: my-secret-token-to-change-in-production - accessTokenValiditySeconds: 14400 - refreshTokenValiditySeconds: 259200 - sessionTimeout : 86400 # session for rft cookie - oauth: - clientsFile: src/main/docker/etc/config/oauth_client_details.csv - metaTokenTimeout: PT1H #timeout should be specified as the ISO-8601 duration format {@code PnDTnHnMn.nS}. - persistentMetaTokenTimeout: P31D #timeout should be specified as the ISO-8601 duration format {@code PnDTnHnMn.nS}. - catalogueServer: - enableAutoImport: false - serverUrl: - siteSettings: - # The line below can be uncommented to add some hidden fields for UI testing - #hiddenSubjectFields: [person_name, date_of_birth, group] - identityServer: - serverUrl: http://localhost:4433 - serverAdminUrl: http://localhost:4434 - loginUrl: http://localhost:3000 + common: + baseUrl: http://localhost:8080 # Modify according to your server's URL + managementPortalBaseUrl: http://localhost:8080/managementportal + privacyPolicyUrl: http://info.thehyve.nl/radar-cns-privacy-policy + adminPassword: admin + activationKeyTimeoutInSeconds: 86400 # 1 day + mail: # specific ManagementPortal mail property, for standard properties see MailProperties + from: ManagementPortal@localhost + frontend: + clientId: ManagementPortalapp + clientSecret: my-secret-token-to-change-in-production + accessTokenValiditySeconds: 14400 + refreshTokenValiditySeconds: 259200 + sessionTimeout: 86400 # session for rft cookie + oauth: + clientsFile: src/main/docker/etc/config/oauth_client_details.csv + metaTokenTimeout: PT1H #timeout should be specified as the ISO-8601 duration format {@code PnDTnHnMn.nS}. + persistentMetaTokenTimeout: P31D #timeout should be specified as the ISO-8601 duration format {@code PnDTnHnMn.nS}. + catalogueServer: + enableAutoImport: false + serverUrl: + siteSettings: + # The line below can be uncommented to add some hidden fields for UI testing + #hiddenSubjectFields: [person_name, date_of_birth, group] + identityServer: + serverUrl: http://localhost:4433 + serverAdminUrl: http://localhost:4434 + loginUrl: http://localhost:3000 # =================================================================== # JHipster specific properties @@ -127,16 +127,16 @@ managementportal: # =================================================================== jhipster: - cache: # Cache configuration - hazelcast: # Hazelcast distributed cache - time-to-live-seconds: 3600 - backup-count: 1 - logging: - logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration - enabled: false - host: localhost - port: 5000 - queue-size: 512 + cache: # Cache configuration + hazelcast: # Hazelcast distributed cache + time-to-live-seconds: 3600 + backup-count: 1 + logging: + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + queue-size: 512 # =================================================================== # Application specific properties diff --git a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml index a5a5822bb..0c256b2f0 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -1,10 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -32,7 +30,7 @@ - + @@ -50,14 +48,14 @@ + tableName="radar_user" + unique="true"> + tableName="radar_user" + unique="true"> @@ -99,7 +97,7 @@ - + @@ -163,16 +161,16 @@ + baseTableName="oauth_client_token" + constraintName="fk_oauth_client_token_username" + referencedColumnNames="login" + referencedTableName="radar_user"/> - + @@ -181,10 +179,10 @@ + baseTableName="oauth_access_token" + constraintName="fk_oauth_access_token_username" + referencedColumnNames="login" + referencedTableName="radar_user"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000001_added_entity_Project.xml b/src/main/resources/config/liquibase/changelog/00000000000001_added_entity_Project.xml index ae81d164a..c6e2f7670 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000001_added_entity_Project.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000001_added_entity_Project.xml @@ -1,8 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -21,35 +21,35 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liqui - + - + - + - + - + - + - + - + diff --git a/src/main/resources/config/liquibase/changelog/00000000000002_added_entity_Source.xml b/src/main/resources/config/liquibase/changelog/00000000000002_added_entity_Source.xml index a3ba1d4c8..604399110 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000002_added_entity_Source.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000002_added_entity_Source.xml @@ -1,10 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -27,7 +25,7 @@ - + @@ -35,19 +33,19 @@ - + - + - + - + diff --git a/src/main/resources/config/liquibase/changelog/00000000000003_added_entity_SourceType.xml b/src/main/resources/config/liquibase/changelog/00000000000003_added_entity_SourceType.xml index 068f67b9a..e4846b3f6 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000003_added_entity_SourceType.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000003_added_entity_SourceType.xml @@ -1,10 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -23,23 +21,23 @@ - + - + - + - + - + @@ -52,19 +50,19 @@ - + - + - + - + diff --git a/src/main/resources/config/liquibase/changelog/00000000000004_added_entity_SourceData.xml b/src/main/resources/config/liquibase/changelog/00000000000004_added_entity_SourceData.xml index 3b7c05ec2..4d40be0d8 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000004_added_entity_SourceData.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000004_added_entity_SourceData.xml @@ -1,8 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -21,7 +21,7 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liqui - + @@ -29,47 +29,47 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liqui - + - + - + - + - + - + - + - + - + - + - + diff --git a/src/main/resources/config/liquibase/changelog/00000000000005_added_entity_Role.xml b/src/main/resources/config/liquibase/changelog/00000000000005_added_entity_Role.xml index 7dc838fe5..187edb28a 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000005_added_entity_Role.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000005_added_entity_Role.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -35,7 +33,7 @@ - + @@ -64,8 +62,8 @@ + tableName="radar_role" + unique="true"> diff --git a/src/main/resources/config/liquibase/changelog/00000000000006_added_entity_Subject.xml b/src/main/resources/config/liquibase/changelog/00000000000006_added_entity_Subject.xml index 24ae8b71b..d62d9c199 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000006_added_entity_Subject.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000006_added_entity_Subject.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -35,13 +33,13 @@ - + - + - + @@ -58,11 +56,10 @@ - + baseTableName="subject" + constraintName="fk_subject_user_id" + referencedColumnNames="id" + referencedTableName="radar_user"/> @@ -78,15 +75,15 @@ + baseTableName="subject_sources" + constraintName="fk_subject_sources_subjects_id" + referencedColumnNames="id" + referencedTableName="subject"/> + baseTableName="subject_sources" + constraintName="fk_subject_sources_sources_id" + referencedColumnNames="id" + referencedTableName="radar_source"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000007_added_source_metadata_entity.xml b/src/main/resources/config/liquibase/changelog/00000000000007_added_source_metadata_entity.xml index b75ca3618..8881577e5 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000007_added_source_metadata_entity.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000007_added_source_metadata_entity.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -35,17 +33,17 @@ - + - + + baseTableName="source_metadata" + constraintName="fk_source_metadata" + referencedColumnNames="id" + referencedTableName="radar_source"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000008_added_subject_metadata_entity.xml b/src/main/resources/config/liquibase/changelog/00000000000008_added_subject_metadata_entity.xml index 2966fb9ce..0bf865098 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000008_added_subject_metadata_entity.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000008_added_subject_metadata_entity.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -35,17 +33,17 @@ - + - + + baseTableName="subject_metadata" + constraintName="fk_subject_metadata" + referencedColumnNames="id" + referencedTableName="subject"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000009_added_project_metadata_entity.xml b/src/main/resources/config/liquibase/changelog/00000000000009_added_project_metadata_entity.xml index 1101c6eff..d7ffb5ad1 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000009_added_project_metadata_entity.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000009_added_project_metadata_entity.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -35,17 +33,17 @@ - + - + + baseTableName="project_metadata" + constraintName="fk_project_metadata" + referencedColumnNames="id" + referencedTableName="project"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000010_added_entity_constraints_Project.xml b/src/main/resources/config/liquibase/changelog/00000000000010_added_entity_constraints_Project.xml index 7f41bf069..031401f45 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000010_added_entity_constraints_Project.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000010_added_entity_constraints_Project.xml @@ -1,8 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -19,10 +19,10 @@ referencedColumnNames="id" referencedTableName="source_type"/> + baseTableName="project" + constraintName="fk_project_owner_id" + referencedColumnNames="id" + referencedTableName="radar_user"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000011_added_entity_constraints_Source.xml b/src/main/resources/config/liquibase/changelog/00000000000011_added_entity_constraints_Source.xml index f102a7c5f..85d80e2e3 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000011_added_entity_constraints_Source.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000011_added_entity_constraints_Source.xml @@ -1,8 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> diff --git a/src/main/resources/config/liquibase/changelog/00000000000012_added_entity_constraints_SourceType.xml b/src/main/resources/config/liquibase/changelog/00000000000012_added_entity_constraints_SourceType.xml index 5b47d41c0..f2ddbea18 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000012_added_entity_constraints_SourceType.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000012_added_entity_constraints_SourceType.xml @@ -1,17 +1,17 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + baseTableName="source_data" + constraintName="fk_source_data_source_type_id" + referencedColumnNames="id" + referencedTableName="source_type"/> diff --git a/src/main/resources/config/liquibase/changelog/00000000000013_added_entity_constraints_Role.xml b/src/main/resources/config/liquibase/changelog/00000000000013_added_entity_constraints_Role.xml index 8ee3e9856..8eb15128a 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000013_added_entity_constraints_Role.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000013_added_entity_constraints_Role.xml @@ -10,9 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> diff --git a/src/main/resources/config/liquibase/changelog/00000000000014_added_demo_data.xml b/src/main/resources/config/liquibase/changelog/00000000000014_added_demo_data.xml index 5c66c9ed5..eef0ecbd4 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000014_added_demo_data.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000014_added_demo_data.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + file="config/liquibase/users.csv" + separator=";" + tableName="radar_user"> diff --git a/src/main/resources/config/liquibase/changelog/20171220155600_drop_table_radar_user_authority.xml b/src/main/resources/config/liquibase/changelog/20171220155600_drop_table_radar_user_authority.xml index 013f88637..449d1e4d5 100644 --- a/src/main/resources/config/liquibase/changelog/20171220155600_drop_table_radar_user_authority.xml +++ b/src/main/resources/config/liquibase/changelog/20171220155600_drop_table_radar_user_authority.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - + + diff --git a/src/main/resources/config/liquibase/changelog/20180313103735_add_missing_fks.xml b/src/main/resources/config/liquibase/changelog/20180313103735_add_missing_fks.xml index da371b9a9..efa954fed 100644 --- a/src/main/resources/config/liquibase/changelog/20180313103735_add_missing_fks.xml +++ b/src/main/resources/config/liquibase/changelog/20180313103735_add_missing_fks.xml @@ -1,9 +1,18 @@ - + - + - + diff --git a/src/main/resources/config/liquibase/changelog/20180313103735_enable_envers_revisions.xml b/src/main/resources/config/liquibase/changelog/20180313103735_enable_envers_revisions.xml index 2632ba2b2..370c1ac14 100644 --- a/src/main/resources/config/liquibase/changelog/20180313103735_enable_envers_revisions.xml +++ b/src/main/resources/config/liquibase/changelog/20180313103735_enable_envers_revisions.xml @@ -1,5 +1,8 @@ - + @@ -340,13 +343,16 @@ - + - + - + @@ -358,13 +364,15 @@ - + - + @@ -373,103 +381,386 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - insert into _revisions_info values (1, 'system', ${now}) - insert into project_aud(id, rev, revtype, description, description_mod, end_date, end_date_mod, location, location_mod, jhi_organization, organization_mod, project_name, project_name_mod, project_status, project_status_mod, start_date, start_date_mod, attributes_mod, roles_mod, source_types_mod) select id, 1, 0, description, true, end_date, true, location, true, jhi_organization, true, project_name, true, project_status, true, start_date, true, true, true, true from project - insert into project_metadata_aud(id, rev, revtype, attribute_key, attribute_value) select id, 1, 0, attribute_key, attribute_value from project_metadata - insert into project_source_type_aud(rev, revtype, projects_id, source_types_id) select 1, 0, projects_id, source_types_id from project_source_type - insert into radar_authority_aud(rev, revtype, name) select 1, 0, name from radar_authority - insert into radar_role_aud(id, rev, revtype, authority_name, authority_mod, project_id, project_mod, users_mod) select id, 1, 0, authority_name, true, project_id, true, true from radar_role - insert into radar_source_aud(id, rev, revtype, assigned, assigned_mod, expected_source_name, expected_source_name_mod, source_id, source_id_mod, source_name, source_name_mod, attributes_mod, project_id, project_mod, source_type_id, source_type_mod, subject_id, subject_mod) select id, 1, 0, assigned, true, expected_source_name, true, source_id, true, source_name, true, true, project_id, true, source_type_id, true, subject_id, true from radar_source - insert into radar_user_aud(id, rev, revtype, activated, activated_mod, activation_key, activation_key_mod, email, email_mod, first_name, first_name_mod, lang_key, lang_key_mod, last_name, last_name_mod, login, login_mod, password_hash, password_mod, reset_date, reset_date_mod, reset_key, reset_key_mod, roles_mod) select id, 1, 0, activated, true, activation_key, true, email, true, first_name, true, lang_key, true, last_name, true, login, true, password_hash, true, reset_date, true, reset_key, true, true from radar_user - insert into role_users_aud(rev, revtype, users_id, roles_id) select 1, 0, users_id, roles_id from role_users - insert into source_data_aud(id, rev, revtype, data_class, data_class_mod, enabled, enabled_mod, frequency, frequency_mod, key_schema, key_schema_mod, processing_state, processing_state_mod, provider, provider_mod, source_data_name, source_data_name_mod, source_data_type, source_data_type_mod, topic, topic_mod, unit, unit_mod, value_schema, value_schema_mod, source_type_id, source_type_mod) select id, 1, 0, data_class, true, enabled, true, frequency, true, key_schema, true, processing_state, true, provider, true, source_data_name, true, source_data_type, true, topic, true, unit, true, value_schema, true, source_type_id, true from source_data - insert into source_metadata_aud(id, rev, revtype, attribute_key, attribute_value) select id, 1, 0, attribute_key, attribute_value from source_metadata - insert into source_type_aud(id, rev, revtype, app_provider, app_provider_mod, assessment_type, assessment_type_mod, dynamic_registration, can_register_dynamically_mod, catalog_version, catalog_version_mod, description, description_mod, model, model_mod, name, name_mod, producer, producer_mod, source_type_scope, source_type_scope_mod, projects_mod, source_data_mod) select id, 1, 0, app_provider, true, assessment_type, true, dynamic_registration, true, catalog_version, true, description, true, model, true, name, true, producer, true, source_type_scope, true, true, true from source_type - insert into subject_aud(id, rev, revtype, external_id, external_id_mod, external_link, external_link_mod, removed, removed_mod, attributes_mod, sources_mod, user_id, user_mod) select id, 1, 0, external_id, true, external_link, true, removed, true, true, true, user_id, true from subject - insert into subject_metadata_aud(id, rev, revtype, attribute_key, attribute_value) select id, 1, 0, attribute_key, attribute_value from subject_metadata + insert into _revisions_info + values (1, 'system', ${now}) + insert into project_aud(id, rev, revtype, description, description_mod, end_date, end_date_mod, location, + location_mod, jhi_organization, organization_mod, project_name, project_name_mod, + project_status, + project_status_mod, start_date, start_date_mod, attributes_mod, roles_mod, + source_types_mod) + select id, + 1, + 0, + description, + true, + end_date, + true, + location, + true, + jhi_organization, + true, + project_name, + true, + project_status, + true, + start_date, + true, + true, + true, + true + from project + + insert into project_metadata_aud(id, rev, revtype, attribute_key, attribute_value) + select id, + 1, + 0, + attribute_key, + attribute_value + from project_metadata + + insert into project_source_type_aud(rev, revtype, projects_id, source_types_id) + select 1, + 0, + projects_id, + source_types_id + from project_source_type + + insert into radar_authority_aud(rev, revtype, name) + select 1, 0, name + from radar_authority + insert into radar_role_aud(id, rev, revtype, authority_name, authority_mod, project_id, project_mod, + users_mod) + select id, + 1, + 0, + authority_name, + true, + project_id, + true, + true + from radar_role + + insert into radar_source_aud(id, rev, revtype, assigned, assigned_mod, expected_source_name, + expected_source_name_mod, source_id, source_id_mod, source_name, + source_name_mod, attributes_mod, + project_id, project_mod, source_type_id, source_type_mod, subject_id, + subject_mod) + select id, + 1, + 0, + assigned, + true, + expected_source_name, + true, + source_id, + true, + source_name, + true, + true, + project_id, + true, + source_type_id, + true, + subject_id, + true + from radar_source + + insert into radar_user_aud(id, rev, revtype, activated, activated_mod, activation_key, activation_key_mod, + email, email_mod, first_name, first_name_mod, lang_key, lang_key_mod, last_name, + last_name_mod, login, + login_mod, password_hash, password_mod, reset_date, reset_date_mod, reset_key, + reset_key_mod, roles_mod) + select id, + 1, + 0, + activated, + true, + activation_key, + true, + email, + true, + first_name, + true, + lang_key, + true, + last_name, + true, + login, + true, + password_hash, + true, + reset_date, + true, + reset_key, + true, + true + from radar_user + + insert into role_users_aud(rev, revtype, users_id, roles_id) + select 1, 0, users_id, roles_id + from role_users + + insert into source_data_aud(id, rev, revtype, data_class, data_class_mod, enabled, enabled_mod, frequency, + frequency_mod, key_schema, key_schema_mod, processing_state, + processing_state_mod, provider, provider_mod, + source_data_name, source_data_name_mod, source_data_type, source_data_type_mod, + topic, topic_mod, unit, + unit_mod, value_schema, value_schema_mod, source_type_id, source_type_mod) + select id, + 1, + 0, + data_class, + true, + enabled, + true, + frequency, + true, + key_schema, + true, + processing_state, + true, + provider, + true, + source_data_name, + true, + source_data_type, + true, + topic, + true, + unit, + true, + value_schema, + true, + source_type_id, + true + from source_data + + insert into source_metadata_aud(id, rev, revtype, attribute_key, attribute_value) + select id, + 1, + 0, + attribute_key, + attribute_value + from source_metadata + + insert into source_type_aud(id, rev, revtype, app_provider, app_provider_mod, assessment_type, + assessment_type_mod, dynamic_registration, can_register_dynamically_mod, + catalog_version, + catalog_version_mod, description, description_mod, model, model_mod, name, + name_mod, producer, producer_mod, + source_type_scope, source_type_scope_mod, projects_mod, source_data_mod) + select id, + 1, + 0, + app_provider, + true, + assessment_type, + true, + dynamic_registration, + true, + catalog_version, + true, + description, + true, + model, + true, + name, + true, + producer, + true, + source_type_scope, + true, + true, + true + from source_type + + insert into subject_aud(id, rev, revtype, external_id, external_id_mod, external_link, external_link_mod, + removed, removed_mod, attributes_mod, sources_mod, user_id, user_mod) + select id, + 1, + 0, + external_id, + true, + external_link, + true, + removed, + true, + true, + true, + user_id, + true + from subject + + insert into subject_metadata_aud(id, rev, revtype, attribute_key, attribute_value) + select id, + 1, + 0, + attribute_key, + attribute_value + from subject_metadata + @@ -532,6 +823,9 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20180323164200_add_role_inactive_participant.xml b/src/main/resources/config/liquibase/changelog/20180323164200_add_role_inactive_participant.xml index 5fb2349ed..f6b5fd166 100644 --- a/src/main/resources/config/liquibase/changelog/20180323164200_add_role_inactive_participant.xml +++ b/src/main/resources/config/liquibase/changelog/20180323164200_add_role_inactive_participant.xml @@ -10,11 +10,9 @@ --> + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - + - + + constraintName="fk_source_subject" referencedTableName="subject" + referencedColumnNames="id"/> - UPDATE radar_source source SET subject_id = ( - SELECT subjects_id FROM subject_sources WHERE sources_id = source.id limit 1 - ) + UPDATE radar_source source + SET subject_id = (SELECT subjects_id + FROM subject_sources + WHERE sources_id = source.id limit 1 + ) diff --git a/src/main/resources/config/liquibase/changelog/20180711141300_drop_project_admin.xml b/src/main/resources/config/liquibase/changelog/20180711141300_drop_project_admin.xml index 07aca5dbd..1312b4283 100644 --- a/src/main/resources/config/liquibase/changelog/20180711141300_drop_project_admin.xml +++ b/src/main/resources/config/liquibase/changelog/20180711141300_drop_project_admin.xml @@ -1,5 +1,8 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20180724105800_add_radar-meta_token.xml b/src/main/resources/config/liquibase/changelog/20180724105800_add_radar-meta_token.xml index 7d59ebe0a..9f13cb36c 100644 --- a/src/main/resources/config/liquibase/changelog/20180724105800_add_radar-meta_token.xml +++ b/src/main/resources/config/liquibase/changelog/20180724105800_add_radar-meta_token.xml @@ -1,9 +1,8 @@ - + @@ -15,7 +14,7 @@ - + @@ -47,6 +46,9 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20180813173100_add_subject_to_meta_token.xml b/src/main/resources/config/liquibase/changelog/20180813173100_add_subject_to_meta_token.xml index fae0742cc..65253c91e 100644 --- a/src/main/resources/config/liquibase/changelog/20180813173100_add_subject_to_meta_token.xml +++ b/src/main/resources/config/liquibase/changelog/20180813173100_add_subject_to_meta_token.xml @@ -1,9 +1,8 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20180824131400_add_client_id_to_meta_token.xml b/src/main/resources/config/liquibase/changelog/20180824131400_add_client_id_to_meta_token.xml index 198e69b3f..d3bbaa14f 100644 --- a/src/main/resources/config/liquibase/changelog/20180824131400_add_client_id_to_meta_token.xml +++ b/src/main/resources/config/liquibase/changelog/20180824131400_add_client_id_to_meta_token.xml @@ -1,9 +1,7 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20180824131400_add_meta_token_mod_to_subject.xml b/src/main/resources/config/liquibase/changelog/20180824131400_add_meta_token_mod_to_subject.xml index 4d6f87069..da9d1ba59 100644 --- a/src/main/resources/config/liquibase/changelog/20180824131400_add_meta_token_mod_to_subject.xml +++ b/src/main/resources/config/liquibase/changelog/20180824131400_add_meta_token_mod_to_subject.xml @@ -1,9 +1,7 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20200504145200_add_meta_token_persistence.xml b/src/main/resources/config/liquibase/changelog/20200504145200_add_meta_token_persistence.xml index 890d77d09..aa9e144d4 100644 --- a/src/main/resources/config/liquibase/changelog/20200504145200_add_meta_token_persistence.xml +++ b/src/main/resources/config/liquibase/changelog/20200504145200_add_meta_token_persistence.xml @@ -1,19 +1,17 @@ - + - + - - + + diff --git a/src/main/resources/config/liquibase/changelog/20210920101000_add_groups.xml b/src/main/resources/config/liquibase/changelog/20210920101000_add_groups.xml index 82c8aa875..655cb2fb0 100644 --- a/src/main/resources/config/liquibase/changelog/20210920101000_add_groups.xml +++ b/src/main/resources/config/liquibase/changelog/20210920101000_add_groups.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -29,10 +27,10 @@ - + - + @@ -66,7 +64,7 @@ - + diff --git a/src/main/resources/config/liquibase/changelog/20210929120000_add_date_of_birth.xml b/src/main/resources/config/liquibase/changelog/20210929120000_add_date_of_birth.xml index 98f304474..a51309cd8 100644 --- a/src/main/resources/config/liquibase/changelog/20210929120000_add_date_of_birth.xml +++ b/src/main/resources/config/liquibase/changelog/20210929120000_add_date_of_birth.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.5.xsd"> @@ -26,11 +26,11 @@ and I want to keep the old 201708301031-2 changeset intact. --> + file="config/liquibase/subject_group.csv" + primaryKey="id" + separator=";" + tableName="subject" + onlyUpdate="true"> diff --git a/src/main/resources/config/liquibase/changelog/20211110160000_add_organization.xml b/src/main/resources/config/liquibase/changelog/20211110160000_add_organization.xml index e14bd034f..62e9383a0 100644 --- a/src/main/resources/config/liquibase/changelog/20211110160000_add_organization.xml +++ b/src/main/resources/config/liquibase/changelog/20211110160000_add_organization.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> @@ -27,13 +25,13 @@ - + - + - + @@ -114,19 +112,20 @@ - INSERT into radar_organization_aud ( - id, rev, revtype, - - description, description_mod, - location, location_mod, - name, name_mod - ) - SELECT - id, 1, 0, - - description, true, - location, true, - name, true + INSERT into radar_organization_aud (id, rev, revtype, + description, description_mod, + location, location_mod, + name, name_mod) + SELECT id, + 1, + 0, + + description, + true, + location, + true, + name, + true FROM radar_organization diff --git a/src/main/resources/config/liquibase/changelog/20211202145000_add_organization_demo_data.xml b/src/main/resources/config/liquibase/changelog/20211202145000_add_organization_demo_data.xml index 20b0ffc13..8beeffc7b 100644 --- a/src/main/resources/config/liquibase/changelog/20211202145000_add_organization_demo_data.xml +++ b/src/main/resources/config/liquibase/changelog/20211202145000_add_organization_demo_data.xml @@ -1,8 +1,8 @@ @@ -14,26 +14,71 @@ ROLE_ORGANIZATION_ADMIN - insert into radar_role_aud(id, rev, revtype, authority_name, authority_mod, project_id, project_mod, organization_id, organization_mod, users_mod) select id, 1, 0, authority_name, true, project_id, true, organization_id, true, true from radar_role WHERE id = 6 + insert into radar_role_aud(id, rev, revtype, authority_name, authority_mod, project_id, project_mod, + organization_id, organization_mod, users_mod) + select id, + 1, + 0, + authority_name, + true, + project_id, + true, + organization_id, + true, + true + from radar_role + WHERE id = 6 - insert into radar_user_aud(id, rev, revtype, activated, activated_mod, activation_key, activation_key_mod, email, email_mod, first_name, first_name_mod, lang_key, lang_key_mod, last_name, last_name_mod, login, login_mod, password_hash, password_mod, reset_date, reset_date_mod, reset_key, reset_key_mod, roles_mod) select id, 1, 0, activated, true, activation_key, true, email, true, first_name, true, lang_key, true, last_name, true, login, true, password_hash, true, reset_date, true, reset_key, true, true from radar_user where id = 55 + insert into radar_user_aud(id, rev, revtype, activated, activated_mod, activation_key, activation_key_mod, + email, email_mod, first_name, first_name_mod, lang_key, lang_key_mod, last_name, + last_name_mod, login, login_mod, password_hash, password_mod, reset_date, + reset_date_mod, reset_key, reset_key_mod, roles_mod) + select id, + 1, + 0, + activated, + true, + activation_key, + true, + email, + true, + first_name, + true, + lang_key, + true, + last_name, + true, + login, + true, + password_hash, + true, + reset_date, + true, + reset_key, + true, + true + from radar_user + where id = 55 - insert into role_users_aud(rev, revtype, users_id, roles_id) select 1, 0, users_id, roles_id from role_users where users_id = 55 + insert into role_users_aud(rev, revtype, users_id, roles_id) + select 1, 0, users_id, roles_id + from role_users + where users_id = 55 diff --git a/src/main/resources/config/liquibase/changelog/20230908103500_expand_subject_demo_data_name_dob.xml b/src/main/resources/config/liquibase/changelog/20230908103500_expand_subject_demo_data_name_dob.xml index 5408bed49..cc98f159a 100644 --- a/src/main/resources/config/liquibase/changelog/20230908103500_expand_subject_demo_data_name_dob.xml +++ b/src/main/resources/config/liquibase/changelog/20230908103500_expand_subject_demo_data_name_dob.xml @@ -1,19 +1,19 @@ - + ID in (1, 2, 3, 4, 5, 6, 7, 8) - + ID in (1, 2, 3, 4, 9, 10, 11, 12) diff --git a/src/main/resources/config/liquibase/changelog/20231114150000_add_user_identity.xml b/src/main/resources/config/liquibase/changelog/20231114150000_add_user_identity.xml index e11c7350e..d533aa6b3 100644 --- a/src/main/resources/config/liquibase/changelog/20231114150000_add_user_identity.xml +++ b/src/main/resources/config/liquibase/changelog/20231114150000_add_user_identity.xml @@ -10,11 +10,9 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 1c729464b..889a7319c 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -3,17 +3,15 @@ error.title=Your request cannot be processed error.subtitle=Sorry, an error has occurred. error.status=Status: error.message=Message: - # Activation email email.activation.title=Your account has been created in ManagementPortal from RADAR-Base. -email.activation.greeting=Dear +email.activation.greeting=Dear email.activation.text1=Your JHipster account has been created by RADAR-base team. email.activation.text2=Please click on the URL below to to activate your account by resetting your password: email.activation.loginIntro=Your username is : {0} email.activation.expiry=Please note that this link will expire in {0} from the time it is sent. email.activation.regards=Regards, email.signature=ManagementPortal Team. - # Creation email email.creation.title=Your account has been created in ManagementPortal from RADAR-Base. email.creation.greeting=Dear @@ -23,7 +21,6 @@ email.creation.loginIntro=Your username is : {0} email.creation.expiry=Please note that this link will expire in {0} from the time it is sent. email.creation.onetimeonly=For security reasons this link can only be used once to successfully reset the password. email.creation.regards=Regards, - # Reset email email.reset.title=ManagementPortal password reset email.reset.greeting=Dear {0} diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties index 2bf395e17..3684a2a82 100644 --- a/src/main/resources/i18n/messages_en.properties +++ b/src/main/resources/i18n/messages_en.properties @@ -3,7 +3,6 @@ error.title=Your request cannot be processed error.subtitle=Sorry, an error has occurred. error.status=Status: error.message=Message: - # Activation email email.activation.title=Your account has been created in ManagementPortal from RADAR-Base. email.activation.greeting=Dear {0} @@ -13,7 +12,6 @@ email.activation.loginIntro=Your username is: {0} email.activation.expiry=Please note that this link will expire in {0} from the time it is sent. email.activation.regards=Regards, email.signature=ManagementPortal Team. - # Creation email email.creation.title=Your account has been created in ManagementPortal from RADAR-Base. email.creation.greeting=Dear @@ -23,7 +21,6 @@ email.creation.loginIntro=Your username is: {0} email.creation.expiry=Please note that this link will expire in {0} hours from the time it is sent. email.creation.onetimeonly=For security reasons this link can only be used once to successfully reset the password. email.creation.regards=Regards, - # Reset email email.reset.title=ManagementPortal password reset email.reset.greeting=Dear {0} diff --git a/src/main/resources/i18n/messages_nl.properties b/src/main/resources/i18n/messages_nl.properties index 2b34a858b..a4a3f6027 100644 --- a/src/main/resources/i18n/messages_nl.properties +++ b/src/main/resources/i18n/messages_nl.properties @@ -3,14 +3,12 @@ error.title=Uw verzoek kon niet worden verwerkt error.subtitle=Onze excuses, er is een fout opgetreden. error.status=Status: error.message=Boodschap: - # Activation email email.activation.title=ManagementPortal account activeren email.activation.greeting=Beste {0} email.activation.text1=Uw ManagementPortal account werd gecreëerd, gelieve op de onderstaande URL the klikken om te activeren: email.activation.text2=Met vriendelijke groeten, email.signature=ManagementPortal Team. - # Creation email email.creation.title=Uw account is gemaakt in ManagementPortal vanuit RADAR-Base. email.creation.greeting=Beste @@ -20,7 +18,6 @@ email.creation.loginIntro=Jouw gebruikersnaam is: {0} email.creation.expiry=Houd er rekening mee dat deze link vervalt in {0} uur vanaf het moment dat deze wordt verzonden. email.creation.onetimeonly=Om veiligheidsredenen kan deze link maar één keer worden gebruikt om het wachtwoord succesvol opnieuw in te stellen. email.creation.regards=Groeten, - # Reset email email.reset.title=ManagementPortal wachtwoord reset email.reset.greeting=Beste {0} diff --git a/src/main/resources/mails/activationEmail.html b/src/main/resources/mails/activationEmail.html index 252ec3c71..9ca49292e 100644 --- a/src/main/resources/mails/activationEmail.html +++ b/src/main/resources/mails/activationEmail.html @@ -1,33 +1,33 @@ - - JHipster activation - - - -

- Dear -

-

- Your JHipster account has been created by RADAR-base team. -

-

- Your user name is -

+ + JHipster activation + + + +

+ Dear +

+

+ Your JHipster account has been created by RADAR-base team. +

+

+ Your user name is +

-

- Please click on this link below to activate your account by resetting your password. -

-

- Activation Link -

-

-

-

- Regards, -
- JHipster. -

- +

+ Please click on this link below to activate your account by resetting your password. +

+

+ Activation Link +

+

+

+

+ Regards, +
+ JHipster. +

+ diff --git a/src/main/resources/mails/creationEmail.html b/src/main/resources/mails/creationEmail.html index 5b6646e6a..2cb614f5d 100644 --- a/src/main/resources/mails/creationEmail.html +++ b/src/main/resources/mails/creationEmail.html @@ -1,36 +1,36 @@ - - JHipster creation - - - -

- Dear -

-

- Your JHipster account has been created by RADAR-base team. -

-

- Your user name is -

+ + JHipster creation + + + +

+ Dear +

+

+ Your JHipster account has been created by RADAR-base team. +

+

+ Your user name is +

-

- Please click on this link below to activate your account by resetting your password. -

-

- login -

-

-

+

+ Please click on this link below to activate your account by resetting your password. +

+

+ login +

+

+

-

-

-

- Regards, -
- JHipster. -

- +

+

+

+ Regards, +
+ JHipster. +

+ diff --git a/src/main/resources/mails/passwordResetEmail.html b/src/main/resources/mails/passwordResetEmail.html index 30d5f62c6..a439ad209 100644 --- a/src/main/resources/mails/passwordResetEmail.html +++ b/src/main/resources/mails/passwordResetEmail.html @@ -1,24 +1,24 @@ - - JHipster password reset - - - -

- Dear -

-

- For your JHipster account a password reset was requested, please click on the URL below to reset it: -

-

- Reset Link -

-

- Regards, -
- JHipster. -

- + + JHipster password reset + + + +

+ Dear +

+

+ For your JHipster account a password reset was requested, please click on the URL below to reset it: +

+

+ Reset Link +

+

+ Regards, +
+ JHipster. +

+ diff --git a/src/main/resources/templates/activationEmail.html b/src/main/resources/templates/activationEmail.html index 252ec3c71..9ca49292e 100644 --- a/src/main/resources/templates/activationEmail.html +++ b/src/main/resources/templates/activationEmail.html @@ -1,33 +1,33 @@ - - JHipster activation - - - -

- Dear -

-

- Your JHipster account has been created by RADAR-base team. -

-

- Your user name is -

+ + JHipster activation + + + +

+ Dear +

+

+ Your JHipster account has been created by RADAR-base team. +

+

+ Your user name is +

-

- Please click on this link below to activate your account by resetting your password. -

-

- Activation Link -

-

-

-

- Regards, -
- JHipster. -

- +

+ Please click on this link below to activate your account by resetting your password. +

+

+ Activation Link +

+

+

+

+ Regards, +
+ JHipster. +

+ diff --git a/src/main/resources/templates/authorize.html b/src/main/resources/templates/authorize.html index ce39f90ef..ac551f2b1 100644 --- a/src/main/resources/templates/authorize.html +++ b/src/main/resources/templates/authorize.html @@ -2,16 +2,16 @@ - + Confirm Access + - - + th:href="@{/css/oauth.css}"/> + - + @@ -22,19 +22,19 @@ Do you authorize at to access your protected resources? Check the resources for which you want to grant access: -
+
- +
-
- + +
diff --git a/src/main/resources/templates/creationEmail.html b/src/main/resources/templates/creationEmail.html index 5b6646e6a..2cb614f5d 100644 --- a/src/main/resources/templates/creationEmail.html +++ b/src/main/resources/templates/creationEmail.html @@ -1,36 +1,36 @@ - - JHipster creation - - - -

- Dear -

-

- Your JHipster account has been created by RADAR-base team. -

-

- Your user name is -

+ + JHipster creation + + + +

+ Dear +

+

+ Your JHipster account has been created by RADAR-base team. +

+

+ Your user name is +

-

- Please click on this link below to activate your account by resetting your password. -

-

- login -

-

-

+

+ Please click on this link below to activate your account by resetting your password. +

+

+ login +

+

+

-

-

-

- Regards, -
- JHipster. -

- +

+

+

+ Regards, +
+ JHipster. +

+ diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 1c9b66738..a88f1a96d 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -2,12 +2,12 @@ - + Your request cannot be processed + - + th:href="@{/css/oauth.css}"/>
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 465900dbb..9919216c1 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -2,36 +2,36 @@ - + RADAR-base Central Login + - + th:href="@{/css/oauth.css}"/> - +