Compare commits

..

2 Commits

Author SHA1 Message Date
Maverick Studer
a441909408 Merge branch 'RE-10691-fp' into 'master'
RED-10691: 500 when approving file in a dossier where dossier owner lost manager role

Closes RE-10691

See merge request redactmanager/persistence-service!930
2025-02-04 09:37:04 +01:00
maverickstuder
a7bdbb5495 RED-10691: 500 when approving file in a dossier where dossier owner lost manager role 2025-01-31 13:35:31 +01:00
15 changed files with 96 additions and 414 deletions

View File

@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
@ -101,8 +102,13 @@ public class StatusController implements StatusResource {
var accessibleDossierIds = filterByPermissionsService.onlyViewableDossierIds(new ArrayList<>(filesByDossier.getValue().keySet()));
var response = new HashMap<String, List<FileStatus>>();
for (var dossierId : accessibleDossierIds) {
var allFoundFiles = fileStatusManagementService.findAllDossierIdAndIds(dossierId, filesByDossier.getValue().get(dossierId));
response.put(dossierId, allFoundFiles.stream().map(FileStatusMapper::toFileStatus).collect(Collectors.toList()));
var allFoundFiles = fileStatusManagementService.findAllDossierIdAndIds(dossierId,
filesByDossier.getValue()
.get(dossierId));
response.put(dossierId,
allFoundFiles.stream()
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.toList()));
}
return new JSONPrimitive<>(response);
@ -351,6 +357,10 @@ public class StatusController implements StatusResource {
.build());
var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));
if (dossier.getOwnerId() == null) {
throw new ConflictException("Dossier has no owner!");
}
if (!dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);

View File

@ -2,19 +2,17 @@ package com.iqser.red.service.persistence.v1.internal.api.controller;
import java.util.List;
import jakarta.transaction.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.DossierResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.Param;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -44,17 +42,4 @@ public class DossierInternalController implements DossierResource {
return dossierManagementService.getDossierById(dossierId, includeArchived, includeDeleted);
}
@Override
@Transactional
public Dossier createDossier(@RequestBody CreateOrUpdateDossierRequest dossierRequest) {
if (dossierRequest.getOwnerId().equals("internal_user")) {
dossierRequest.setOwnerId(KeycloakSecurity.getUserId());
// dossierRequest.setOwnerId(KeycloakSecurity.getUserId());
// dossierRequest.setOwnerId(KeycloakSecurity.getUserId());
}
return dossierManagementService.createDossier(dossierRequest);
}
}

View File

@ -2,8 +2,6 @@ package com.iqser.red.service.persistence.v1.internal.api.controller;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;
import java.util.List;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@ -39,11 +37,4 @@ public class DossierTemplateInternalController implements DossierTemplateResourc
return convert(dossierTemplatePersistenceService.getDossierTemplate(dossierTemplateId), DossierTemplate.class);
}
@Override
public List<DossierTemplate> getAllDossierTemplates() {
return convert(dossierTemplatePersistenceService.getAllDossierTemplates(), DossierTemplate.class);
}
}

View File

@ -1,35 +0,0 @@
package com.iqser.red.service.persistence.v1.internal.api.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.management.v1.processor.service.UploadManagementService;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.UploadResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings("PMD")
public class UploadInternalController implements UploadResource {
private final UploadManagementService uploadManagementService;
@Override
public FileUploadResult upload(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions,
@Parameter(name = DISABLE_AUTOMATIC_ANALYSIS_PARAM, description = "Disables automatic redaction for the uploaded file, imports only imported redactions") @RequestParam(value = DISABLE_AUTOMATIC_ANALYSIS_PARAM, required = false, defaultValue = "false") boolean disableAutomaticAnalysis) {
return uploadManagementService.upload(file, dossierId, keepManualRedactions, disableAutomaticAnalysis);
}
}

View File

@ -6,13 +6,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
@ResponseStatus(value = HttpStatus.OK)
@ -37,9 +33,4 @@ public interface DossierResource {
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted);
@ResponseBody
@PostMapping(value = InternalApi.BASE_PATH + REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Dossier createDossier(@RequestBody CreateOrUpdateDossierRequest dossierRequest);
}

View File

