diff --git a/README.md b/README.md index 9ebe9e8..f3fe79f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,24 @@ Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfiguri * `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort * `APP_PSEUDONYMIZE_GPAS_SSLCALOCATION`: Root Zertifikat für gPas, falls es dediziert hinzugefügt werden muss. +### Anmeldung mit einem Passwort + +Ein initialer Administrator-Account kann optional konfiguriert werden und sorgt dafür, dass bestimmte Bereiche nur nach +einem erfolgreichen Login erreichbar sind. + +* `APP_SECURITY_ADMIN_USER`: Muss angegeben werden zur Aktivierung der Zugriffsbeschränkung. +* `APP_SECURITY_ADMIN_PASSWORD`: Das Passwort für den Administrator (Empfohlen). + +Wird kein Administrator-Passwort angegeben, wird ein zufälliger Wert generiert und beim Start der Anwendung in den Logs +angezeigt. + +#### Auswirkungen auf den dargestellten Inhalt + +Nur Administratoren haben Zugriff auf den Konfigurationsbereich, nur angemeldete Benutzer können die anonymisierte oder +pseudonymisierte Patienten-ID einsehen. + +Wurde kein Administrator-Account konfiguriert, sind diese Inhalte generell nicht verfügbar. + ### Transformation von Werten In Onkostar kann es vorkommen, dass ein Wert eines Merkmalskatalogs an einem Standort angepasst wurde und dadurch nicht dem Wert entspricht, diff --git a/build.gradle.kts b/build.gradle.kts index f679c4f..55b8346 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.kafka:spring-kafka") implementation("org.flywaydb:flyway-mysql") diff --git a/src/main/kotlin/dev/dnpm/etl/processor/EtlProcessorApplication.kt b/src/main/kotlin/dev/dnpm/etl/processor/EtlProcessorApplication.kt index 5d28c97..4b9b307 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/EtlProcessorApplication.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/EtlProcessorApplication.kt @@ -20,9 +20,10 @@ package dev.dnpm.etl.processor import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration import org.springframework.boot.runApplication -@SpringBootApplication +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) class EtlProcessorApplication fun main(args: Array) { diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt index 6b85603..9c92869 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt @@ -1,7 +1,7 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -76,6 +76,16 @@ data class KafkaTargetProperties( } } +@ConfigurationProperties(SecurityConfigProperties.NAME) +data class SecurityConfigProperties( + val adminUser: String?, + val adminPassword: String?, +) { + companion object { + const val NAME = "app.security" + } +} + enum class PseudonymGenerator { BUILDIN, GPAS diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt new file mode 100644 index 0000000..68eb629 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt @@ -0,0 +1,98 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package dev.dnpm.etl.processor.config + +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.invoke +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.crypto.factory.PasswordEncoderFactories +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.provisioning.InMemoryUserDetailsManager +import org.springframework.security.web.SecurityFilterChain +import java.util.* + + +@Configuration +@EnableConfigurationProperties( + value = [ + SecurityConfigProperties::class + ] +) +@ConditionalOnProperty(value = ["app.security.admin-user"]) +@EnableWebSecurity +class AppSecurityConfiguration( + private val securityConfigProperties: SecurityConfigProperties +) { + + private val logger = LoggerFactory.getLogger(AppSecurityConfiguration::class.java) + + @Bean + fun userDetailsService(passwordEncoder: PasswordEncoder): InMemoryUserDetailsManager { + val adminUser = if (securityConfigProperties.adminUser.isNullOrBlank()) { + logger.warn("Using random Admin User: admin") + "admin" + } else { + securityConfigProperties.adminUser + } + val adminPassword = if (securityConfigProperties.adminPassword.isNullOrBlank()) { + val random = UUID.randomUUID().toString() + logger.warn("Using random Admin Passwort: {}", random) + random + } else { + securityConfigProperties.adminPassword + } + + val user: UserDetails = User.withUsername(adminUser) + .password(passwordEncoder.encode(adminPassword)) + .roles("ADMIN") + .build() + + return InMemoryUserDetailsManager(user) + } + + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + http { + authorizeRequests { + authorize("/configs/**", hasRole("ADMIN")) + authorize(anyRequest, permitAll) + } + formLogin { + loginPage = "/login" + } + csrf { disable() } + } + return http.build() + } + + @Bean + fun passwordEncoder(): PasswordEncoder { + return PasswordEncoderFactories.createDelegatingPasswordEncoder() + } + +} + diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt new file mode 100644 index 0000000..02c98cf --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/web/LoginController.kt @@ -0,0 +1,33 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package dev.dnpm.etl.processor.web + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping + +@Controller +class LoginController { + + @GetMapping(path = ["/login"]) + fun login(): String { + return "login" + } + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index dabe84b..d538338 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -10,6 +10,9 @@ app: topic: test response-topic: test_response servers: localhost:9094 + #security: + # admin-user: admin + # admin-password: very-secret server: port: 8000 diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css index b2ba085..fee5d4c 100644 --- a/src/main/resources/static/style.css +++ b/src/main/resources/static/style.css @@ -74,20 +74,25 @@ nav > ul { nav > ul > li { display: inline-block; padding: 0 1rem; +} + +nav > ul > li.login { + margin: 0 0 0 1em; + padding: 0 0 0 2em; border-left: 1px solid var(--table-border); } -nav > ul > li:first-of-type { - border-left: none; -} - nav li a { - color: #004a8f; + color: var(--bg-blue); text-transform: uppercase; text-decoration: none; font-weight: 700; } +nav li.login a { + color: var(--bg-red); +} + nav li a:hover { text-decoration: underline; } @@ -177,6 +182,39 @@ form.samplecode-input input:focus-visible { background: lightgreen; } +.login-form { + width: fit-content; + margin: 3em auto; + padding: 2em 5em; + + border: 1px solid var(--table-border); + border-radius: .5em; + background: white; +} + +.login-form form { + width: 20em; + margin: 0 auto; + display: grid; + grid-gap: .5em; + + border: none; + background: none; +} + +.login-form form * { + padding: 0.5em; + border: 1px solid var(--table-border); + border-radius: 3px; +} + +.login-form button { + margin: 1em 0; + background: var(--bg-blue); + color: white; + border: none; +} + .border { padding: 1.5em; border: 1px solid var(--table-border); @@ -200,6 +238,7 @@ table { } .border > table { + padding: 0; border: none; background: transparent; } @@ -272,6 +311,13 @@ td > small { text-align: center; } +td.patient-id { + width: 32em; + text-overflow: ellipsis; + overflow: hidden; + display: block; +} + td.bg-blue, th.bg-blue, td.bg-green, th.bg-green, td.bg-yellow, th.bg-yellow, @@ -459,4 +505,19 @@ input.inline:focus-visible { .connection-display .connection.available { background: var(--bg-green); +} + +.notification { + margin: 1em; + padding: .5em; + border-radius: 3px; + text-align: center; +} + +.notification.success { + color: var(--bg-green); +} + +.notification.error { + color: var(--bg-red); } \ No newline at end of file diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 677e841..7a9af2f 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -1,5 +1,5 @@ - + @@ -14,7 +14,15 @@ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index b34804b..b1c3142 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,5 +1,5 @@ - + ETL-Prozessor @@ -42,7 +42,8 @@ [[ ${request.uuid} ]] - [[ ${request.patientId} ]] + [[ ${request.patientId} ]] + *** diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..018122d --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,23 @@ + + + + + ETL-Prozessor + + + +
+
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/report.html b/src/main/resources/templates/report.html index 01accc4..6f89345 100644 --- a/src/main/resources/templates/report.html +++ b/src/main/resources/templates/report.html @@ -1,5 +1,5 @@ - + ETL-Prozessor @@ -31,7 +31,8 @@ [[ ${request.type} ]] [[ ${request.uuid} ]] - [[ ${request.patientId} ]] + [[ ${request.patientId} ]] + ***