diff options
| author | Paul-Christian Volkmer | 2024-03-01 09:30:07 +0100 |
|---|---|---|
| committer | Paul-Christian Volkmer | 2024-03-01 09:30:07 +0100 |
| commit | 5c15ad4518f70e4523405fa67635c4dff1a73e43 (patch) | |
| tree | dda919da7733b3b63cafc9e71fe1dea0f37f18d3 /src/main | |
| parent | 0b6decf88d9084616874d65827e7eb1e8050d1c5 (diff) | |
feat: add user role database table and role-based permissions
Diffstat (limited to 'src/main')
5 files changed, 98 insertions, 9 deletions
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 6017aab..d28369a 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt @@ -19,6 +19,9 @@ package dev.dnpm.etl.processor.config +import dev.dnpm.etl.processor.security.Role +import dev.dnpm.etl.processor.security.UserRole +import dev.dnpm.etl.processor.security.UserRoleRepository import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -27,10 +30,13 @@ 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.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 import java.util.* @@ -77,13 +83,20 @@ class AppSecurityConfiguration( @Bean @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true") - fun filterChainOidc(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain { + fun filterChainOidc(http: HttpSecurity, passwordEncoder: PasswordEncoder, userRoleRepository: UserRoleRepository): SecurityFilterChain { http { authorizeRequests { authorize("/configs/**", hasRole("ADMIN")) authorize("/mtbfile/**", hasAnyRole("MTBFILE")) - authorize("/report/**", fullyAuthenticated) - authorize(anyRequest, permitAll) + 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, fullyAuthenticated) } httpBasic { realmName = "ETL-Processor" @@ -100,6 +113,24 @@ class AppSecurityConfiguration( } @Bean + @ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true") + fun grantedAuthoritiesMapper(userRoleRepository: UserRoleRepository): 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, Role.GUEST)) + } + } + .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 { http { @@ -126,4 +157,3 @@ class AppSecurityConfiguration( } } - diff --git a/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt new file mode 100644 index 0000000..83ba662 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/security/UserRole.kt @@ -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.Optional + +@Table("user_role") +data class UserRole( + @Id val id: Long? = null, + val username: String, + val 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> + +}
\ No newline at end of file diff --git a/src/main/resources/db/migration/mariadb/V0_3_0__UserRoles.sql b/src/main/resources/db/migration/mariadb/V0_3_0__UserRoles.sql new file mode 100644 index 0000000..99399fd --- /dev/null +++ b/src/main/resources/db/migration/mariadb/V0_3_0__UserRoles.sql @@ -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 +);
\ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V0_3_0__UserRoles.sql b/src/main/resources/db/migration/postgresql/V0_3_0__UserRoles.sql new file mode 100644 index 0000000..7dbfc08 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V0_3_0__UserRoles.sql @@ -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) +);
\ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 3951f66..be3123b 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -53,17 +53,17 @@ <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="${request.report}"> - <th:block sec:authorize="not authenticated">[[ ${request.uuid} ]]</th:block> - <a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="authenticated">[[ ${request.uuid} ]]</a> + <a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">[[ ${request.uuid} ]]</a> + <th:block sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">[[ ${request.uuid} ]]</th:block> </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} ]] </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> </td> - <td class="patient-id" sec:authorize="not authenticated">***</td> + <td class="patient-id" sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">***</td> </tr> </tbody> </table> |
