mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-04-19 17:26:51 +00:00
Merge pull request #21 from CCC-MF/feat_18
feat #18: initial support for authentication
This commit is contained in:
commit
21959c1698
18
README.md
18
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,
|
||||
|
@ -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")
|
||||
|
@ -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<String>) {
|
||||
|
@ -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
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" th:href="@{/style.css}" />
|
||||
@ -14,7 +14,15 @@
|
||||
<ul>
|
||||
<li><a th:href="@{/}">Übersicht</a></li>
|
||||
<li><a th:href="@{/statistics}">Statistiken</a></li>
|
||||
<li><a th:href="@{/configs}">Konfiguration</a></li>
|
||||
<li sec:authorize="hasRole('ADMIN')">
|
||||
<a th:href="@{/configs}">Konfiguration</a>
|
||||
</li>
|
||||
<li class="login" sec:authorize="not isAuthenticated()">
|
||||
<a th:href="@{/login}">Login</a>
|
||||
</li>
|
||||
<li class="login" sec:authorize="isAuthenticated()">
|
||||
<a th:href="@{/logout}">Abmelden</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ETL-Prozessor</title>
|
||||
@ -42,7 +42,8 @@
|
||||
<a th:href="@{/report/{id}(id=${request.uuid})}">[[ ${request.uuid} ]]</a>
|
||||
</td>
|
||||
<td><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></td>
|
||||
<td>[[ ${request.patientId} ]]</td>
|
||||
<td class="patient-id" sec:authorize="authenticated">[[ ${request.patientId} ]]</td>
|
||||
<td class="patient-id" sec:authorize="not authenticated">***</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
23
src/main/resources/templates/login.html
Normal file
23
src/main/resources/templates/login.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ETL-Prozessor</title>
|
||||
<link rel="stylesheet" th:href="@{/style.css}" />
|
||||
</head>
|
||||
<body>
|
||||
<div th:replace="~{fragments.html :: nav}"></div>
|
||||
<main>
|
||||
<div class="login-form">
|
||||
<h2 class="centered">Anmelden</h2>
|
||||
<div class="centered notification error" th:if="${param.error}">Anmeldung nicht erfolgreich</div>
|
||||
<div class="centered notification success" th:if="${param.logout}">Sie haben sich abgemeldet</div>
|
||||
<form method="post" th:action="@{/login}">
|
||||
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required="">
|
||||
<button class="" type="submit">Anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ETL-Prozessor</title>
|
||||
@ -31,7 +31,8 @@
|
||||
<td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td>
|
||||
<td>[[ ${request.uuid} ]]</td>
|
||||
<td><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></td>
|
||||
<td>[[ ${request.patientId} ]]</td>
|
||||
<td class="patient-id" sec:authorize="authenticated">[[ ${request.patientId} ]]</td>
|
||||
<td class="patient-id" sec:authorize="not authenticated">***</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
Loading…
x
Reference in New Issue
Block a user