diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt index 7f3c410..7075d1e 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt @@ -9,6 +9,7 @@ import org.springframework.core.io.Resource import org.springframework.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.RestController import java.util.UUID @@ -20,6 +21,7 @@ class ApplicationFormController( val applicationFormFormatService: ApplicationFormFormatService ) : ApplicationFormApi { + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ResponseEntity { val updatedCreateApplicationFormDto = createApplicationFormDto.copy(isTemplate = false) return ResponseEntity.ok( @@ -29,6 +31,7 @@ class ApplicationFormController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun getAllApplicationForms(organizationId: String?): ResponseEntity { return ResponseEntity.ok( pagedApplicationFormMapper.toPagedApplicationFormDto( @@ -37,6 +40,7 @@ class ApplicationFormController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun getApplicationFormById(id: UUID): ResponseEntity { return ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( @@ -45,6 +49,7 @@ class ApplicationFormController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun getApplicationFormHtml(id: UUID): ResponseEntity { val applicationForm = applicationFormService.getApplicationFormById(id) return ResponseEntity.ok( @@ -52,6 +57,7 @@ class ApplicationFormController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun getApplicationFormPdf(id: UUID): ResponseEntity { val applicationForm = applicationFormService.getApplicationFormById(id) val pdfBytes = applicationFormFormatService.generatePdf(applicationForm) @@ -62,6 +68,7 @@ class ApplicationFormController( .body(resource) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun updateApplicationForm( id: UUID, applicationFormDto: ApplicationFormDto @@ -73,11 +80,13 @@ class ApplicationFormController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun deleteApplicationForm(id: UUID): ResponseEntity { applicationFormService.deleteApplicationFormByID(id) return ResponseEntity.noContent().build() } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun submitApplicationForm(id: UUID): ResponseEntity { return ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt index 9620623..49377da 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt @@ -7,6 +7,7 @@ import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto import com.betriebsratkanzlei.legalconsenthub_api.model.PagedApplicationFormDto import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.RestController import java.util.UUID @@ -17,6 +18,7 @@ class ApplicationFormTemplateController( val applicationFormMapper: ApplicationFormMapper, ) : ApplicationFormTemplateApi { + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun createApplicationFormTemplate(createApplicationFormDto: CreateApplicationFormDto): ResponseEntity { return ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( @@ -25,6 +27,7 @@ class ApplicationFormTemplateController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun getAllApplicationFormTemplates(): ResponseEntity { return ResponseEntity.ok( pagedApplicationFormMapper.toPagedApplicationFormDto( @@ -33,6 +36,7 @@ class ApplicationFormTemplateController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun getApplicationFormTemplateById(id: UUID): ResponseEntity { return ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( @@ -41,6 +45,7 @@ class ApplicationFormTemplateController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun updateApplicationFormTemplate( id: UUID, applicationFormDto: ApplicationFormDto @@ -52,6 +57,7 @@ class ApplicationFormTemplateController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun deleteApplicationFormTemplate(id: UUID): ResponseEntity { applicationFormTemplateService.deleteApplicationFormTemplateByID(id) return ResponseEntity.noContent().build() diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/comment/CommentController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/comment/CommentController.kt index 659c158..c3b3ae9 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/comment/CommentController.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/comment/CommentController.kt @@ -5,6 +5,7 @@ import com.betriebsratkanzlei.legalconsenthub_api.model.CommentDto import com.betriebsratkanzlei.legalconsenthub_api.model.CreateCommentDto import com.betriebsratkanzlei.legalconsenthub_api.model.PagedCommentDto import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.RestController import java.util.UUID @@ -12,6 +13,7 @@ import java.util.UUID class CommentController( val commentService: CommentService, val commentMapper: CommentMapper, val pagedCommentMapper: PagedCommentMapper ) : CommentApi { + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun createComment( applicationFormId: UUID, formElementId: UUID, @@ -24,6 +26,7 @@ class CommentController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun getCommentsByApplicationFormId(applicationFormId: UUID): ResponseEntity { return ResponseEntity.ok( pagedCommentMapper.toPagedCommentDto( @@ -32,6 +35,7 @@ class CommentController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')") override fun updateComment(id: UUID, commentDto: CommentDto): ResponseEntity { return ResponseEntity.ok( commentMapper.toCommentDto( @@ -40,6 +44,7 @@ class CommentController( ) } + @PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')") override fun deleteComment(id: UUID): ResponseEntity { commentService.deleteCommentByID(id) return ResponseEntity.noContent().build() diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/config/SecurityConfig.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/config/SecurityConfig.kt index 9d46ea7..507ef0e 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/config/SecurityConfig.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/config/SecurityConfig.kt @@ -3,18 +3,15 @@ package com.betriebsratkanzlei.legalconsenthub.config import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtAuthenticationConverter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.core.annotation.Order +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity 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.oauth2.jose.jws.SignatureAlgorithm -import org.springframework.security.oauth2.jwt.JwtDecoder -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder import org.springframework.security.web.SecurityFilterChain -import org.springframework.http.HttpMethod @Configuration @EnableWebSecurity +@EnableMethodSecurity class SecurityConfig { @Bean diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/security/CustomJwtAuthenticationConverter.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/security/CustomJwtAuthenticationConverter.kt index f43a45b..7fca2a3 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/security/CustomJwtAuthenticationConverter.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/security/CustomJwtAuthenticationConverter.kt @@ -3,18 +3,23 @@ package com.betriebsratkanzlei.legalconsenthub.security import org.springframework.core.convert.converter.Converter import org.springframework.security.authentication.AbstractAuthenticationToken import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.oauth2.jwt.Jwt import org.springframework.stereotype.Component @Component class CustomJwtAuthenticationConverter : Converter { override fun convert(jwt: Jwt): AbstractAuthenticationToken { - val authorities: Collection = emptyList() - val userId = jwt.subject val username = jwt.getClaimAsString("name") - val realmAccess = jwt.getClaimAsMap("realm_access") - val roles = (realmAccess?.get("roles") as? List<*>)?.mapNotNull { it as? String } ?: emptyList() + + val resourceAccess = jwt.getClaimAsMap("resource_access") as? Map<*, *> + val legalconsenthubResource = resourceAccess?.get("legalconsenthub") as? Map<*, *> + val roles = (legalconsenthubResource?.get("roles") as? List<*>)?.mapNotNull { it as? String } ?: emptyList() + + val authorities: Collection = roles.map { role -> + SimpleGrantedAuthority("ROLE_$role") + } val principal = CustomJwtTokenPrincipal(userId, username, roles) diff --git a/legalconsenthub/composables/usePermissions.ts b/legalconsenthub/composables/usePermissions.ts new file mode 100644 index 0000000..f1185cf --- /dev/null +++ b/legalconsenthub/composables/usePermissions.ts @@ -0,0 +1,152 @@ +export type Permission = + | 'application-form:read' + | 'application-form:write' + | 'application-form:sign' + | 'application-form-template:add' + | 'application-form-template:edit' + | 'application-form-template:delete' + | 'comment:add' + | 'comment:edit' + | 'comment:delete' + +export type Role = + | 'CHIEF_EXECUTIVE_OFFICER' + | 'BUSINESS_DEPARTMENT' + | 'IT_DEPARTMENT' + | 'HUMAN_RESOURCES' + | 'HEAD_OF_WORKS_COUNCIL' + | 'WORKS_COUNCIL' + | 'EMPLOYEE' + +const ROLE_PERMISSIONS: Record = { + CHIEF_EXECUTIVE_OFFICER: [ + 'application-form:read', + 'application-form:write', + 'application-form:sign', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + HEAD_OF_WORKS_COUNCIL: [ + 'application-form:read', + 'application-form:write', + 'application-form:sign', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + BUSINESS_DEPARTMENT: [ + 'application-form:read', + 'application-form:write', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + IT_DEPARTMENT: [ + 'application-form:read', + 'application-form:write', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + HUMAN_RESOURCES: [ + 'application-form:read', + 'application-form:write', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + WORKS_COUNCIL: [ + 'application-form:read', + 'application-form:write', + 'application-form-template:add', + 'application-form-template:edit', + 'application-form-template:delete', + 'comment:add', + 'comment:edit', + 'comment:delete' + ], + EMPLOYEE: ['application-form:read', 'comment:add', 'comment:edit'] +} + +export const usePermissions = () => { + const { user } = useUserSession() + + const userRoles = computed(() => { + return (user.value?.roles ?? []) as Role[] + }) + + const userPermissions = computed(() => { + const permissions = new Set() + userRoles.value.forEach((role) => { + const rolePermissions = ROLE_PERMISSIONS[role] ?? [] + rolePermissions.forEach((permission) => permissions.add(permission)) + }) + return Array.from(permissions) + }) + + const hasPermission = (permission: Permission): boolean => { + return userPermissions.value.includes(permission) + } + + const hasAnyPermission = (permissions: Permission[]): boolean => { + return permissions.some((permission) => hasPermission(permission)) + } + + const hasAllPermissions = (permissions: Permission[]): boolean => { + return permissions.every((permission) => hasPermission(permission)) + } + + const hasRole = (role: Role): boolean => { + return userRoles.value.includes(role) + } + + const hasAnyRole = (roles: Role[]): boolean => { + return roles.some((role) => hasRole(role)) + } + + const canReadApplicationForms = computed(() => hasPermission('application-form:read')) + const canWriteApplicationForms = computed(() => hasPermission('application-form:write')) + const canSignApplicationForms = computed(() => hasPermission('application-form:sign')) + const canAddTemplate = computed(() => hasPermission('application-form-template:add')) + const canEditTemplate = computed(() => hasPermission('application-form-template:edit')) + const canDeleteTemplate = computed(() => hasPermission('application-form-template:delete')) + const canAddComment = computed(() => hasPermission('comment:add')) + const canEditComment = computed(() => hasPermission('comment:edit')) + const canDeleteComment = computed(() => hasPermission('comment:delete')) + + return { + userRoles, + userPermissions, + hasPermission, + hasAnyPermission, + hasAllPermissions, + hasRole, + hasAnyRole, + canReadApplicationForms, + canWriteApplicationForms, + canSignApplicationForms, + canAddTemplate, + canEditTemplate, + canDeleteTemplate, + canAddComment, + canEditComment, + canDeleteComment + } +} + diff --git a/legalconsenthub/middleware/permissions.global.ts b/legalconsenthub/middleware/permissions.global.ts new file mode 100644 index 0000000..a748ba9 --- /dev/null +++ b/legalconsenthub/middleware/permissions.global.ts @@ -0,0 +1,8 @@ +export default defineNuxtRouteMiddleware((to) => { + const { canWriteApplicationForms } = usePermissions() + + if (to.path === '/create' && !canWriteApplicationForms.value) { + return navigateTo('/', { replace: true }) + } +}) + diff --git a/legalconsenthub/pages/create.vue b/legalconsenthub/pages/create.vue index 68b900e..e5c8d2e 100644 --- a/legalconsenthub/pages/create.vue +++ b/legalconsenthub/pages/create.vue @@ -16,18 +16,11 @@