/* * This file is part of ETL-Processor * * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken * Copyright (c) 2025-2026 Paul-Christian Volkmer, 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 dev.dnpm.etl.processor.security.UserRole import dev.dnpm.etl.processor.security.UserRoleRepository import dev.dnpm.etl.processor.security.UserRoleService import java.util.* 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.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.UserDetails import org.springframework.security.crypto.factory.PasswordEncoderFactories 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.web.SecurityFilterChain private const val LOGIN_PATH = "/login" @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) private fun authorizeAppRequests(http: HttpSecurity) { http { authorizeHttpRequests { authorize("/configs/**", hasRole("ADMIN")) authorize("/api/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/api/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/patient/**", hasAnyRole("ADMIN", "USER")) authorize("/report/**", hasAnyRole("ADMIN", "USER")) authorize("/submission/**", hasAnyRole("ADMIN", "USER")) authorize(anyRequest, permitAll) } } } @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) passwordEncoder.encode(random) } else { securityConfigProperties.adminPassword } val admin: UserDetails = User.withUsername(adminUser).password(adminPassword).roles("ADMIN").build() val users = securityConfigProperties.users.map { User.withUsername(it.username).password(it.password).roles("USER").build() }.toTypedArray() return InMemoryUserDetailsManager(admin, *users) } @Bean @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true") fun filterChainOidc( http: HttpSecurity, passwordEncoder: PasswordEncoder, userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry, ): SecurityFilterChain { authorizeAppRequests(http) http { httpBasic { realmName = "ETL-Processor" } formLogin { loginPage = LOGIN_PATH } oauth2Login { loginPage = LOGIN_PATH } sessionManagement { sessionConcurrency { maximumSessions = 1 expiredUrl = "$LOGIN_PATH?expired" } sessionFixation { newSession() } } csrf { disable() } } return http.build() } @Bean @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true") fun grantedAuthoritiesMapper( userRoleRepository: UserRoleRepository, appSecurityConfigProperties: SecurityConfigProperties, ): GrantedAuthoritiesMapper { return GrantedAuthoritiesMapper { grantedAuthority -> grantedAuthority .filterIsInstance() .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 @ConditionalOnProperty( value = ["app.security.enable-oidc"], havingValue = "false", matchIfMissing = true, ) fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain { authorizeAppRequests(http) http { httpBasic { realmName = "ETL-Processor" } formLogin { loginPage = LOGIN_PATH } csrf { disable() } } return http.build() } @Bean fun sessionRegistry(): SessionRegistry { return SessionRegistryImpl() } @Bean fun passwordEncoder(): PasswordEncoder { return PasswordEncoderFactories.createDelegatingPasswordEncoder() } @Bean @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true") fun userRoleService( userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry, ): UserRoleService { return UserRoleService(userRoleRepository, sessionRegistry) } }