diff options
4 files changed, 59 insertions, 38 deletions
@@ -20,6 +20,13 @@ Zudem ist eine minimalistische Weboberfläche integriert, die einen Einblick in ### 🔥 Wichtige Änderungen in Version 0.15 +#### Konfiguration von Benutzern + +Zusätzlich zu einem Administrator-Account können nun [weitere Benutzer](#weitere-benutzer) +mit nicht administrativen Rechten in der Konfiguration angelegt werden. + +#### TAN-Speicherung + Ab Version 0.15 wird zu jeder Anfrage die generierte TAN zusätzlich zur Request-ID gespeichert. Die TAN wird nur für MTB-Anfragen gespeichert, da sie für Lösch-Anfragen nicht relevant ist. @@ -222,8 +229,22 @@ Hier Beispiele für das Beispielpasswort `very-secret`: * `{sha256}9a34717f0646b5e9cfcba70055de62edb026ff4f68671ba3db96aa29297d2df5f1a037d58c745657` Wird kein Administrator-Passwort angegeben, wird ein zufälliger Wert generiert und beim Start der -Anwendung in den Logs -angezeigt. +Anwendung in den Logs angezeigt. + +#### Weitere Benutzer + +Ab Version 0.15.0 können weitere Benutzer-Accounts konfiguriert werden, ohne OpenID Connect zu verwenden. +Diese haben lediglich Benutzerrechte und können keine Konfigurationen einsehen oder ändern. + +Beispiele: + +``` +APP_SECURITY_USERS[0]_USERNAME=myuser +APP_SECURITY_USERS[0]_PASSWORD={noop}very-secret +APP_SECURITY_USERS[1]_USERNAME=otheruser +APP_SECURITY_USERS[1]_PASSWORD={bcrypt}$2y$05$CCkfsMr/wbTleMyjVIK8g.Aa3RCvrvoLXVAsL.f6KeouS88vXD9b6 +... +``` #### Weitere (nicht administrative) Nutzer mit OpenID Connect diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt index afaca73..4c7de9c 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt @@ -159,16 +159,16 @@ class MtbFileRestControllerTest { "/api/mtb/etl/patient-record", ] ) - fun testShouldDenyPermissionToSendMtbFile(url: String) { + fun testShouldGrantPermissionToSendMtbFileToUser(url: String) { mockMvc .post(url) { - with(anonymous()) + with(user("testuser").roles("USER")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) } - .andExpect { status { isUnauthorized() } } + .andExpect { status { isAccepted() } } - verify(requestProcessor, never()).processMtbFile(any<Mtb>()) + verify(requestProcessor, times(1)).processMtbFile(any<Mtb>()) } @ParameterizedTest @@ -185,14 +185,13 @@ class MtbFileRestControllerTest { "/api/mtb/etl/patient-record", ] ) - fun testShouldDenyPermissionToSendMtbFileForUser(url: String) { + fun testShouldDenyPermissionToSendMtbFileForAnonymous(url: String) { mockMvc .post(url) { - with(user("fakeuser").roles("USER")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) } - .andExpect { status { isForbidden() } } + .andExpect { status { isUnauthorized() } } verify(requestProcessor, never()).processMtbFile(any<Mtb>()) } 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 d2922f2..63f50a6 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt @@ -136,12 +136,18 @@ data class SecurityConfigProperties( val enableTokens: Boolean = false, val enableOidc: Boolean = false, val defaultNewUserRole: Role = Role.USER, + val users: List<UserProperties> = listOf(), ) { companion object { const val NAME = "app.security" } } +data class UserProperties( + val username: String, + val password: String, +) + 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 index 9b48d22..60b1a9c 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt @@ -53,6 +53,22 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon 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 = @@ -72,10 +88,14 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon securityConfigProperties.adminPassword } - val user: UserDetails = + val admin: UserDetails = User.withUsername(adminUser).password(adminPassword).roles("ADMIN").build() - return InMemoryUserDetailsManager(user) + val users = securityConfigProperties.users.map { + User.withUsername(it.username).password(it.password).roles("USER").build() + }.toTypedArray() + + return InMemoryUserDetailsManager(admin, *users) } @Bean @@ -86,24 +106,8 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon userRoleRepository: UserRoleRepository, sessionRegistry: SessionRegistry, ): SecurityFilterChain { + authorizeAppRequests(http) 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("/report/**", hasAnyRole("ADMIN", "USER")) - authorize("/submission/**", 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) - } httpBasic { realmName = "ETL-Processor" } formLogin { loginPage = LOGIN_PATH } oauth2Login { loginPage = LOGIN_PATH } @@ -154,17 +158,8 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon matchIfMissing = true, ) fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain { + authorizeAppRequests(http) http { - authorizeHttpRequests { - authorize("/configs/**", hasRole("ADMIN")) - authorize("/api/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN")) - authorize("/api/mtb/**", hasAnyRole("MTBFILE", "ADMIN")) - authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN")) - authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN")) - authorize("/report/**", hasRole("ADMIN")) - authorize("/submission/**", hasAnyRole("ADMIN")) - authorize(anyRequest, permitAll) - } httpBasic { realmName = "ETL-Processor" } formLogin { loginPage = LOGIN_PATH } csrf { disable() } |
