RED-5368: View dossier permissions are not working for dossier status controller

This commit is contained in:
Maverick Studer 2024-01-18 14:13:18 +01:00
parent f608aa1043
commit 2505cb70d2
10 changed files with 186 additions and 59 deletions

View File

@ -4,7 +4,6 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_STATUS;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.transaction.Transactional;
@ -17,7 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.ColorUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierStatusMapper;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierStatusResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStatusRequest;
@ -67,10 +65,7 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatusForTemplate(@PathVariable("dossierTemplateId") String dossierTemplateId) {
return dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
return dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId);
}
@ -78,10 +73,7 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatuses(@RequestBody List<String> dossierTemplateIds) {
return dossierStatusPersistenceService.getAllDossierStatuses(dossierTemplateIds)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
return dossierStatusPersistenceService.getAllDossierStatuses(dossierTemplateIds);
}
@ -90,7 +82,7 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public DossierStatusInfo getDossierStatus(@PathVariable("dossierStatusId") String dossierStatusId) {
return MagicConverter.convert(dossierStatusPersistenceService.getDossierStatus(dossierStatusId), DossierStatusInfo.class, new DossierStatusMapper());
return dossierStatusPersistenceService.getDossierStatusInfo(dossierStatusId);
}
@ -102,4 +94,8 @@ public class DossierStatusController implements DossierStatusResource {
dossierStatusPersistenceService.deleteDossierStatus(dossierStatusId, replaceDossierStatusId);
}
}

View File