@ -1,7 +1,5 @@
package com.iqser.red.service.persistence.service.v1.api.internal.resources;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -31,8 +29,4 @@ public interface DossierTemplateResource {
@GetMapping(value = InternalApi.BASE_PATH + DOSSIER_TEMPLATE_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
DossierTemplate getDossierTemplateById(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId);
@GetMapping(value = InternalApi.BASE_PATH + DOSSIER_TEMPLATE_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierTemplate> getAllDossierTemplates();
}

View File

@ -1,33 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.internal.resources;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
public interface UploadResource {
String DOSSIER_ID = "dossierId";
String FILE_ID = "fileId";
String UPLOAD_PATH = InternalApi.BASE_PATH + "/upload";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DISABLE_AUTOMATIC_ANALYSIS_PARAM = "disableAutomaticAnalysis";
@ResponseBody
@PostMapping(value = UPLOAD_PATH + DOSSIER_ID_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileUploadResult upload(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions,
@Parameter(name = DISABLE_AUTOMATIC_ANALYSIS_PARAM, description = "Disables automatic analysis for the uploaded file, imports only imported redactions") @RequestParam(value = DISABLE_AUTOMATIC_ANALYSIS_PARAM, required = false, defaultValue = "false") boolean disableAutomaticAnalysis);
}

View File

@ -1,16 +1,5 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import static com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException.DOSSIER_NOT_FOUND_MESSAGE;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierMapper;
@ -28,6 +17,17 @@ import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException.DOSSIER_NOT_FOUND_MESSAGE;
@Slf4j
@Service
@RequiredArgsConstructor
@ -47,13 +47,6 @@ public class DossierManagementService {
}
@Transactional
public Dossier createDossier(CreateOrUpdateDossierRequest dossierRequest) {
return MagicConverter.convert(dossierService.addDossier(dossierRequest), Dossier.class, new DossierMapper());
}
@Transactional
public Dossier updateDossier(CreateOrUpdateDossierRequest dossierRequest, String dossierId) {
@ -72,9 +65,8 @@ public class DossierManagementService {
List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossierId);
var relevantFileIds = fileStatuses.stream()
.filter(fileStatus -> fileStatus.getDeleted() == null)
.map(FileModel::getId)
.toList();
.filter(fileStatus -> fileStatus.getDeleted() == null).map(FileModel::getId).toList();
dossierDeletionService.softDeleteDossier(dossierId, relevantFileIds, now);
fileDeletionService.reindexDeletedFiles(dossierId, relevantFileIds);
@ -128,14 +120,12 @@ public class DossierManagementService {
}
@Transactional
public List<String> getAllDossierIdsForDossierTemplateId(String dossierTemplateId, boolean includeArchived, boolean includeDeleted) {
return dossierService.getAllDossierIdsForDossierTemplateId(dossierTemplateId, includeArchived, includeDeleted);
}
@Transactional
public List<String> findAllDossierIdsInDossierTemplateId(String dossierTemplateId, Set<String> dossierIds) {
@ -225,10 +215,7 @@ public class DossierManagementService {
for (String dossierId : dossierIds) {
List<String> fileIds = fileStatusService.getDossierStatus(dossierId)
.stream()
.map(FileModel::getId)
.collect(Collectors.toList());
List<String> fileIds = fileStatusService.getDossierStatus(dossierId).stream().map(FileModel::getId).collect(Collectors.toList());
dossierDeletionService.hardDeleteDossier(dossierId, fileIds);
dossierDeletionService.hardDeleteFileDataAndIndexUpdates(dossierId, fileIds);
}
@ -241,13 +228,10 @@ public class DossierManagementService {
for (String dossierId : dossierIds) {
var dossier = dossierService.getDossierById(dossierId);
List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossierId);
var relevantFileIds = fileStatuses.stream()
.filter(fileStatus -> fileStatus.getDeleted() != null && (fileStatus.getDeleted().equals(dossier.getSoftDeletedTime()) || fileStatus.getDeleted()
.isAfter(dossier.getSoftDeletedTime())))
.map(FileModel::getId)
.collect(Collectors.toList());
var relevantFileIds = fileStatuses.stream().filter(fileStatus -> fileStatus.getDeleted() != null && (fileStatus.getDeleted().equals(dossier.getSoftDeletedTime()) || fileStatus.getDeleted()
.isAfter(dossier.getSoftDeletedTime()))).map(FileModel::getId).collect(Collectors.toList());
dossierDeletionService.undeleteDossier(dossierId, relevantFileIds, dossier.getSoftDeletedTime());
dossierDeletionService.undeleteDossier(dossierId,relevantFileIds,dossier.getSoftDeletedTime());
dossierDeletionService.reindexUndeletedFiles(dossier.getDossierTemplateId(), dossierId, relevantFileIds);
}
@ -294,7 +278,6 @@ public class DossierManagementService {
public DossierChangeResponseV2 changesSinceV2(JSONPrimitive<OffsetDateTime> since) {
return dossierService.changesSinceV2(since.getValue());
}
@ -302,7 +285,7 @@ public class DossierManagementService {
@Transactional
public List<Dossier> getDossiersByIds(Set<String> viewableDossierIds) {
return getConvertedAllDossiers(dossierService.getAllDossiers(viewableDossierIds), true, true);
return getConvertedAllDossiers(dossierService.getAllDossiers(viewableDossierIds), true,true);
}
}

View File

@ -59,8 +59,6 @@ public class DossierService {
validateDossierName(createOrUpdateDossierRequest);
try {
System.out.println("PSPS: " + createOrUpdateDossierRequest);
log.info("PSPS: " + createOrUpdateDossierRequest);
return dossierPersistenceService.insert(createOrUpdateDossierRequest);
} catch (Exception e) {
if (e.getCause() instanceof ConstraintViolationException) {

View File

@ -48,7 +48,6 @@ public class FileService {
public JSONPrimitive<String> upload(AddFileRequest request, boolean keepManualRedactions, long size, boolean disableAutomaticAnalysis) {
log.info("MMMM: in file upload");
dossierPersistenceService.getAndValidateDossier(request.getDossierId());
var existingStatus = retrieveStatus(request.getFileId());

View File

@ -260,7 +260,6 @@ public class FileStatusService {
protected void addToAnalysisQueue(String dossierId, String fileId, boolean priority, Set<Integer> sectionsToReanalyse, AnalysisType analysisType) {
log.info("OOOO: in analysis queue");
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
var fileEntity = fileStatusPersistenceService.getStatus(fileId);
@ -889,11 +888,8 @@ public class FileStatusService {
@Transactional
public void createStatus(String dossierId, String fileId, String uploader, String filename, long size, boolean disableAutomaticAnalysis) {
log.info("NNNN: Creating status for {}", fileId);
fileStatusPersistenceService.createStatus(dossierId, fileId, filename, uploader, size, disableAutomaticAnalysis);
log.info("NNNN: Websocket");
websocketService.sendFileEvent(dossierId, fileId, FileEventType.CREATE);
log.info("NNNN: AnalysisQueue");
addToAnalysisQueue(dossierId, fileId, false, Set.of(), AnalysisType.DEFAULT);
}

View File

@ -1,253 +0,0 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
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.utils.FileUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import com.knecon.fforesight.tenantcommons.TenantContext;
import io.micrometer.core.annotation.Timed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class UploadManagementService {
private static final int THRESHOLD_ENTRIES = 10000; // Maximum number of files allowed
private static final int THRESHOLD_SIZE = 1000000000; // 1 GB total unzipped data
private static final double THRESHOLD_RATIO = 10; // Max allowed compression ratio
private final UploadService uploadService;
private final FileFormatValidationService fileFormatValidationService;
@Timed
public FileUploadResult upload(MultipartFile file, String dossierId, boolean keepManualRedactions, boolean disableAutomaticAnalysis) {
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
throw new BadRequestException("Could not upload file, no filename provided.");
}
String extension = getExtension(originalFilename);
try {
return switch (extension) {
case "zip" -> handleZip(dossierId, file.getBytes(), keepManualRedactions, disableAutomaticAnalysis);
case "csv" -> uploadService.importCsv(dossierId, file.getBytes());
default -> {
validateExtensionOrThrow(extension);
yield uploadService.processSingleFile(dossierId, originalFilename, file.getBytes(), keepManualRedactions, disableAutomaticAnalysis);
}
};
} catch (IOException e) {
throw new BadRequestException("Failed to process file: " + e.getMessage(), e);
}
}
private void validateExtensionOrThrow(String extension) {
if (!fileFormatValidationService.getAllFileFormats().contains(extension)) {
throw new BadRequestException("Invalid file uploaded (unrecognized extension).");
}
if (!fileFormatValidationService.getValidFileFormatsForTenant(TenantContext.getTenantId()).contains(extension)) {
throw new NotAllowedException("Insufficient permissions for this file type.");
}
}
/**
* 1. Write the uploaded content to a temp ZIP file
* 2. Check the number of entries and reject if too big or if symlinks found
* 3. Unzip and process each file, while checking size and ratio.
*/
private FileUploadResult handleZip(String dossierId, byte[] fileContent, boolean keepManualRedactions, boolean disableAutomaticAnalysis) throws IOException {
File tempZip = FileUtils.createTempFile(UUID.randomUUID().toString(), ".zip");
try (FileOutputStream fos = new FileOutputStream(tempZip)) {
IOUtils.write(fileContent, fos);
}
validateZipEntries(tempZip);
try {
ZipData zipData = processZipContents(tempZip, dossierId, keepManualRedactions, disableAutomaticAnalysis);
if (zipData.csvBytes != null) {
try {
FileUploadResult csvResult = uploadService.importCsv(dossierId, zipData.csvBytes);
zipData.fileUploadResult.getProcessedAttributes().addAll(csvResult.getProcessedAttributes());
zipData.fileUploadResult.getProcessedFileIds().addAll(csvResult.getProcessedFileIds());
} catch (Exception e) {
log.debug("CSV file inside ZIP failed to import", e);
}
} else if (zipData.fileUploadResult.getFileIds().isEmpty()) {
if (zipData.containedUnpermittedFiles) {
throw new NotAllowedException("Zip file contains unpermitted files.");
} else {
throw new BadRequestException("Only unsupported files in the ZIP.");
}
}
return zipData.fileUploadResult;
} finally {
if (!tempZip.delete()) {
log.warn("Could not delete temporary ZIP file: {}", tempZip);
}
}
}
private void validateZipEntries(File tempZip) throws IOException {
try (FileInputStream fis = new FileInputStream(tempZip); ZipFile zipFile = new ZipFile(fis.getChannel())) {
int count = 0;
var entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry ze = entries.nextElement();
if (ze.isUnixSymlink()) {
throw new BadRequestException("ZIP-files with symlinks are not allowed.");
}
if (!ze.isDirectory() && !ze.getName().startsWith(".")) {
count++;
if (count > THRESHOLD_ENTRIES) {
throw new BadRequestException("ZIP-Bomb detected: too many entries.");
}
}
}
}
}
private ZipData processZipContents(File tempZip, String dossierId, boolean keepManualRedactions, boolean disableAutomaticAnalysis) throws IOException {
ZipData zipData = new ZipData();
try (FileInputStream fis = new FileInputStream(tempZip); ZipFile zipFile = new ZipFile(fis.getChannel())) {
var entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
if (entry.isDirectory() || entry.getName().startsWith(".")) {
continue;
}
byte[] entryBytes = readEntryWithRatioCheck(entry, zipFile);
zipData.totalSizeArchive += entryBytes.length;
if (zipData.totalSizeArchive > THRESHOLD_SIZE) {
throw new BadRequestException("ZIP-Bomb detected (exceeds total size limit).");
}
String extension = getExtension(entry.getName());
if ("csv".equalsIgnoreCase(extension)) {
zipData.csvBytes = entryBytes;
} else {
handleRegularFile(dossierId, entryBytes, extension, extractFileName(entry.getName()), zipData, keepManualRedactions, disableAutomaticAnalysis);
}
}
}
return zipData;
}
private byte[] readEntryWithRatioCheck(ZipArchiveEntry entry, ZipFile zipFile) throws IOException {
long compressedSize = entry.getCompressedSize() > 0 ? entry.getCompressedSize() : 1;
try (var is = zipFile.getInputStream(entry); var bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
int totalUncompressed = 0;
while ((bytesRead = is.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
totalUncompressed += bytesRead;
double ratio = (double) totalUncompressed / compressedSize;
if (ratio > THRESHOLD_RATIO) {
throw new BadRequestException("ZIP-Bomb detected (compression ratio too high).");
}
}
return bos.toByteArray();
}
}
private void handleRegularFile(String dossierId,
byte[] fileBytes,
String extension,
String fileName,
ZipData zipData,
boolean keepManualRedactions,
boolean disableAutomaticAnalysis) {
if (!fileFormatValidationService.getAllFileFormats().contains(extension)) {
zipData.containedUnpermittedFiles = false;
return;
}
if (!fileFormatValidationService.getValidFileFormatsForTenant(TenantContext.getTenantId()).contains(extension)) {
zipData.containedUnpermittedFiles = true;
return;
}
try {
FileUploadResult result = uploadService.processSingleFile(dossierId, fileName, fileBytes, keepManualRedactions, disableAutomaticAnalysis);
zipData.fileUploadResult.getFileIds().addAll(result.getFileIds());
} catch (Exception e) {
log.debug("Failed to process file '{}' in ZIP: {}", fileName, e.getMessage(), e);
}
}
private String extractFileName(String path) {
int idx = path.lastIndexOf('/');
return (idx >= 0) ? path.substring(idx + 1) : path;
}
private String getExtension(String fileName) {
int idx = fileName.lastIndexOf('.');
if (idx < 0) {
return "";
}
return fileName.substring(idx + 1).toLowerCase(Locale.ROOT);
}
@FieldDefaults(level = AccessLevel.PUBLIC)
private static final class ZipData {
byte[] csvBytes;
int totalSizeArchive;
FileUploadResult fileUploadResult = new FileUploadResult();
boolean containedUnpermittedFiles;
}
}

View File

@ -74,9 +74,8 @@ public class UploadService {
var storageId = StorageIdUtils.getStorageId(dossierId, fileId, FileType.UNTOUCHED);
try {
log.info("MMMM: Will store object with id {} into storage {}", fileId, storageId);
storageService.storeObject(TenantContext.getTenantId(), storageId, new ByteArrayInputStream(fileContent));
log.info("MMMM: Uploading file");
fileService.upload(new AddFileRequest(fileName, fileId, dossierId, KeycloakSecurity.getUserId()), keepManualRedactions, fileContent.length, disableAutomaticAnalysis);
} catch (Exception e) {

View File

@ -1,13 +1,18 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
@ -18,8 +23,8 @@ import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.TypeProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.LegalBasisEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
@ -33,6 +38,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.warning.ApproveResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.warning.WarningType;
import feign.FeignException;
public class ApprovalTest extends AbstractPersistenceServerServiceTest {
@Autowired
@ -44,15 +51,15 @@ public class ApprovalTest extends AbstractPersistenceServerServiceTest {
@Autowired
private DossierTesterAndProvider dossierTesterAndProvider;
@Autowired
private TypeProvider typeProvider;
@Autowired
private FileClient fileClient;
@SpyBean
private LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
@SpyBean
private DossierACLService dossierACLService;
@Test
public void testApprovalNoWarnings() {
@ -181,4 +188,54 @@ public class ApprovalTest extends AbstractPersistenceServerServiceTest {
assertTrue(approveResponse.getFileWarnings().isEmpty());
}
@Test
void testApprovalWhenDossierHasNoOwner() {
DossierTemplateModel dossierTemplateModel = dossierTemplateTesterAndProvider.provideTestTemplate();
Dossier dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplateModel);
FileStatus file = fileTesterAndProvider.testAndProvideFile(dossier, "some-file");
fileTesterAndProvider.markFileAsProcessed(dossier.getId(), file.getFileId());
EntityLog entityLog = new EntityLog();
when(entityLogService.getEntityLog(anyString(), anyString(), anyBoolean())).thenReturn(entityLog);
List<com.iqser.red.service.persistence.management.v1.processor.service.users.model.User> allUsers = new ArrayList<>();
allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
.userId("manageradmin1@test.com")
.email("manageradmin1@test.com")
.isActive(true)
.roles(Set.of(getAllRoles()))
.build());
allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
.userId("manageradmin2@test.com")
.email("manageradmin2@test.com")
.isActive(true)
.roles(Set.of("RED_USER"))
.build());
when(usersClient.getAllUsers(false)).thenReturn(allUsers);
when(usersClient.getAllUsers(true)).thenReturn(allUsers);
doAnswer(invocation -> {
Dossier arg = invocation.getArgument(0);
if (dossier.getId().equals(arg.getId())) {
Dossier emptyDossier = new Dossier();
emptyDossier.setId(arg.getId());
return emptyDossier;
} else {
return invocation.callRealMethod();
}
}).when(dossierACLService).enhanceDossierWithACLData(any(Dossier.class));
FeignException ex = assertThrows(FeignException.Conflict.class, () -> {
fileClient.setStatusApproved(dossier.getId(), file.getFileId(), false);
});
assertTrue(ex.getMessage().contains("Dossier has no owner!"));
}
}

View File

@ -261,7 +261,7 @@ public abstract class AbstractPersistenceServerServiceTest {
@MockBean
protected TenantsClient tenantsClient;
@MockBean
private UsersClient usersClient;
protected UsersClient usersClient;
@Autowired
protected EncryptionDecryptionService encryptionDecryptionService;
@Autowired
@ -286,7 +286,7 @@ public abstract class AbstractPersistenceServerServiceTest {
private CurrentApplicationTypeProvider currentApplicationTypeProvider;
private static String[] getAllRoles() {
protected static String[] getAllRoles() {
var allRoles = ApplicationRoles.ROLE_DATA.entrySet()
.stream()