Merge branch 'RED-7176' into 'master'
RED-7176: Soft-/Hard-/Restore Dossier without Dossier Owner not possible Closes RED-7176 See merge request redactmanager/persistence-service!323
This commit is contained in:
commit
4cfe7d9c95
@ -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<String> 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<String> 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<String> filterDossierIdsByOwnedKeepUnowned(Set<String> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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<String> 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<String> 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<String> dossierIds);
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<String> onlyAccessibleDossierIds(Set<String> dossierIds) {
|
||||
|
||||
return dossierIds;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@PreFilter("hasPermission(filterObject, 'Dossier', 'OWNER')")
|
||||
public Set<String> onlyOwnedDossierIds(Set<String> dossierIds) {
|
||||
|
||||
return dossierIds;
|
||||
|
||||
}
|
||||
|
||||
@PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'ACCESS_OBJECT')")
|
||||
public List<Dossier> onlyAccessibleDossiers(List<Dossier> dossiers) {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user