@ -50,6 +50,7 @@ public class DossierStatusEntity {
@Column(updatable = false, insertable = false, name = "dossier_template_id")
private String dossierTemplateId;
@Builder.Default
@OneToMany(mappedBy = "dossierStatus", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<DossierEntity> dossiers = new ArrayList<>();

View File

@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -14,6 +15,7 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierIdAndStatusProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileRepository;
@ -41,6 +43,8 @@ public class DossierTemplateStatsService {
private final DossierRepository dossierRepository;
private final FilterByPermissionsService filterByPermissionsService;
public DossierTemplateStats getDossierTemplateStats(String dossierTemplateId) {
@ -58,16 +62,19 @@ public class DossierTemplateStatsService {
dts.setDossierTemplateId(dossierTemplateId);
dts.setName(dossierTemplateName);
dts.setDossierTemplateStatus(dossierTemplateStatus);
dts.setNumberOfArchivedDossiers(dossierRepository.countArchived(dossierTemplateId));
dts.setNumberOfActiveDossiers(dossierRepository.countActive(dossierTemplateId));
dts.setNumberOfDeletedDossiers(dossierRepository.countSofDeleted(dossierTemplateId));
dts.setNumberOfArchivedDossiers(filterByPermissionsService.onlyViewableDossierIds(dossierRepository.findArchivedIds(dossierTemplateId)).size());
dts.setNumberOfActiveDossiers(filterByPermissionsService.onlyViewableDossierIds(dossierRepository.findActiveIds(dossierTemplateId)).size());
dts.setNumberOfDeletedDossiers(filterByPermissionsService.onlyViewableDossierIds(dossierRepository.findSoftDeletedIds(dossierTemplateId)).size());
dts.setNumberOfActiveFiles(fileRepository.countActiveFiles(dossierTemplateId));
dts.setNumberOfSoftDeletedFiles(fileRepository.countSoftDeletedFilesPerDossierTemplateId(dossierTemplateId));
var processingCounts = fileRepository.countFilesByProcessingStatus(dossierTemplateId);
var workflowCounts = fileRepository.countFilesByWorkflowStatus(dossierTemplateId);
var pageCounts = fileRepository.countPages(dossierTemplateId);
var dossierStatusCounts = dossierRepository.countByDossierStatus(dossierTemplateId);
Map<Optional<String>, List<String>> dossierIdsGroupedByDossierStatusId = dossierRepository.findDossierIdAndStatusForTemplate(dossierTemplateId)
.stream()
.collect(Collectors.groupingBy(dossierIdAndStatusProjection -> Optional.ofNullable(dossierIdAndStatusProjection.getDossierStatusId()),
Collectors.mapping(DossierIdAndStatusProjection::getDossierId, Collectors.toList())));
dts.setNumberOfPages(pageCounts.getNumberOfAnalyzedPages());
dts.setNumberOfExcludedPages(pageCounts.getNumberOfExcludedPages());
@ -78,11 +85,13 @@ public class DossierTemplateStatsService {
dts.setFileCountPerWorkflowStatus(workflowCounts.stream()
.map(t -> new DossierTemplateStats.WorkflowStatusCount(t.getWorkflowStatus(), t.getCount()))
.collect(Collectors.toList()));
dts.setDossierCountByStatus(dossierStatusCounts.stream()
.map(t -> new DossierTemplateStats.DossierStatusCount(t.getDossierStatusId(), t.getCount()))
.collect(Collectors.toList()));
dts.setDossierCountByStatus(dossierIdsGroupedByDossierStatusId.entrySet()
.stream()
// the undefined status should be mapped to a specific string instead of using null
.map(entry -> new DossierTemplateStats.DossierStatusCount(entry.getKey().orElse(null), filterByPermissionsService.onlyViewableDossierIds(entry.getValue()).size()))
.toList());
dts.setDossiersInTemplate(dossierRepository.findActiveDossierIdsForTemplate(dossierTemplateId));
dts.setDossiersInTemplate(filterByPermissionsService.onlyViewableDossierIds(dossierRepository.findActiveDossierIdsForTemplate(dossierTemplateId)));
return dts;
}

View File

@ -0,0 +1,81 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.util.List;
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
public class FilterByPermissionsService {
@PreFilter("hasPermission(filterObject, 'Dossier', 'VIEW_OBJECT')")
public List<String> onlyViewableDossierIds(List<String> dossierIds) {
return dossierIds;
}
@PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> onlyViewableDossiers(List<Dossier> dossiers) {
return dossiers;
}
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public List<String> onlyAccessibleDossierIds(List<String> dossierIds) {
return dossierIds;
}
@PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'ACCESS_OBJECT')")
public List<Dossier> onlyAccessibleDossiers(List<Dossier> dossiers) {
return dossiers;
}
@PreFilter("hasPermission(filterObject, 'Dossier', 'REVIEW')")
public List<String> onlyReviewableDossierIds(List<String> dossierIds) {
return dossierIds;
}
@PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'REVIEW')")
public List<Dossier> onlyReviewableDossiers(List<Dossier> dossiers) {
return dossiers;
}
@PreFilter("hasPermission(filterObject, 'Dossier', 'APPROVE')")
public List<String> onlyApprovableDossierIds(List<String> dossierIds) {
return dossierIds;
}
@PreFilter("hasPermission(filterObject.dossierId, 'Dossier', 'APPROVE')")
public List<Dossier> onlyApprovableDossiers(List<Dossier> dossiers) {
return dossiers;
}
}

View File

@ -4,6 +4,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import jakarta.transaction.Transactional;
@ -16,8 +17,10 @@ import com.iqser.red.service.persistence.management.v1.processor.exception.BadRe
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierStatusRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierStatusFilteringMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import lombok.RequiredArgsConstructor;
@ -29,6 +32,7 @@ public class DossierStatusPersistenceService {
private final DossierStatusRepository dossierStatusRepository;
private final DossierTemplatePersistenceService dossierTemplatePersistenceService;
private final DossierStatusFilteringMapper dossierStatusFilteringMapper;
private final static int MAX_STATUS_NAME_LENGTH = 255;
@ -74,7 +78,8 @@ public class DossierStatusPersistenceService {
}
private void validateDossierStatusForTemplate(String dossierStatusName, String dossierTemplateId, String dossierStatusId) {
@Transactional
public void validateDossierStatusForTemplate(String dossierStatusName, String dossierTemplateId, String dossierStatusId) {
if (!StringUtils.isEmpty(dossierStatusName) && dossierStatusName.length() > MAX_STATUS_NAME_LENGTH) {
throw new BadRequestException(String.format("The name is too long (%s), max length %s", dossierStatusName.length(), MAX_STATUS_NAME_LENGTH));
@ -88,28 +93,42 @@ public class DossierStatusPersistenceService {
}
@Transactional
public List<DossierStatusInfo> getAllDossierStatusForTemplate(String dossierTemplateId) {
return dossierStatusRepository.getAllDossierStatusForDossierTemplate(Collections.singletonList(dossierTemplateId));
return dossierStatusRepository.findByDossierTemplateIdIn(Collections.singletonList(dossierTemplateId))
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class, dossierStatusFilteringMapper))
.collect(Collectors.toList());
}
@Transactional
public List<DossierStatusInfo> getAllDossierStatuses(List<String> dossierTemplateIds) {
return dossierStatusRepository.getAllDossierStatusForDossierTemplate(dossierTemplateIds);
}
public DossierStatusInfo getDossierStatusInfo(String dossierStatusId) {
return dossierStatusRepository.findProjectionById(dossierStatusId)
.orElseThrow(() -> new NotFoundException(String.format(DOSSIER_STATUS_NOT_FOUND_MESSAGE, dossierStatusId)));
return dossierStatusRepository.findByDossierTemplateIdIn(dossierTemplateIds)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class, dossierStatusFilteringMapper))
.collect(Collectors.toList());
}
@Transactional
public DossierStatusEntity getDossierStatus(String dossierStatusId) {
return dossierStatusRepository.findById(dossierStatusId).orElseThrow(() -> new NotFoundException(String.format(DOSSIER_STATUS_NOT_FOUND_MESSAGE, dossierStatusId)));
}
@Transactional
public DossierStatusInfo getDossierStatusInfo(String dossierStatusId) {
return MagicConverter.convert(dossierStatusRepository.findById(dossierStatusId)
.orElseThrow(() -> new NotFoundException(String.format(DOSSIER_STATUS_NOT_FOUND_MESSAGE, dossierStatusId))), DossierStatusInfo.class, dossierStatusFilteringMapper);
}

View File

@ -1,10 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection;
public interface DossierCountByStatusProjection {
int getCount();
public interface DossierIdAndStatusProjection {
String getDossierStatusId();
String getDossierId();
}

View File

@ -9,7 +9,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierCountByStatusProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierIdAndStatusProjection;
public interface DossierRepository extends JpaRepository<DossierEntity, String> {
@ -57,9 +57,20 @@ public interface DossierRepository extends JpaRepository<DossierEntity, String>
@Query("select count(d) from DossierEntity d where d.archivedTime is null and d.softDeletedTime is null and d.hardDeletedTime is null and d.dossierTemplateId = :dossierTemplateId")
int countActive(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select d.id from DossierEntity d where d.archivedTime is not null and d.softDeletedTime is null and d.hardDeletedTime is null and d.dossierTemplateId = :dossierTemplateId")
List<String> findArchivedIds(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select d.dossierStatusId as dossierStatusId, count(d) as count from DossierEntity d where d.archivedTime is null and d.softDeletedTime is null and d.hardDeletedTime is null and d.dossierTemplateId = :dossierTemplateId group by d.dossierStatusId")
List<DossierCountByStatusProjection> countByDossierStatus(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select d.id from DossierEntity d where d.hardDeletedTime is null and d.softDeletedTime is not null and d.dossierTemplateId = :dossierTemplateId")
List<String> findSoftDeletedIds(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select d.id from DossierEntity d where d.archivedTime is null and d.softDeletedTime is null and d.hardDeletedTime is null and d.dossierTemplateId = :dossierTemplateId")
List<String> findActiveIds(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select distinct d.dossierStatusId as dossierStatusId, d.id as dossierId from DossierEntity d where d.archivedTime is null and d.softDeletedTime is null and d.hardDeletedTime is null and d.dossierTemplateId = :dossierTemplateId")
List<DossierIdAndStatusProjection> findDossierIdAndStatusForTemplate(@Param("dossierTemplateId") String dossierTemplateId);
@Query("select d.id from DossierEntity d where d.dossierTemplateId = :dossierTemplateId and d.archivedTime is null and d.softDeletedTime is null and d.hardDeletedTime is null")

View File

@ -2,22 +2,16 @@ package com.iqser.red.service.persistence.management.v1.processor.service.persis
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierStatusEntity;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
public interface DossierStatusRepository extends JpaRepository<DossierStatusEntity, String> {
@Query("select new com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo(s.id, s.name, s.description, s.dossierTemplateId, s.color, s.rank, count(d)) from DossierStatusEntity s left outer join s.dossiers d where s.dossierTemplateId in (:dossierTemplateIds) group by s order by s.rank ASC")
List<DossierStatusInfo> getAllDossierStatusForDossierTemplate(@Param("dossierTemplateIds") List<String> dossierTemplateIds);
@Query("select new com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo(s.id, s.name, s.description, s.color, s.dossierTemplateId, s.rank, count(d)) from DossierStatusEntity s left outer join s.dossiers d where s.id = :dossierStatusId group by s")
Optional<DossierStatusInfo> findProjectionById(@Param("dossierStatusId") String dossierStatusId);
List<DossierStatusEntity> findByDossierTemplateIdIn(List<String> dossierTemplateIds);
@Modifying
@Query("update DossierStatusEntity d set d.rank = :newRank where d.id = :dossierStatusId")

View File

@ -0,0 +1,32 @@
package com.iqser.red.service.persistence.management.v1.processor.utils;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierStatusEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.FilterByPermissionsService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
import lombok.AllArgsConstructor;
@Component
@AllArgsConstructor
public class DossierStatusFilteringMapper implements BiConsumer<DossierStatusEntity, DossierStatusInfo> {
private final FilterByPermissionsService filterByPermissionsService;
@Override
public void accept(DossierStatusEntity dossierStatusEntity, DossierStatusInfo dossierStatusInfo) {
dossierStatusInfo.setDossierCount((long) filterByPermissionsService.onlyViewableDossierIds(dossierStatusEntity.getDossiers()
.stream()
.map(DossierEntity::getId)
.collect(Collectors.toList())).size());
}
}

View File

@ -1,16 +0,0 @@
package com.iqser.red.service.persistence.management.v1.processor.utils;
import java.util.function.BiConsumer;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierStatusEntity;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
public class DossierStatusMapper implements BiConsumer<DossierStatusEntity, DossierStatusInfo> {
@Override
public void accept(DossierStatusEntity dossierStatusEntity, DossierStatusInfo dossierStatusInfo) {
dossierStatusInfo.setDossierCount((long) dossierStatusEntity.getDossiers().size());
}
}