diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/DossierController.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/DossierController.java index 9e0fb0086..6eb15122f 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/DossierController.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/DossierController.java @@ -20,13 +20,13 @@ import java.util.stream.Collectors; import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.service.DossierCreatorService; + import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; @@ -42,6 +42,7 @@ import com.iqser.red.service.persistence.management.v1.processor.roles.Applicati import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService; import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService; import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService; +import com.iqser.red.service.persistence.management.v1.processor.service.FilterByPermissionsService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService; import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService; @@ -346,12 +347,14 @@ public class DossierController implements DossierResource { } - - - @PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "') && hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')") + @PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')") public void deleteDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId) { - var dossierToBeDeleted = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, true, false)); + Dossier dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, true, false)); + + if (dossier.getOwnerId() != null && !dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) { + throw new AccessDeniedException("Can not delete dossier that is owned by a different user"); + } dossierManagementService.delete(dossierId); @@ -362,14 +365,14 @@ public class DossierController implements DossierResource { .message("Dossier moved to trash.") .build()); - dossierToBeDeleted.getMemberIds() + dossier.getMemberIds() .stream() .filter(m -> !KeycloakSecurity.getUserId().equals(m)) .forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder() .userId(member) .issuerId(KeycloakSecurity.getUserId()) .notificationType(NotificationType.DOSSIER_DELETED.name()) - .target(Map.of("dossierId", dossierId, "dossierName", dossierToBeDeleted.getDossierName())) + .target(Map.of("dossierId", dossierId, "dossierName", dossier.getDossierName())) .build())); } @@ -477,15 +480,13 @@ public class DossierController implements DossierResource { @PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')") - @PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')") public void hardDeleteDossiers(@RequestParam(DOSSIER_ID_PARAM) Set dossierIds) { - for (String dossierId : dossierIds) { - accessControlService.verifyUserIsDossierOwner(dossierId); - } - dossierManagementService.hardDeleteDossiers(dossierIds); + var filteredDossierIds = filterDossierIdsByOwnedKeepUnowned(dossierIds); - for (String dossierId : dossierIds) { + dossierManagementService.hardDeleteDossiers(filteredDossierIds); + + for (String dossierId : filteredDossierIds) { auditPersistenceService.audit(AuditRequest.builder() .userId(KeycloakSecurity.getUserId()) @@ -499,12 +500,13 @@ public class DossierController implements DossierResource { @PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')") - @PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')") public void undeleteDossiers(@RequestBody Set dossierIds) { - dossierManagementService.undeleteDossiers(dossierIds); - for (String dossierId : dossierIds) { + var filteredDossierIds = filterDossierIdsByOwnedKeepUnowned(dossierIds); + dossierManagementService.undeleteDossiers(filteredDossierIds); + + for (String dossierId : filteredDossierIds) { auditPersistenceService.audit(AuditRequest.builder() .userId(KeycloakSecurity.getUserId()) .objectId(dossierId) @@ -515,4 +517,16 @@ public class DossierController implements DossierResource { } } + + private Set filterDossierIdsByOwnedKeepUnowned(Set dossierIds) { + + return dossierIds.stream() + .map(id -> dossierManagementService.getDossierById(id, true, true)) + .map(dossierACLService::enhanceDossierWithACLData) + .filter(dossier -> dossier.getOwnerId() == null || dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) + .map(Dossier::getId) + .collect(Collectors.toSet()); + } + } + diff --git a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/DossierResource.java b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/DossierResource.java index c71fe11c8..da0e5f853 100644 --- a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/DossierResource.java +++ b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/DossierResource.java @@ -133,7 +133,7 @@ public interface DossierResource { @ResponseStatus(value = HttpStatus.NO_CONTENT) @PostMapping(value = ARCHIVE_DOSSIERS_PATH + ARCHIVE_PATH) @Operation(summary = "Archives an existing dossier.", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully archived the dossier."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to archive dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while archiving."), @ApiResponse(responseCode = "404", description = "Dossier not found")}) + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully archived the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to archive dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while archiving."), @ApiResponse(responseCode = "404", description = "Dossier not found")}) void archiveDossiers(@RequestBody Set dossierIds); @@ -147,14 +147,14 @@ public interface DossierResource { @ResponseStatus(value = HttpStatus.NO_CONTENT) @DeleteMapping(value = DELETED_DOSSIERS_PATH + HARD_DELETE_PATH) @Operation(summary = "Hard deletes existing dossiers.", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the dossier."), @ApiResponse(responseCode = "404", description = "Not found")}) + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the dossiers."), @ApiResponse(responseCode = "404", description = "Not found")}) void hardDeleteDossiers(@RequestParam(DOSSIER_ID_PARAM) Set dossierIds); - @ResponseBody + @ResponseStatus(value = HttpStatus.NO_CONTENT) @PostMapping(value = DELETED_DOSSIERS_PATH + UNDELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Restores dossiers.", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully restored the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to restore dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring.")}) + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully restored the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to restore dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring.")}) void undeleteDossiers(@RequestBody Set dossierIds); } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AccessControlService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AccessControlService.java index 4be6329ee..147790cda 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AccessControlService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AccessControlService.java @@ -1,8 +1,13 @@ package com.iqser.red.service.persistence.management.v1.processor.service; +import java.util.Collection; +import java.util.Collections; + import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.acls.AclPermissionEvaluator; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -10,6 +15,8 @@ import com.iqser.red.service.persistence.management.v1.processor.acl.custom.doss import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException; import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException; import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; +import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles; +import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus; import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity; @@ -23,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; public class AccessControlService { private final FileStatusManagementService fileStatusManagementService; + private final UserService userService; private final DossierManagementService dossierManagementService; private final DossierACLService dossierACLService; private final AclPermissionEvaluator aclPermissionEvaluator; @@ -120,16 +128,19 @@ public class AccessControlService { } - @PostAuthorize("hasAuthority('RED_MANAGER') || hasPermission(#dossierId, 'Dossier', 'APPROVE') || hasPermission(#dossierId, 'Dossier', 'OWNER')") - public void verifyUserIsDossierOwnerOrApproverOrManager(String dossierId) { - - } public boolean hasUserViewPermissionsForDossier(String dossierId) { + return aclPermissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), dossierId, "Dossier", "VIEW_OBJECT"); } + public boolean hasUserAuthority(String authority) { + + return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(authority)); + } + + public void verifyFileIsNotApproved(String dossierId, String fileId) { try { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FilterByPermissionsService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FilterByPermissionsService.java index c19bfc115..78b7efba7 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FilterByPermissionsService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FilterByPermissionsService.java @@ -1,16 +1,18 @@ package com.iqser.red.service.persistence.management.v1.processor.service; import java.util.List; +import java.util.Set; +import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.stereotype.Service; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier; /* -* Service for executing Spring Security filtering operations -* Used to filter nested collections (i.e. Dossiers) in objects (i.e. DossierStatus) -* Mainly used to prevent information leakage + * Service for executing Spring Security filtering operations + * Used to filter nested collections (i.e. Dossiers) in objects (i.e. DossierStatus) + * Mainly used to prevent information leakage */ @Service public class FilterByPermissionsService { @@ -39,6 +41,21 @@ public class FilterByPermissionsService { } + @PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')") + public Set onlyAccessibleDossierIds(Set dossierIds) { + + return dossierIds; + + } + + + @PreFilter("hasPermission(filterObject, 'Dossier', 'OWNER')") + public Set onlyOwnedDossierIds(Set dossierIds) { + + return dossierIds; + + } + @PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'ACCESS_OBJECT')") public List onlyAccessibleDossiers(List dossiers) {