mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-04-20 17:56:50 +00:00
Merge pull request #48 from CCC-MF/issue_36
Freigabe und Berechtigung für OIDC-Benutzer
This commit is contained in:
commit
5928d52237
17
README.md
17
README.md
@ -114,6 +114,23 @@ Weitere Informationen zur Konfiguration des OIDC-Providers
|
|||||||
sind [hier](https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html#oauth2-client)
|
sind [hier](https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html#oauth2-client)
|
||||||
zu finden.
|
zu finden.
|
||||||
|
|
||||||
|
#### Rollenbasierte Berechtigungen
|
||||||
|
|
||||||
|
Wird OpenID Connect verwendet, gibt es eine rollenbasierte Berechtigungszuweisung.
|
||||||
|
|
||||||
|
Die Standardrolle für neue OIDC-Benutzer kann mit der Option `APP_SECURITY_DEFAULT_USER_ROLE` festgelegt werden.
|
||||||
|
Mögliche Werte sind `user` oder `guest`. Standardwert ist `user`.
|
||||||
|
|
||||||
|
Benutzer mit der Rolle "Gast" sehen nur die Inhalte, die auch nicht angemeldete Benutzer sehen.
|
||||||
|
|
||||||
|
Hierdurch ist es möglich, einzelne Benutzer einzuschränken oder durch Änderung der Standardrolle auf `guest` nur
|
||||||
|
einzelne Benutzer als vollwertige Nutzer zuzulassen.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Benutzer werden nach dem Entfernen oder der Änderung der vergebenen Rolle automatisch abgemeldet und müssen sich neu anmelden.
|
||||||
|
Sie bekommen dabei wieder die Standardrolle zugewiesen.
|
||||||
|
|
||||||
#### Auswirkungen auf den dargestellten Inhalt
|
#### Auswirkungen auf den dargestellten Inhalt
|
||||||
|
|
||||||
Nur Administratoren haben Zugriff auf den Konfigurationsbereich, nur angemeldete Benutzer können die anonymisierte oder
|
Nur Administratoren haben Zugriff auf den Konfigurationsbereich, nur angemeldete Benutzer können die anonymisierte oder
|
||||||
|
BIN
docs/userroles.png
Normal file
BIN
docs/userroles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
import dev.dnpm.etl.processor.security.Role
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty
|
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty
|
||||||
|
|
||||||
@ -102,7 +103,8 @@ data class SecurityConfigProperties(
|
|||||||
val adminUser: String?,
|
val adminUser: String?,
|
||||||
val adminPassword: String?,
|
val adminPassword: String?,
|
||||||
val enableTokens: Boolean = false,
|
val enableTokens: Boolean = false,
|
||||||
val enableOidc: Boolean = false
|
val enableOidc: Boolean = false,
|
||||||
|
val defaultNewUserRole: Role = Role.USER
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val NAME = "app.security"
|
const val NAME = "app.security"
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
import dev.dnpm.etl.processor.security.UserRole
|
||||||
|
import dev.dnpm.etl.processor.security.UserRoleRepository
|
||||||
|
import dev.dnpm.etl.processor.services.UserRoleService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
@ -27,10 +30,15 @@ import org.springframework.context.annotation.Configuration
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.security.config.annotation.web.invoke
|
import org.springframework.security.config.annotation.web.invoke
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
|
||||||
|
import org.springframework.security.core.session.SessionRegistry
|
||||||
|
import org.springframework.security.core.session.SessionRegistryImpl
|
||||||
import org.springframework.security.core.userdetails.User
|
import org.springframework.security.core.userdetails.User
|
||||||
import org.springframework.security.core.userdetails.UserDetails
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||||
import org.springframework.security.web.SecurityFilterChain
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -77,12 +85,19 @@ class AppSecurityConfiguration(
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
|
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
|
||||||
fun filterChainOidc(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
|
fun filterChainOidc(http: HttpSecurity, passwordEncoder: PasswordEncoder, userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry): SecurityFilterChain {
|
||||||
http {
|
http {
|
||||||
authorizeRequests {
|
authorizeRequests {
|
||||||
authorize("/configs/**", hasRole("ADMIN"))
|
authorize("/configs/**", hasRole("ADMIN"))
|
||||||
authorize("/mtbfile/**", hasAnyRole("MTBFILE"))
|
authorize("/mtbfile/**", hasAnyRole("MTBFILE"))
|
||||||
authorize("/report/**", fullyAuthenticated)
|
authorize("/report/**", hasAnyRole("ADMIN", "USER"))
|
||||||
|
authorize("*.css", permitAll)
|
||||||
|
authorize("*.ico", permitAll)
|
||||||
|
authorize("*.jpeg", permitAll)
|
||||||
|
authorize("*.js", permitAll)
|
||||||
|
authorize("*.svg", permitAll)
|
||||||
|
authorize("*.css", permitAll)
|
||||||
|
authorize("/login/**", permitAll)
|
||||||
authorize(anyRequest, permitAll)
|
authorize(anyRequest, permitAll)
|
||||||
}
|
}
|
||||||
httpBasic {
|
httpBasic {
|
||||||
@ -94,11 +109,39 @@ class AppSecurityConfiguration(
|
|||||||
oauth2Login {
|
oauth2Login {
|
||||||
loginPage = "/login"
|
loginPage = "/login"
|
||||||
}
|
}
|
||||||
|
sessionManagement {
|
||||||
|
sessionConcurrency {
|
||||||
|
maximumSessions = 1
|
||||||
|
maxSessionsPreventsLogin = true
|
||||||
|
expiredUrl = "/login?expired"
|
||||||
|
}
|
||||||
|
sessionFixation {
|
||||||
|
newSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
csrf { disable() }
|
csrf { disable() }
|
||||||
}
|
}
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
|
||||||
|
fun grantedAuthoritiesMapper(userRoleRepository: UserRoleRepository, appSecurityConfigProperties: SecurityConfigProperties): GrantedAuthoritiesMapper {
|
||||||
|
return GrantedAuthoritiesMapper { grantedAuthority ->
|
||||||
|
grantedAuthority.filterIsInstance<OidcUserAuthority>()
|
||||||
|
.onEach {
|
||||||
|
val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
|
||||||
|
if (userRole.isEmpty) {
|
||||||
|
userRoleRepository.save(UserRole(null, it.userInfo.preferredUsername, appSecurityConfigProperties.defaultNewUserRole))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
val userRole = userRoleRepository.findByUsername(it.userInfo.preferredUsername)
|
||||||
|
SimpleGrantedAuthority("ROLE_${userRole.get().role.toString().uppercase()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "false", matchIfMissing = true)
|
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "false", matchIfMissing = true)
|
||||||
fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
|
fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
|
||||||
@ -120,10 +163,19 @@ class AppSecurityConfiguration(
|
|||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun sessionRegistry(): SessionRegistry {
|
||||||
|
return SessionRegistryImpl()
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun passwordEncoder(): PasswordEncoder {
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder()
|
return PasswordEncoderFactories.createDelegatingPasswordEncoder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
|
||||||
|
fun userRoleService(userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry): UserRoleService {
|
||||||
|
return UserRoleService(userRoleRepository, sessionRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt
Normal file
44
src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.security
|
||||||
|
|
||||||
|
import org.springframework.data.annotation.Id
|
||||||
|
import org.springframework.data.relational.core.mapping.Table
|
||||||
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Table("user_role")
|
||||||
|
data class UserRole(
|
||||||
|
@Id val id: Long? = null,
|
||||||
|
val username: String,
|
||||||
|
var role: Role = Role.GUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Role(val value: String) {
|
||||||
|
GUEST("guest"),
|
||||||
|
USER("user")
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserRoleRepository : CrudRepository<UserRole, Long> {
|
||||||
|
|
||||||
|
fun findByUsername(username: String): Optional<UserRole>
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.services
|
||||||
|
|
||||||
|
import dev.dnpm.etl.processor.security.Role
|
||||||
|
import dev.dnpm.etl.processor.security.UserRole
|
||||||
|
import dev.dnpm.etl.processor.security.UserRoleRepository
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.security.core.session.SessionRegistry
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
||||||
|
|
||||||
|
class UserRoleService(
|
||||||
|
private val userRoleRepository: UserRoleRepository,
|
||||||
|
private val sessionRegistry: SessionRegistry
|
||||||
|
) {
|
||||||
|
fun updateUserRole(id: Long, role: Role) {
|
||||||
|
val userRole = userRoleRepository.findByIdOrNull(id) ?: return
|
||||||
|
userRole.role = role
|
||||||
|
userRoleRepository.save(userRole)
|
||||||
|
expireSessionFor(userRole.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteUserRole(id: Long) {
|
||||||
|
val userRole = userRoleRepository.findByIdOrNull(id) ?: return
|
||||||
|
userRoleRepository.delete(userRole)
|
||||||
|
expireSessionFor(userRole.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAll(): List<UserRole> {
|
||||||
|
return userRoleRepository.findAll().toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expireSessionFor(username: String) {
|
||||||
|
sessionRegistry.allPrincipals
|
||||||
|
.filterIsInstance<OidcUser>()
|
||||||
|
.filter { it.preferredUsername == username }
|
||||||
|
.flatMap {
|
||||||
|
sessionRegistry.getAllSessions(it, true)
|
||||||
|
}
|
||||||
|
.onEach {
|
||||||
|
it.expireNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,9 +22,12 @@ package dev.dnpm.etl.processor.web
|
|||||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.output.MtbFileSender
|
import dev.dnpm.etl.processor.output.MtbFileSender
|
||||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||||
|
import dev.dnpm.etl.processor.security.Role
|
||||||
|
import dev.dnpm.etl.processor.security.UserRole
|
||||||
import dev.dnpm.etl.processor.services.Token
|
import dev.dnpm.etl.processor.services.Token
|
||||||
import dev.dnpm.etl.processor.services.TokenService
|
import dev.dnpm.etl.processor.services.TokenService
|
||||||
import dev.dnpm.etl.processor.services.TransformationService
|
import dev.dnpm.etl.processor.services.TransformationService
|
||||||
|
import dev.dnpm.etl.processor.services.UserRoleService
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.http.codec.ServerSentEvent
|
import org.springframework.http.codec.ServerSentEvent
|
||||||
@ -43,7 +46,8 @@ class ConfigController(
|
|||||||
private val pseudonymGenerator: Generator,
|
private val pseudonymGenerator: Generator,
|
||||||
private val mtbFileSender: MtbFileSender,
|
private val mtbFileSender: MtbFileSender,
|
||||||
private val connectionCheckService: ConnectionCheckService,
|
private val connectionCheckService: ConnectionCheckService,
|
||||||
private val tokenService: TokenService?
|
private val tokenService: TokenService?,
|
||||||
|
private val userRoleService: UserRoleService?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@ -56,10 +60,16 @@ class ConfigController(
|
|||||||
if (tokenService != null) {
|
if (tokenService != null) {
|
||||||
model.addAttribute("tokens", tokenService.findAll())
|
model.addAttribute("tokens", tokenService.findAll())
|
||||||
} else {
|
} else {
|
||||||
model.addAttribute("tokens", listOf<Token>())
|
model.addAttribute("tokens", emptyList<Token>())
|
||||||
}
|
}
|
||||||
model.addAttribute("transformations", transformationService.getTransformations())
|
model.addAttribute("transformations", transformationService.getTransformations())
|
||||||
|
if (userRoleService != null) {
|
||||||
|
model.addAttribute("userRolesEnabled", true)
|
||||||
|
model.addAttribute("userRoles", userRoleService.findAll())
|
||||||
|
} else {
|
||||||
|
model.addAttribute("userRolesEnabled", false)
|
||||||
|
model.addAttribute("userRoles", emptyList<UserRole>())
|
||||||
|
}
|
||||||
return "configs"
|
return "configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +122,34 @@ class ConfigController(
|
|||||||
return "configs/tokens"
|
return "configs/tokens"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = ["userroles/{id}"])
|
||||||
|
fun deleteUserRole(@PathVariable id: Long, model: Model): String {
|
||||||
|
if (userRoleService != null) {
|
||||||
|
userRoleService.deleteUserRole(id)
|
||||||
|
|
||||||
|
model.addAttribute("userRolesEnabled", true)
|
||||||
|
model.addAttribute("userRoles", userRoleService.findAll())
|
||||||
|
} else {
|
||||||
|
model.addAttribute("userRolesEnabled", false)
|
||||||
|
model.addAttribute("userRoles", emptyList<UserRole>())
|
||||||
|
}
|
||||||
|
return "configs/userroles"
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = ["userroles/{id}"])
|
||||||
|
fun updateUserRole(@PathVariable id: Long, @ModelAttribute("role") role: Role, model: Model): String {
|
||||||
|
if (userRoleService != null) {
|
||||||
|
userRoleService.updateUserRole(id, role)
|
||||||
|
|
||||||
|
model.addAttribute("userRolesEnabled", true)
|
||||||
|
model.addAttribute("userRoles", userRoleService.findAll())
|
||||||
|
} else {
|
||||||
|
model.addAttribute("userRolesEnabled", false)
|
||||||
|
model.addAttribute("userRoles", emptyList<UserRole>())
|
||||||
|
}
|
||||||
|
return "configs/userroles"
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
|
@GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
|
||||||
fun events(): Flux<ServerSentEvent<Any>> {
|
fun events(): Flux<ServerSentEvent<Any>> {
|
||||||
return configsUpdateProducer.asFlux().map {
|
return configsUpdateProducer.asFlux().map {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS user_role
|
||||||
|
(
|
||||||
|
id int auto_increment primary key,
|
||||||
|
username varchar(255) not null unique,
|
||||||
|
role varchar(255) not null,
|
||||||
|
created_at datetime default utc_timestamp() not null
|
||||||
|
);
|
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS user_role
|
||||||
|
(
|
||||||
|
id serial,
|
||||||
|
username varchar(255) not null unique,
|
||||||
|
role varchar(255) not null,
|
||||||
|
created_at timestamp with time zone default now() not null,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
@ -202,6 +202,17 @@ form.samplecode-input input:focus-visible {
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userrole-form form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form form *,
|
.login-form form *,
|
||||||
.token-form form * {
|
.token-form form * {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@ -210,7 +221,8 @@ form.samplecode-input input:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-form form hr,
|
.login-form form hr,
|
||||||
.token-form form hr {
|
.token-form form hr,
|
||||||
|
.userrole-form form hr {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -224,6 +236,14 @@ form.samplecode-input input:focus-visible {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userrole-form form select {
|
||||||
|
padding: 0.5em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
line-height: 1.2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
border: 1px solid var(--table-border);
|
border: 1px solid var(--table-border);
|
||||||
@ -527,6 +547,10 @@ input.inline:focus-visible {
|
|||||||
color: var(--bg-green);
|
color: var(--bg-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification.notice {
|
||||||
|
color: var(--bg-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
.notification.error {
|
.notification.error {
|
||||||
color: var(--bg-red);
|
color: var(--bg-red);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
<section th:insert="~{configs/tokens.html}">
|
<section th:insert="~{configs/tokens.html}">
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section th:insert="~{configs/userroles.html}">
|
||||||
|
</section>
|
||||||
|
|
||||||
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
||||||
<div th:insert="~{configs/connectionAvailable.html}" th:hx-get="@{/configs?connectionAvailable}" hx-trigger="sse:connection-available">
|
<div th:insert="~{configs/connectionAvailable.html}" th:hx-get="@{/configs?connectionAvailable}" hx-trigger="sse:connection-available">
|
||||||
</div>
|
</div>
|
||||||
|
39
src/main/resources/templates/configs/userroles.html
Normal file
39
src/main/resources/templates/configs/userroles.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<div th:if="${not userRolesEnabled}">
|
||||||
|
<h2><span>⛔</span> Benutzerberechtigungen</h2>
|
||||||
|
<p>Die Verwendung von rollenbasierten Benutzerberechtigungen ist nicht aktiviert.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="userroles" th:if="${userRolesEnabled}">
|
||||||
|
<h2><span>✅</span> Benutzerberechtigungen</h2>
|
||||||
|
<div class="border">
|
||||||
|
<div th:if="${userRoles.isEmpty()}">Noch keine Benutzerberechtigungen vorhanden.</div>
|
||||||
|
<table th:if="${not userRoles.isEmpty()}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Benutzername</th>
|
||||||
|
<th>Rolle</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="userRole : ${userRoles}">
|
||||||
|
<td>[[ ${userRole.username} ]]</td>
|
||||||
|
<td>
|
||||||
|
<div class="userrole-form">
|
||||||
|
<form th:hx-put="@{/configs/userroles/{id}(id=${userRole.id})}" hx-target="#userroles">
|
||||||
|
<select name="role">
|
||||||
|
<option th:selected="${userRole.role.value == 'guest'}" value="GUEST">Gast</option>
|
||||||
|
<option th:selected="${userRole.role.value == 'user'}" value="USER">Benutzer</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-blue">Übernehmen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-red" th:hx-delete="@{/configs/userroles/{id}(id=${userRole.id})}" hx-target="#userroles">Löschen</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -53,17 +53,17 @@
|
|||||||
<td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td>
|
<td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td>
|
||||||
<td th:if="not ${request.report}">[[ ${request.uuid} ]]</td>
|
<td th:if="not ${request.report}">[[ ${request.uuid} ]]</td>
|
||||||
<td th:if="${request.report}">
|
<td th:if="${request.report}">
|
||||||
<th:block sec:authorize="not authenticated">[[ ${request.uuid} ]]</th:block>
|
<a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">[[ ${request.uuid} ]]</a>
|
||||||
<a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="authenticated">[[ ${request.uuid} ]]</a>
|
<th:block sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">[[ ${request.uuid} ]]</th:block>
|
||||||
</td>
|
</td>
|
||||||
<td><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></td>
|
<td><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></td>
|
||||||
<td class="patient-id" th:if="${patientId != null}" sec:authorize="authenticated">
|
<td class="patient-id" th:if="${patientId != null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
|
||||||
[[ ${request.patientId} ]]
|
[[ ${request.patientId} ]]
|
||||||
</td>
|
</td>
|
||||||
<td class="patient-id" th:if="${patientId == null}" sec:authorize="authenticated">
|
<td class="patient-id" th:if="${patientId == null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
|
||||||
<a th:href="@{/patient/{pid}(pid=${request.patientId})}">[[ ${request.patientId} ]]</a>
|
<a th:href="@{/patient/{pid}(pid=${request.patientId})}">[[ ${request.patientId} ]]</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="patient-id" sec:authorize="not authenticated">***</td>
|
<td class="patient-id" sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">***</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<div class="login-form">
|
<div class="login-form">
|
||||||
<h2 class="centered">Anmelden</h2>
|
<h2 class="centered">Anmelden</h2>
|
||||||
<div class="centered notification error" th:if="${param.error}">Anmeldung nicht erfolgreich</div>
|
<div class="centered notification error" th:if="${param.error}">Anmeldung nicht erfolgreich</div>
|
||||||
|
<div class="centered notification notice" th:if="${param.expired}">Sitzung abgelaufen oder von einem Administrator beendet.</div>
|
||||||
<div class="centered notification success" th:if="${param.logout}">Sie haben sich abgemeldet</div>
|
<div class="centered notification success" th:if="${param.logout}">Sie haben sich abgemeldet</div>
|
||||||
<form method="post" th:action="@{/login}">
|
<form method="post" th:action="@{/login}">
|
||||||
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="" />
|
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user