Skip to content

Commit

Permalink
[P4ADEV-1829] Moved file save logics to FileStorerService
Browse files Browse the repository at this point in the history
  • Loading branch information
mscarsel committed Jan 14, 2025
1 parent 0a9f22b commit d6c5f62
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 199 deletions.
3 changes: 3 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ microservice-chart:

SHARED_FOLDER_ROOT: "/shared"

ORGANIZATION_BASE_URL: "http://p4pa-organization-microservice-chart:8080"
AUTH_SERVER_BASE_URL: "http://p4pa-auth-microservice-chart:8080/payhub"

envSecret:
APPLICATIONINSIGHTS_CONNECTION_STRING: appinsights-connection-string
FILE_ENCRYPT_PASSWORD: file-encrypt-password

# nodeSelector: {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package it.gov.pagopa.pu.fileshare.config;

import it.gov.pagopa.pu.fileshare.dto.generated.IngestionFlowFileType;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "folders")
public class FoldersPathsConfig {
private String shared;
private Map<IngestionFlowFileType,String> ingestionFlowFileTypePaths;

public String getIngestionFlowFilePath(IngestionFlowFileType ingestionFlowFileType) {
return Optional.ofNullable(
ingestionFlowFileTypePaths.get(ingestionFlowFileType))
.orElseThrow(()-> {
log.debug("No path configured for ingestionFlowFileType {}",ingestionFlowFileType);
return new UnsupportedOperationException();
});
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
package it.gov.pagopa.pu.fileshare.service;

import it.gov.pagopa.pu.fileshare.exception.custom.FileUploadException;
import it.gov.pagopa.pu.fileshare.exception.custom.InvalidFileException;
import it.gov.pagopa.pu.fileshare.util.AESUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
public class FileService {
private final String sharedFolderRootPath;
private final String fileEncryptPassword;

public FileService(@Value("${folders.shared}") String sharedFolderRootPath,
@Value(("${app.fileEncryptPassword}")) String fileEncryptPassword) {
this.sharedFolderRootPath = sharedFolderRootPath;
this.fileEncryptPassword = fileEncryptPassword;
}

public void validateFile(MultipartFile ingestionFlowFile, String validFileExt) {
if( ingestionFlowFile == null){
Expand All @@ -38,7 +21,7 @@ public void validateFile(MultipartFile ingestionFlowFile, String validFileExt) {
validateFilename(filename);
}

private static void validateFilename(String filename) {
public static void validateFilename(String filename) {
if(Stream.of("..", "\\", "/").anyMatch(filename::contains)){
log.debug("Invalid ingestion flow filename");
throw new InvalidFileException("Invalid filename");
Expand All @@ -51,48 +34,4 @@ private static void validateFileExtension(String validFileExt, String filename)
throw new InvalidFileException("Invalid file extension");
}
}

private Path getFilePath(String relativePath, String filename) {
String basePath = sharedFolderRootPath+relativePath;
Path fileLocation = Paths.get(basePath,filename).normalize();
if(!fileLocation.startsWith(basePath)){
log.debug("Invalid file path");
throw new InvalidFileException("Invalid file path");
}
return fileLocation;
}

public void saveToSharedFolder(MultipartFile file, String relativePath){
if(file==null){
log.debug("File is mandatory");
throw new FileUploadException("File is mandatory");
}

String filename = org.springframework.util.StringUtils.cleanPath(StringUtils.defaultString(file.getOriginalFilename()));
validateFilename(filename);
Path fileLocation = getFilePath(relativePath, filename);
//create missing parent folder, if any
try {
if (!fileLocation.toAbsolutePath().getParent().toFile().exists())
Files.createDirectories(fileLocation.toAbsolutePath().getParent());
encryptAndSaveFile(file, fileLocation);
}catch (Exception e) {
log.debug(
"Error uploading file to folder %s%s".formatted(sharedFolderRootPath,
relativePath), e);
throw new FileUploadException(
"Error uploading file to folder %s%s [%s]".formatted(
sharedFolderRootPath, relativePath, e.getMessage()));
}
log.debug("File upload to folder %s%s completed".formatted(sharedFolderRootPath,
relativePath));
}

private void encryptAndSaveFile(MultipartFile file, Path fileLocation)
throws IOException {
try(InputStream is = file.getInputStream();
InputStream cipherIs = AESUtils.encrypt(fileEncryptPassword, is)){
Files.copy(cipherIs, fileLocation, StandardCopyOption.REPLACE_EXISTING);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package it.gov.pagopa.pu.fileshare.service;

import it.gov.pagopa.pu.fileshare.config.FoldersPathsConfig;
import it.gov.pagopa.pu.fileshare.exception.custom.FileUploadException;
import it.gov.pagopa.pu.fileshare.exception.custom.InvalidFileException;
import it.gov.pagopa.pu.fileshare.util.AESUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
public class FileStorerService {
private final FoldersPathsConfig foldersPathsConfig;
private final String fileEncryptPassword;

public FileStorerService(FoldersPathsConfig foldersPathsConfig,
@Value(("${app.fileEncryptPassword}")) String fileEncryptPassword) {
this.foldersPathsConfig = foldersPathsConfig;
this.fileEncryptPassword = fileEncryptPassword;
}

private Path getFilePath(String relativePath, String filename) {
String basePath = foldersPathsConfig.getShared()+relativePath;
Path fileLocation = Paths.get(basePath,filename).normalize();
if(!fileLocation.startsWith(basePath)){
log.debug("Invalid file path");
throw new InvalidFileException("Invalid file path");
}
return fileLocation;
}

public String saveToSharedFolder(MultipartFile file, String relativePath){
if(file==null){
log.debug("File is mandatory");
throw new FileUploadException("File is mandatory");
}

String sharedFolderRootPath = foldersPathsConfig.getShared();
String filename = org.springframework.util.StringUtils.cleanPath(
StringUtils.defaultString(file.getOriginalFilename()));
FileService.validateFilename(filename);
Path fileLocation = getFilePath(relativePath, filename);
//create missing parent folder, if any
try {
if (!Files.exists(fileLocation.getParent()))
Files.createDirectories(fileLocation.getParent());
encryptAndSaveFile(file, fileLocation);
}catch (Exception e) {
String errorMessage = "Error uploading file to folder %s%s".formatted(
sharedFolderRootPath,
relativePath);
log.debug(
errorMessage, e);
throw new FileUploadException(
errorMessage);
}
log.debug("File upload to folder %s%s completed".formatted(sharedFolderRootPath,
relativePath));
return Paths.get(relativePath,filename).toString();
}

private void encryptAndSaveFile(MultipartFile file, Path fileLocation)
throws IOException {
try(InputStream is = file.getInputStream();
InputStream cipherIs = AESUtils.encrypt(fileEncryptPassword, is)){
Files.copy(cipherIs, fileLocation, StandardCopyOption.REPLACE_EXISTING);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package it.gov.pagopa.pu.fileshare.service.ingestion;

import it.gov.pagopa.pu.fileshare.config.FoldersPathsConfig;
import it.gov.pagopa.pu.fileshare.dto.generated.IngestionFlowFileType;
import it.gov.pagopa.pu.fileshare.service.FileService;
import it.gov.pagopa.pu.fileshare.service.FileStorerService;
import it.gov.pagopa.pu.fileshare.service.UserAuthorizationService;
import it.gov.pagopa.pu.p4paauth.dto.generated.UserInfo;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -14,24 +16,28 @@
public class IngestionFlowFileServiceImpl implements IngestionFlowFileService {
private final UserAuthorizationService userAuthorizationService;
private final FileService fileService;
private final FileStorerService fileStorerService;
private final FoldersPathsConfig foldersPathsConfig;
private final String validIngestionFlowFileExt;
private final String ingestionFlowFilePath;

public IngestionFlowFileServiceImpl(
UserAuthorizationService userAuthorizationService, FileService fileService,
@Value("${uploads.ingestion-flow-file.valid-extension}") String validIngestionFlowFileExt,
@Value("${folders.ingestion-flow-file.path}") String ingestionFlowFilePath
FileStorerService fileStorerService,
FoldersPathsConfig foldersPathsConfig,
@Value("${uploads.ingestion-flow-file.valid-extension}") String validIngestionFlowFileExt
) {
this.userAuthorizationService = userAuthorizationService;
this.fileService = fileService;
this.fileStorerService = fileStorerService;
this.foldersPathsConfig = foldersPathsConfig;
this.validIngestionFlowFileExt = validIngestionFlowFileExt;
this.ingestionFlowFilePath = ingestionFlowFilePath;
}

@Override
public void uploadIngestionFlowFile(Long organizationId, IngestionFlowFileType ingestionFlowFileType, MultipartFile ingestionFlowFile, UserInfo user, String accessToken) {
userAuthorizationService.checkUserAuthorization(organizationId, user, accessToken);
fileService.validateFile(ingestionFlowFile, validIngestionFlowFileExt);
fileService.saveToSharedFolder(ingestionFlowFile,ingestionFlowFilePath);
String savedFilePath = fileStorerService.saveToSharedFolder(ingestionFlowFile,
foldersPathsConfig.getIngestionFlowFilePath(ingestionFlowFileType));
}
}
9 changes: 7 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,13 @@ folders:
process-target-sub-folders:
archive: "\${PROCESS_TARGET_SUB_FOLDER_ARCHIVE:archive}"
errors: "\${PROCESS_TARGET_SUB_FOLDER_ERRORS:errors}"
ingestion-flow-file:
path: "\${INGESTION_FLOW_FILE_PATH:/ingestion_flow_file}"
ingestion-flow-file-type-paths:
RECEIPT: "\${INGESTION_FLOW_FILE_RECEIPT_PATH:/receipt}"
PAYMENTS_REPORTING: "\${INGESTION_FLOW_FILE_PAYMENTS_REPORTING_PATH:/payments_reporting}"
OPI: "\${INGESTION_FLOW_FILE_OPI_PATH:/opi}"
TREASURY_CSV: "\${INGESTION_FLOW_FILE_TREASURY_CSV_PATH:/treasury_csv}"
TREASURY_XLS: "\${INGESTION_FLOW_FILE_TREASURY_XLS_PATH:/treasury_xls}"
TREASURY_POSTE: "\${INGESTION_FLOW_FILE_TREASURY_POSTE_PATH:/treasury_poste}"

rest:
default-timeout:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package it.gov.pagopa.pu.fileshare.config;

import it.gov.pagopa.pu.fileshare.dto.generated.IngestionFlowFileType;
import java.util.EnumMap;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class FoldersPathsConfigTest {
private FoldersPathsConfig foldersPathsConfig;

@BeforeEach
void setUp() {
foldersPathsConfig = new FoldersPathsConfig();

}

@Test
void givenPopulatedPathWhenGetIngestionFlowFilePathThenOK(){
String expected = "/receipt";
Map<IngestionFlowFileType,String> paths = new EnumMap<>(
IngestionFlowFileType.class);
paths.put(IngestionFlowFileType.RECEIPT, "/receipt");
foldersPathsConfig.setIngestionFlowFileTypePaths(paths);

String result = foldersPathsConfig.getIngestionFlowFilePath(
IngestionFlowFileType.RECEIPT);

Assertions.assertEquals(expected,result);
}

@Test
void givenNoPathWhenGetIngestionFlowFilePathThenUnsupportedOperation(){
foldersPathsConfig.setIngestionFlowFileTypePaths(new EnumMap<>(IngestionFlowFileType.class));
try {
foldersPathsConfig.getIngestionFlowFilePath(
IngestionFlowFileType.RECEIPT);
Assertions.fail("Expected UnsupportedOperationException");
}catch (UnsupportedOperationException e){
//do nothing
}
}
}
Loading

0 comments on commit d6c5f62

Please sign in to comment.