diff --git a/build.gradle.kts b/build.gradle.kts index 20adac91..9a957741 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,7 +53,7 @@ val commonsCompressVersion = "1.27.1" val commonsLang3Version = "3.17.0" val commonsTextVersion = "1.12.0" val jacksonModuleVersion = "2.18.1" - +val jsoupVersion = "1.18.1" dependencies { implementation("org.springframework.boot:spring-boot-starter") @@ -71,6 +71,11 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-parameter-names:$jacksonModuleVersion") + // Used for mail + implementation("org.springframework.boot:spring-boot-starter-mail") + implementation("org.springframework.retry:spring-retry") + implementation("org.jsoup:jsoup:$jsoupVersion") + // Testing testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.junit.jupiter:junit-jupiter-api") diff --git a/gradle.lockfile b/gradle.lockfile index e69d2c27..2905cf40 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -39,4 +39,10 @@ org.springframework:spring-core:6.1.14=compileClasspath org.springframework:spring-expression:6.1.14=compileClasspath org.springframework:spring-jcl:6.1.14=compileClasspath org.yaml:snakeyaml:2.0=compileClasspath +org.eclipse.angus:jakarta.mail:2.0.3=compileClasspath +org.springframework:spring-context-support:6.1.14=compileClasspath +org.jsoup:jsoup:1.18.1=compileClasspath +jakarta.activation:jakarta.activation-api:2.1.3=compileClasspath +org.springframework.boot:spring-boot-starter-mail:3.3.5=compileClasspath +org.springframework.retry:spring-retry:2.0.10=compileClasspath empty= diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/FdRIngestionActivity.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivity.java similarity index 76% rename from src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/FdRIngestionActivity.java rename to src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivity.java index da3b00f2..88e082e5 100644 --- a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/FdRIngestionActivity.java +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivity.java @@ -1,5 +1,5 @@ -package it.gov.pagopa.payhub.activities.activity.fdr; -import it.gov.pagopa.payhub.activities.dto.fdr.FdRIngestionActivityResult; +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; +import it.gov.pagopa.payhub.activities.dto.reportingflow.FdRIngestionActivityResult; /** * Interface for the FdRIngestionActivity. diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImpl.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImpl.java new file mode 100644 index 00000000..894d9f87 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImpl.java @@ -0,0 +1,46 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; + +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFileHandlerService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFileValidatorService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFlowRetrieverService; +import it.gov.pagopa.payhub.activities.dto.reportingflow.FdRIngestionActivityResult; +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Component +public class FdRIngestionActivityImpl implements FdRIngestionActivity { + private final IngestionFlowRetrieverService ingestionFlowRetrieverService; + private final IngestionFileValidatorService ingestionFileValidatorService; + private final IngestionFileHandlerService ingestionFileHandlerService; + + public FdRIngestionActivityImpl(IngestionFlowRetrieverService ingestionFlowRetrieverService, + IngestionFileValidatorService ingestionFileValidatorService, + IngestionFileHandlerService ingestionFileHandlerService) { + this.ingestionFlowRetrieverService = ingestionFlowRetrieverService; + this.ingestionFileValidatorService = ingestionFileValidatorService; + this.ingestionFileHandlerService = ingestionFileHandlerService; + } + + @Override + public FdRIngestionActivityResult processFile(String ingestionFlowId) { + List iufList = new ArrayList<>(); + boolean success = true; + + try { + IngestionFlowDTO ingestionFlowDTO = ingestionFlowRetrieverService.getIngestionFlow(Long.valueOf(ingestionFlowId)); + + ingestionFileValidatorService.validate(ingestionFlowDTO.getFilePathName(), ingestionFlowDTO.getFileName(), ingestionFlowDTO.getRequestTokenCode()); + + ingestionFileHandlerService.setUpProcess(ingestionFlowDTO.getFilePathName(), ingestionFlowDTO.getFileName()); + } catch (Exception e) { + log.error("Error during IngestionActivity flowId {} due to: {}", ingestionFlowId, e.getMessage()); + success = false; + } + return new FdRIngestionActivityResult(iufList, success); + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/SendEmailIngestionFlowActivity.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivity.java similarity index 90% rename from src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/SendEmailIngestionFlowActivity.java rename to src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivity.java index f5758fab..41520dc8 100644 --- a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/SendEmailIngestionFlowActivity.java +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivity.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.payhub.activities.activity.fdr; +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; /** * Interface for SendEmailIngestionFlowActivity. diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivityImpl.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivityImpl.java new file mode 100644 index 00000000..002652eb --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/SendEmailIngestionFlowActivityImpl.java @@ -0,0 +1,64 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; + +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.AsyncSendMailService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFlowRetrieverService; +import it.gov.pagopa.payhub.activities.dao.IngestionFlowDao; +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import it.gov.pagopa.payhub.activities.exception.SendMailException; +import it.gov.pagopa.payhub.activities.helper.MailParameterHelper; +import it.gov.pagopa.payhub.activities.model.MailParams; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class SendEmailIngestionFlowActivityImpl implements SendEmailIngestionFlowActivity { + private final IngestionFlowRetrieverService ingestionFlowRetrieverService; + private final AsyncSendMailService asyncSendMailService; + private final MailParams mailParams; + private final JavaMailSender javaMailSender; + private final IngestionFlowDao ingestionFlowDao; + + public SendEmailIngestionFlowActivityImpl(IngestionFlowRetrieverService ingestionFlowRetrieverService, AsyncSendMailService asyncSendMailService, IngestionFlowDao ingestionFlowDao, MailParams mailParams, JavaMailSender javaMailSender) { + this.ingestionFlowRetrieverService = ingestionFlowRetrieverService; + this.asyncSendMailService = asyncSendMailService; + this.ingestionFlowDao = ingestionFlowDao; + this.mailParams = mailParams; + this.javaMailSender = javaMailSender; + } + + /** + * Sends an email based on the process result of the given file ingestionFlow ID. + * + * @param ingestionFlowId the unique identifier of the IngestionFlow record related to the imported file. + * @param success true if the process succeeded, false otherwise. + * @return true if the email was sent successfully, false otherwise. + */ + @Override + public boolean sendEmail(String ingestionFlowId, boolean success) { + // verify if previous operation is success + if (success){ + try { + IngestionFlowDTO ingestionFlowDTO = ingestionFlowRetrieverService.getIngestionFlow(Long.valueOf(ingestionFlowId)); + if (ingestionFlowDTO!=null) { + mailParams.setIngestionFlowDTO(ingestionFlowDTO); + } + // get e-mail parameters + MailParams params = MailParameterHelper.getMailParams(mailParams); + + // send e-mail if there are no errors in parameters + if (params.isSuccess()){ + mailParams.setHtmlText(params.getHtmlText()); + mailParams.setMailSubject(params.getMailSubject()); + mailParams.setIngestionFlowId(ingestionFlowId); + asyncSendMailService.sendMail(javaMailSender, mailParams); + return true; + } + } catch (Exception e) { + throw new SendMailException("Error sending mail for id: "+ingestionFlowId); + } + } + return false; + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/UpdateIngestionFlowStatusActivity.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/UpdateIngestionFlowStatusActivity.java similarity index 88% rename from src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/UpdateIngestionFlowStatusActivity.java rename to src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/UpdateIngestionFlowStatusActivity.java index 01bfd004..7c7a5359 100644 --- a/src/main/java/it/gov/pagopa/payhub/activities/activity/fdr/UpdateIngestionFlowStatusActivity.java +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/UpdateIngestionFlowStatusActivity.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.payhub.activities.activity.fdr; +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; /** * Interface for the UpdateIngestionFlowStatusActivity. diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/AsyncSendMailService.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/AsyncSendMailService.java new file mode 100644 index 00000000..828147dd --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/AsyncSendMailService.java @@ -0,0 +1,76 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import it.gov.pagopa.payhub.activities.exception.SendMailException; +import it.gov.pagopa.payhub.activities.model.MailParams; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.safety.Safelist; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import java.util.concurrent.Executor; + +@Service +@Slf4j +public class AsyncSendMailService { + @Value("${async.sendMail.corePoolSize:2}") + private String corePoolSize; + @Value("${async.sendMail.maxPoolSize:10}") + private String maxPoolSize; + @Value("${async.sendMail.queueCapacity:500}") + private String queueCapacity; + + + @Async("sendMailTaskExecutor") + @Retryable(value = MailException.class, maxAttemptsExpression = "${async.sendMail.retry.maxAttempts}", + backoff = @Backoff(random = true, delayExpression = "${async.sendMail.retry.delay}", + maxDelayExpression = "${async.sendMail.retry.maxDelay}", multiplierExpression = "${async.sendMail.retry.multiplier}")) + public void sendMail(JavaMailSender javaMailSender, MailParams mailParams) { + try { + javaMailSender.send( mimeMessage -> { + MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + message.setFrom(mailParams.getEmailFromAddress(), mailParams.getEmailFromName()); + message.setTo(mailParams.getTo()); + if(ArrayUtils.isNotEmpty(mailParams.getCc())) + message.setCc(mailParams.getCc()); + message.setSubject(mailParams.getMailSubject()); + String plainText = Jsoup.clean(mailParams.getHtmlText(), "", Safelist.none(), new Document.OutputSettings().prettyPrint(false)); + message.setText(plainText, mailParams.getHtmlText()); + log.info("sending mail message"); + } ); + log.info("MAIL has been send"); + } + catch (Exception e) { + log.info("MAIL error"); + throw new SendMailException("Error in mail sending"); + } + } + + @Recover + private void recover(MailException e, String[] to, String[] cc, String subject, String htmlText){ + //TODO write fail to db or queue for retry, in case + } + + @Bean("sendMailTaskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(Integer.parseInt(corePoolSize)); + executor.setMaxPoolSize(Integer.parseInt(maxPoolSize)); + executor.setQueueCapacity(Integer.parseInt(queueCapacity)); + executor.setThreadNamePrefix("BatchSendMail-"); + executor.initialize(); + return executor; + } + +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerService.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerService.java new file mode 100644 index 00000000..0c73d087 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerService.java @@ -0,0 +1,55 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import it.gov.pagopa.payhub.activities.util.AESUtils; +import it.gov.pagopa.payhub.activities.util.FileUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +@Service +public class IngestionFileHandlerService { + private static final String TEMPORARY_PATH = "TEMP"; + + private final String dataCipherPsw; + + public IngestionFileHandlerService(@Value("${data-cipher.encrypt-psw}") String dataCipherPsw) { + this.dataCipherPsw = dataCipherPsw; + } + + public Path setUpProcess(String relativePath, String filename) throws IOException { + Path relativePathDir = Paths.get(relativePath); + Path encryptedFilePath = relativePathDir.resolve(filename); + + // Prepare temporary path + Path temporaryPath = relativePathDir.resolve(TEMPORARY_PATH); + Files.createDirectories(temporaryPath); + + // Derive the decrypted file name and path + String filenameNoCipher = filename.replace(AESUtils.CIPHER_EXTENSION, ""); + Path temporaryZipFilePath = temporaryPath.resolve(filenameNoCipher); + + // Decrypt the file + log.debug("Decrypting file: {}", encryptedFilePath); + AESUtils.decrypt(dataCipherPsw, encryptedFilePath.toFile(), temporaryZipFilePath.toFile()); + + // Validate ZIP file + log.debug("Validating ZIP file: {}", temporaryZipFilePath); + FileUtils.validateZip(temporaryZipFilePath); + + // Unzip the file + String unzippedFilename = filenameNoCipher.replace(".zip", ".xml"); + Path outputUnzippedPath = temporaryPath.resolve(unzippedFilename); + + log.debug("Unzipping file: {} to {}", temporaryZipFilePath, outputUnzippedPath); + FileUtils.unzipFile(temporaryZipFilePath, outputUnzippedPath); + + log.debug("File setup process completed successfully for: {}", filename); + return outputUnzippedPath; + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorService.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorService.java new file mode 100644 index 00000000..4753cd8f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorService.java @@ -0,0 +1,71 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import it.gov.pagopa.payhub.activities.exception.InvalidIngestionFileException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; +import org.springframework.stereotype.Service; + +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.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +@Slf4j +@Service +public class IngestionFileValidatorService { + + public void validate(String relativePath, String filename, String requestTokenCode) { + Path fileLocation = Paths.get(relativePath, filename); + + validateFile(fileLocation); + validateAUTH(fileLocation.toString(), requestTokenCode); + validateMD5(fileLocation.toString()); + } + + private void validateFile(Path filePath) { + if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { + throw new InvalidIngestionFileException("File non trovato: " + filePath); + } + } + + private void validateMD5(String filename) { + String md5 = filename.replace(".zip", ".md5"); + String valueMD5 = readContentFile(md5); + String calculatedMD5 = calculateMd5(filename); + if(!valueMD5.equalsIgnoreCase(calculatedMD5)) { + throw new InvalidIngestionFileException("Error while calculating MD5 file value"); + } + } + + private void validateAUTH(String filename, String requestToken) { + String auth = filename.replace(".zip", ".auth"); + String valueAUTH = readContentFile(auth); + if(!valueAUTH.equalsIgnoreCase(requestToken)) { + throw new InvalidIngestionFileException("Error while calculating MD5 file value"); + } + } + + private String readContentFile(String filename) { + try { + return Files.readString(Paths.get(filename)); + } catch (IOException e) { + throw new InvalidIngestionFileException("Error while reading file: " + filename); + } + } + + private String calculateMd5(String filename) { + try (InputStream is = Files.newInputStream(Paths.get(filename))) { + MessageDigest md = MessageDigest.getInstance("MD5"); + DigestInputStream dis = new DigestInputStream(is, md); + IOUtils.toByteArray(dis); + return Hex.encodeHexString(md.digest()); + } catch (NoSuchAlgorithmException | IOException e) { + throw new InvalidIngestionFileException("Error while calculating MD5"); + } + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverService.java b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverService.java new file mode 100644 index 00000000..6cca2811 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverService.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import it.gov.pagopa.payhub.activities.dao.IngestionFlowDao; +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import it.gov.pagopa.payhub.activities.exception.IngestionFlowNotFoundException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@Slf4j +public class IngestionFlowRetrieverService { + + private final IngestionFlowDao ingestionFlowDao; + + public IngestionFlowRetrieverService(IngestionFlowDao ingestionFlowDao) { + this.ingestionFlowDao = ingestionFlowDao; + } + + public IngestionFlowDTO getIngestionFlow(Long ingestionFlowId) { + Optional ingestionFlow = ingestionFlowDao.getIngestionFlow(ingestionFlowId); + + return ingestionFlow + .orElseThrow(() -> new IngestionFlowNotFoundException("Cannot found ingestionFlow having id: "+ ingestionFlowId)); + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/dao/IngestionFlowDao.java b/src/main/java/it/gov/pagopa/payhub/activities/dao/IngestionFlowDao.java new file mode 100644 index 00000000..c663b6d7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/dao/IngestionFlowDao.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.payhub.activities.dao; + +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; + +import java.util.Optional; + +public interface IngestionFlowDao { + + /** + * * It will return the requested IngestionFlowDTO entity from its id + * */ + Optional getIngestionFlow(Long ingestionFlowId); +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/dto/fdr/FdRIngestionActivityResult.java b/src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/FdRIngestionActivityResult.java similarity index 87% rename from src/main/java/it/gov/pagopa/payhub/activities/dto/fdr/FdRIngestionActivityResult.java rename to src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/FdRIngestionActivityResult.java index 304a55dd..5c2c51f2 100644 --- a/src/main/java/it/gov/pagopa/payhub/activities/dto/fdr/FdRIngestionActivityResult.java +++ b/src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/FdRIngestionActivityResult.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.payhub.activities.dto.fdr; +package it.gov.pagopa.payhub.activities.dto.reportingflow; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/IngestionFlowDTO.java b/src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/IngestionFlowDTO.java new file mode 100644 index 00000000..e79a85ac --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/dto/reportingflow/IngestionFlowDTO.java @@ -0,0 +1,37 @@ +package it.gov.pagopa.payhub.activities.dto.reportingflow; + +import it.gov.pagopa.payhub.activities.dto.OrganizationDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.sql.Timestamp; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IngestionFlowDTO { + private Long ingestionFlowId; + private int version; + private OrganizationDTO orgId; + private String PspIdentifier; + private String flowIdentifierCode; + private Timestamp flowDateTime; + private String flowType; + private String flowTypeCode; + private String operatorName; + private String status; + private String filePathName; + private String fileName; + private Long downloadedFileSize; + private String requestTokenCode; + private Timestamp creationDate; + private Timestamp lastChangeDate; + private String fileSourceCode; + private String descriptionFileNameScraps; + private String errorCode; + private Long TotalRowsNumber; + private Long numberLinesImportedCorrectly; +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/exception/IngestionFlowNotFoundException.java b/src/main/java/it/gov/pagopa/payhub/activities/exception/IngestionFlowNotFoundException.java new file mode 100644 index 00000000..a155398b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/exception/IngestionFlowNotFoundException.java @@ -0,0 +1,6 @@ +package it.gov.pagopa.payhub.activities.exception; + +public class IngestionFlowNotFoundException extends ActivitiesException { + + public IngestionFlowNotFoundException(String message) { super(message); } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/exception/InvalidIngestionFileException.java b/src/main/java/it/gov/pagopa/payhub/activities/exception/InvalidIngestionFileException.java new file mode 100644 index 00000000..f8bf1a9b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/exception/InvalidIngestionFileException.java @@ -0,0 +1,7 @@ +package it.gov.pagopa.payhub.activities.exception; + +public class InvalidIngestionFileException extends ActivitiesException { + public InvalidIngestionFileException(String message) { + super(message); + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/exception/SendMailException.java b/src/main/java/it/gov/pagopa/payhub/activities/exception/SendMailException.java new file mode 100644 index 00000000..3f7d5da7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/exception/SendMailException.java @@ -0,0 +1,7 @@ +package it.gov.pagopa.payhub.activities.exception; + + +public class SendMailException extends ActivitiesException { + public SendMailException(String message) { super(message); } +} + diff --git a/src/main/java/it/gov/pagopa/payhub/activities/helper/EmailHelper.java b/src/main/java/it/gov/pagopa/payhub/activities/helper/EmailHelper.java new file mode 100644 index 00000000..a5d1523f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/helper/EmailHelper.java @@ -0,0 +1,23 @@ +package it.gov.pagopa.payhub.activities.helper; + +import it.gov.pagopa.payhub.activities.exception.SendMailException; + +import java.io.InputStream; +import java.util.Properties; + +public class EmailHelper { + /** + * + * @return mail template Properties from properties + */ + public Properties getProperties() { + Properties properties = new Properties(); + try { + InputStream inputStream = EmailHelper.class.getClassLoader().getResourceAsStream("mail-templates.properties"); + properties.load(inputStream); + } catch (Exception e) { + throw new SendMailException("Error in mail template configuration"); + } + return properties; + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/helper/MailParameterHelper.java b/src/main/java/it/gov/pagopa/payhub/activities/helper/MailParameterHelper.java new file mode 100644 index 00000000..2393fd4e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/helper/MailParameterHelper.java @@ -0,0 +1,72 @@ +package it.gov.pagopa.payhub.activities.helper; + +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import it.gov.pagopa.payhub.activities.exception.SendMailException; +import it.gov.pagopa.payhub.activities.model.MailParams; +import it.gov.pagopa.payhub.activities.utils.Constants; +import org.apache.commons.text.StringSubstitutor; +import org.springframework.util.Assert; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class MailParameterHelper { + /** + * helper for e-mail + * @param mailParams parameters not updated + * @return MailParams parameters updated + */ + public static MailParams getMailParams(MailParams mailParams) { + + IngestionFlowDTO ingestionFlowDTO = mailParams.getIngestionFlowDTO(); + + + String fileName = ingestionFlowDTO.getFileName(); + Long fileSize = ingestionFlowDTO.getDownloadedFileSize(); + Long totalRowsNumber = ingestionFlowDTO.getTotalRowsNumber(); + DateFormat parser = new SimpleDateFormat("EEE, MMM dd yyyy, hh:mm:ss"); + String actualDate = parser.format(new Date()); + String mailText = "Il caricamento del file " + fileName; + if (fileSize>0 && totalRowsNumber>0) { + mailText += " è andato a buon fine, tutti i " + totalRowsNumber + " dati presenti sono stati caricati correttamente."; + } + else { + mailText += " NON è andato a buon fine"; + } + + try { + EmailHelper emailHelper = new EmailHelper(); + Properties mailProperties = emailHelper.getProperties(); + Assert.notEmpty(mailProperties.values(), "Wrong mail configuration"); + String templateName = mailParams.getTemplateName(); + String subject = mailProperties.getProperty("template."+templateName+".subject"); + String body = mailProperties.getProperty("template."+templateName+".body"); + Assert.notNull(subject, "Invalid email template (missing subject) "+templateName); + Assert.notNull(body, "Invalid email template (missing body) "+templateName); + + String mailSubject = StringSubstitutor.replace(subject, mailParams.getParams(), "{", "}"); + String htmlText = StringSubstitutor.replace(body, mailParams.getParams(), "{", "}"); + + MailParams params = new MailParams(); + Map map = new HashMap<>(); + map.put(Constants.MAIL_TEXT, mailText); + map.put(Constants.ACTUAL_DATE,actualDate); + map.put(Constants.FILE_NAME, fileName); + params.setMailSubject(mailSubject); + params.setHtmlText(htmlText); + params.setParams(map); + params.setSuccess(true); + return params; + } + catch (SendMailException sendMailException) { + throw sendMailException; + } + catch (Exception e) { + throw new SendMailException("Error in mail parameters"); + } + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/model/MailParams.java b/src/main/java/it/gov/pagopa/payhub/activities/model/MailParams.java new file mode 100644 index 00000000..3b684185 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/model/MailParams.java @@ -0,0 +1,28 @@ +package it.gov.pagopa.payhub.activities.model; + +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Map; + +@Data +@Builder(toBuilder=true) +@NoArgsConstructor +@AllArgsConstructor +public class MailParams implements Serializable { + IngestionFlowDTO ingestionFlowDTO; + Map params; + String ingestionFlowId; + String emailFromAddress; + String emailFromName; + String templateName; + String mailSubject; + String htmlText; + boolean success; + String[] to; + String[] cc; +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/util/AESUtils.java b/src/main/java/it/gov/pagopa/payhub/activities/util/AESUtils.java new file mode 100644 index 00000000..94cd1262 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/util/AESUtils.java @@ -0,0 +1,114 @@ +package it.gov.pagopa.payhub.activities.util; + +import javax.crypto.*; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +public class AESUtils { + private AESUtils() { + } + + private static final String ALGORITHM = "AES/GCM/NoPadding"; + private static final String FACTORY_INSTANCE = "PBKDF2WithHmacSHA256"; + private static final int TAG_LENGTH_BIT = 128; + private static final int IV_LENGTH_BYTE = 12; + private static final int SALT_LENGTH_BYTE = 16; + private static final String ALGORITHM_TYPE = "AES"; + private static final int KEY_LENGTH = 256; + private static final int ITERATION_COUNT = 65536; + + public static final String CIPHER_EXTENSION = ".cipher"; + + public static byte[] getRandomNonce(int length) { + byte[] nonce = new byte[length]; + new SecureRandom().nextBytes(nonce); + return nonce; + } + + public static SecretKey getSecretKey(String password, byte[] salt) { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); + + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(FACTORY_INSTANCE); + return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM_TYPE); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException("Cannot initialize cryptographic data", e); + } + } + + public static InputStream encrypt(String password, InputStream plainStream) { + byte[] salt = getRandomNonce(SALT_LENGTH_BYTE); + SecretKey secretKey = getSecretKey(password, salt); + + // GCM recommends 12 bytes iv + byte[] iv = getRandomNonce(IV_LENGTH_BYTE); + Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, secretKey, iv); + + // prefix IV and Salt to cipher text + byte[] prefix = ByteBuffer.allocate(iv.length + salt.length) + .put(iv) + .put(salt) + .array(); + + return new SequenceInputStream( + new ByteArrayInputStream(prefix), + new CipherInputStream(new BufferedInputStream(plainStream), cipher)); + } + + public static File encrypt(String password, File plainFile) { + File cipherFile = new File(plainFile.getAbsolutePath() + CIPHER_EXTENSION); + try(FileInputStream fis = new FileInputStream(plainFile); + InputStream cipherStream = encrypt(password, fis)){ + Files.copy(cipherStream, cipherFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new IllegalStateException("Something went wrong when ciphering input file " + plainFile.getAbsolutePath(), e); + } + return cipherFile; + } + + public static InputStream decrypt(String password, InputStream cipherStream) { + try { + byte[] iv = cipherStream.readNBytes(IV_LENGTH_BYTE); + byte[] salt = cipherStream.readNBytes(SALT_LENGTH_BYTE); + + SecretKey secretKey = getSecretKey(password, salt); + Cipher cipher = initCipher(Cipher.DECRYPT_MODE, secretKey, iv); + + return new CipherInputStream(new BufferedInputStream(cipherStream), cipher); + } catch (IOException e) { + throw new IllegalStateException("Cannot read AES prefix data", e); + } + } + + public static void decrypt(String password, File cipherFile, File outputPlainFile) { + try(FileInputStream fis = new FileInputStream(cipherFile); + InputStream plainStream = decrypt(password, fis)){ + Files.copy(plainStream, outputPlainFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new IllegalStateException("Something went wrong when deciphering input file " + cipherFile.getAbsolutePath(), e); + } + } + + private static Cipher initCipher(int mode, SecretKey secretKey, byte[] iv) { + try { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(mode, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + return cipher; + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException e) { + throw new IllegalStateException("Cannot initialize cipher data", e); + } + } + +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/util/FileUtils.java b/src/main/java/it/gov/pagopa/payhub/activities/util/FileUtils.java new file mode 100644 index 00000000..7c01b9d0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/util/FileUtils.java @@ -0,0 +1,52 @@ +package it.gov.pagopa.payhub.activities.util; + +import it.gov.pagopa.payhub.activities.exception.InvalidIngestionFileException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class FileUtils { + + private FileUtils() { + } + + public static void validateZip(Path zipFilePath) { + try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { + zipFile.entries(); + } catch (Exception e) { + throw new InvalidIngestionFileException("Invalid zip file"); + } + } + + public static Path unzipFile(Path zipFilePath, Path outputDir) { + Path extractedFilePath; + try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { + Enumeration entries = zipFile.entries(); + if (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + throw new InvalidIngestionFileException("ZIP file contains directories, but only files are expected"); + } + String checkedFilename = SecureFileUtils.checkFileName(entry.getName()); + Path entryPath = outputDir.resolve(checkedFilename); + Files.createDirectories(entryPath.getParent()); + try { + Files.copy(zipFile.getInputStream(entry), entryPath, StandardCopyOption.REPLACE_EXISTING); + extractedFilePath = entryPath; // Restituisci il path completo del file estratto + } catch (IOException e) { + throw new InvalidIngestionFileException("Failed to extract file: " + entry.getName()); + } + } else { + throw new InvalidIngestionFileException("ZIP file is empty or contains no files"); + } + } catch (IOException e) { + throw new InvalidIngestionFileException("Error while unzipping file: " + zipFilePath); + } + return extractedFilePath; + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/util/SecureFileUtils.java b/src/main/java/it/gov/pagopa/payhub/activities/util/SecureFileUtils.java new file mode 100644 index 00000000..9ebea02f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/util/SecureFileUtils.java @@ -0,0 +1,29 @@ +package it.gov.pagopa.payhub.activities.util; + +import java.util.zip.ZipEntry; + +public class SecureFileUtils { + private SecureFileUtils() { + } + + /** + * Checks if the name of the zip entry is safe. + * The name must: + * - Start with an alphanumeric character. + * - Not contain the string "..". + * @param fileName The name of the zip entry. + * @return fileName if the name is safe. + */ + public static String checkFileName(String fileName) throws IllegalArgumentException { + if (!Character.isLetterOrDigit(fileName.charAt(0)) + || fileName.contains("..")) { + throw new IllegalArgumentException("Potential Zip Slip exploit detected: " + fileName); + } + return fileName; + } + + public static ZipEntry checkFileName(ZipEntry entry) throws IllegalArgumentException { + checkFileName(entry.getName()); + return entry; + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/activities/utils/Constants.java b/src/main/java/it/gov/pagopa/payhub/activities/utils/Constants.java new file mode 100644 index 00000000..d9fd19c1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/activities/utils/Constants.java @@ -0,0 +1,7 @@ +package it.gov.pagopa.payhub.activities.utils; + +public class Constants { + public static final String MAIL_TEXT = "mailText"; + public static final String ACTUAL_DATE = "actualDate"; + public static final String FILE_NAME = "fileName"; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f98de691..8c0ec9f1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,11 @@ spring: application: name: ${artifactId} - version: ${version} \ No newline at end of file + version: ${version} + +data-chiper: + encrypt-psw: "\${DATA_CIPHER_ENCRYPT_PASSWORD:PWD}" + + +ingestion-flow: + destination-base-path: "" \ No newline at end of file diff --git a/src/main/resources/mail-templates.properties b/src/main/resources/mail-templates.properties new file mode 100644 index 00000000..8cf25b58 --- /dev/null +++ b/src/main/resources/mail-templates.properties @@ -0,0 +1,23 @@ +template.mail-importFlussoRendicontazione-ok.subject=Rendicontazione: Resoconto Caricamento file: {fileName} +template.mail-importFlussoRendicontazione-ok.body= \ +Gentile Utente,
\n \ +questa e' una mail generata in automatico dal sistema MyPay, si raccomanda di non rispondere a questo messaggio.
\n\n \ +Contattare l'assistenza per richiedere, se necessario, ulteriori chiarimenti.
\n\n \ +

Di seguito il testo del messaggio: {mailText}

\n\n \ +{actualDate} + +template.mail-importFlussoRendicontazione-scarti.subject=Rendicontazione: Resoconto Caricamento file: {fileName} +template.mail-importFlussoRendicontazione-scarti.body= \ +Gentile Utente,
\n \ +questa e' una mail generata in automatico dal sistema MyPay, si raccomanda di non rispondere a questo messaggio.
\n\n \ +Contattare l'assistenza per richiedere, se necessario, ulteriori chiarimenti.
\n\n \ +

Di seguito il testo del messaggio: {mailText}

\n\n \ +{actualDate} + +template.mail-importFlussoRendicontazione-error.subject=Rendicontazione: Resoconto Caricamento file: {fileName} +template.mail-importFlussoRendicontazione-error.body= \ +Gentile Utente,
\n \ +questa e' una mail generata in automatico dal sistema MyPay, si raccomanda di non rispondere a questo messaggio.
\n\n \ +Contattare l'assistenza per richiedere, se necessario, ulteriori chiarimenti.
\n\n \ +

Di seguito il testo del messaggio: {mailText}

\n\n \ +{actualDate} diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/SendEmailIngestionFlowActivityTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/SendEmailIngestionFlowActivityTest.java new file mode 100644 index 00000000..1116871e --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/SendEmailIngestionFlowActivityTest.java @@ -0,0 +1,101 @@ +package it.gov.pagopa.payhub.activities.activity; + +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.SendEmailIngestionFlowActivityImpl; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.AsyncSendMailService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFlowRetrieverService; +import it.gov.pagopa.payhub.activities.dao.IngestionFlowDao; +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; + +import it.gov.pagopa.payhub.activities.model.MailParams; +import it.gov.pagopa.payhub.activities.utils.Constants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class SendEmailIngestionFlowActivityTest { + + @Mock + IngestionFlowRetrieverService ingestionFlowRetrieverService; + + @Mock + private IngestionFlowDao ingestionFlowDao; + + @Mock + AsyncSendMailService asyncSendMailService; + + private SendEmailIngestionFlowActivityImpl sendEmailIngestionFlowActivity; + + private MailParams mailParams; + + @BeforeEach + void init() { + JavaMailSender javaMailSender = new JavaMailSenderImpl(); + mailParams = new MailParams(); + sendEmailIngestionFlowActivity = new SendEmailIngestionFlowActivityImpl(ingestionFlowRetrieverService, asyncSendMailService, ingestionFlowDao, mailParams, javaMailSender); + } + + @Test + void sendEmailIngestionSuccess() { + boolean success = true; + String manageFlussoId = "100"; + setMailParams(manageFlussoId, "mail-importFlussoRendicontazione-ok", 1000L); + boolean result = sendEmailIngestionFlowActivity.sendEmail(manageFlussoId, success); + assertTrue(result); + } + + @Test + void sendEmailIngestionError() { + boolean success = true; + String manageFlussoId = "100"; + setMailParams(manageFlussoId, "mail-importFlussoRendicontazione-error", 0L); + boolean result = sendEmailIngestionFlowActivity.sendEmail(manageFlussoId, success); + assertTrue(result); + } + + void setMailParams(String manageFlussoId, String templatename, Long fileSize){ + Long ingestionFlowId = Long.valueOf(manageFlussoId); + IngestionFlowDTO ingestionFlowDTO = new IngestionFlowDTO(); + ingestionFlowDTO.setFileName("test.zip"); + ingestionFlowDTO.setIngestionFlowId(ingestionFlowId); + ingestionFlowDTO.setDownloadedFileSize(fileSize); + + DateFormat parser = new SimpleDateFormat("EEE, MMM dd yyyy, hh:mm:ss"); + String actualDate = parser.format(new Date()); + String mailText = "Il caricamento del file " + ingestionFlowDTO.getFileName(); + if (fileSize>0) { + ingestionFlowDTO.setTotalRowsNumber(100L); + mailText += " è andato a buon fine, tutti i " + fileSize + " dati presenti sono stati caricati correttamente."; + } + else { + ingestionFlowDTO.setTotalRowsNumber(0L); + mailText += " NON è andato a buon fine"; + } + + Map map = new HashMap<>(); + map.put(Constants.MAIL_TEXT, mailText); + map.put(Constants.ACTUAL_DATE,actualDate); + map.put(Constants.FILE_NAME, ingestionFlowDTO.getFileName()); + + mailParams.setEmailFromAddress("test@test.com"); + mailParams.setEmailFromName("test"); + mailParams.setTemplateName(templatename); + mailParams.setParams(map); + mailParams.setIngestionFlowDTO(ingestionFlowDTO); + } + + +} + diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/AuthorizeOperatorOnDebtPositionTypeActivityTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/debtposition/AuthorizeOperatorOnDebtPositionTypeActivityTest.java similarity index 94% rename from src/test/java/it/gov/pagopa/payhub/activities/activity/AuthorizeOperatorOnDebtPositionTypeActivityTest.java rename to src/test/java/it/gov/pagopa/payhub/activities/activity/debtposition/AuthorizeOperatorOnDebtPositionTypeActivityTest.java index 260addbc..a25591d7 100644 --- a/src/test/java/it/gov/pagopa/payhub/activities/activity/AuthorizeOperatorOnDebtPositionTypeActivityTest.java +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/debtposition/AuthorizeOperatorOnDebtPositionTypeActivityTest.java @@ -1,6 +1,5 @@ -package it.gov.pagopa.payhub.activities.activity; +package it.gov.pagopa.payhub.activities.activity.debtposition; -import it.gov.pagopa.payhub.activities.activity.debtposition.AuthorizeOperatorOnDebtPositionTypeActivity; import it.gov.pagopa.payhub.activities.dao.DebtPositionTypeOrgDao; import it.gov.pagopa.payhub.activities.dto.OrganizationDTO; import it.gov.pagopa.payhub.activities.dto.debtposition.DebtPositionTypeOrgDTO; diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImplTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImplTest.java new file mode 100644 index 00000000..5758443b --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/FdRIngestionActivityImplTest.java @@ -0,0 +1,29 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting; + +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFileHandlerService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFileValidatorService; +import it.gov.pagopa.payhub.activities.activity.paymentsreporting.service.IngestionFlowRetrieverService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FdRIngestionActivityImplTest { + + @Mock + private IngestionFlowRetrieverService ingestionFlowRetrieverServiceMock; + @Mock + private IngestionFileValidatorService ingestionFileValidatorServiceMock; + @Mock + private IngestionFileHandlerService ingestionFileHandlerServiceMock; + + private FdRIngestionActivityImpl activity; + + @BeforeEach + void init() { + activity = new FdRIngestionActivityImpl(ingestionFlowRetrieverServiceMock, ingestionFileValidatorServiceMock, ingestionFileHandlerServiceMock); + } + + +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerServiceTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerServiceTest.java new file mode 100644 index 00000000..0acfae37 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileHandlerServiceTest.java @@ -0,0 +1,44 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +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; + +import java.io.IOException; + +@ExtendWith(MockitoExtension.class) +class IngestionFileHandlerServiceTest { + + private static final String TEST_PATH = "some/path"; + private static final String TEST_FILENAME = "testFile.zip"; + private static final String TEST_CIPHER_PSW = "testPassword"; + + private IngestionFileHandlerService ingestionFileHandlerService; + + + @BeforeEach + void setUp() { + ingestionFileHandlerService = new IngestionFileHandlerService(TEST_CIPHER_PSW); + } + + @Test + void setUpProcessSuccess() throws IOException { + + } + + @Test + void decryptFailed() throws IOException { + + } + + @Test + void validateZipFailed() throws IOException { + + } + + @Test + void unzipFileFailed() throws IOException { + + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorServiceTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorServiceTest.java new file mode 100644 index 00000000..b4149a15 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFileValidatorServiceTest.java @@ -0,0 +1,28 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import org.junit.jupiter.api.BeforeEach; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.nio.file.Path; + +class IngestionFileValidatorServiceTest { + + @Mock + private Path filePath; + @Mock + private Path md5FilePath; + @Mock + private Path authFilePath; + + @InjectMocks + private IngestionFileValidatorService ingestionFileValidatorService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + +} diff --git a/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverServiceTest.java b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverServiceTest.java new file mode 100644 index 00000000..b60a2fd1 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/activity/paymentsreporting/service/IngestionFlowRetrieverServiceTest.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.payhub.activities.activity.paymentsreporting.service; + +import it.gov.pagopa.payhub.activities.dao.IngestionFlowDao; +import it.gov.pagopa.payhub.activities.dto.reportingflow.IngestionFlowDTO; +import it.gov.pagopa.payhub.activities.exception.IngestionFlowNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class IngestionFlowRetrieverServiceTest { + + @Mock + private IngestionFlowDao ingestionFlowDaoMock; + + private IngestionFlowRetrieverService service; + + @BeforeEach + void init() { + service = new IngestionFlowRetrieverService(ingestionFlowDaoMock); + } + + @Test + void givenIngestionFlowIdThenSuccess() { + Long ingestionFlowId = 1L; + IngestionFlowDTO expected = IngestionFlowDTO.builder() + .ingestionFlowId(ingestionFlowId) + .build(); + + when(ingestionFlowDaoMock.getIngestionFlow(ingestionFlowId)).thenReturn(Optional.of(expected)); + + IngestionFlowDTO actual = service.getIngestionFlow(ingestionFlowId); + + assertEquals(expected, actual); + } + + @Test + void givenIngestionFlowIdThenThrowIngestionFlowNotFoundException() { + Long ingestionFlowId = 1L; + when(ingestionFlowDaoMock.getIngestionFlow(ingestionFlowId)).thenReturn(Optional.empty()); + assertThrows(IngestionFlowNotFoundException.class, () -> service.getIngestionFlow(ingestionFlowId)); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/payhub/activities/util/AESUtilsTest.java b/src/test/java/it/gov/pagopa/payhub/activities/util/AESUtilsTest.java new file mode 100644 index 00000000..ad2ee196 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/util/AESUtilsTest.java @@ -0,0 +1,47 @@ +package it.gov.pagopa.payhub.activities.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +class AESUtilsTest { + + @Test + void testStream() throws IOException { + // Given + String plain = "PLAINTEXT"; + String psw = "PSW"; + + // When + InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8))); + InputStream resultStream = AESUtils.decrypt(psw, cipherStream); + + // Then + Assertions.assertEquals(new String(resultStream.readAllBytes(), StandardCharsets.UTF_8), plain); + } + + @Test + void testFile() throws IOException { + // Given + String plain = "PLAINTEXT"; + Path plainFile = Path.of("build", "tmp", "plainFile.txt"); + Files.writeString(plainFile, plain); + String psw = "PSW"; + Path decryptedFile = plainFile.getParent().resolve("decryptedFile.txt"); + + // When + File cipherFile = AESUtils.encrypt(psw, plainFile.toFile()); + AESUtils.decrypt(psw, cipherFile, decryptedFile.toFile()); + + // Then + Assertions.assertEquals(Files.readAllLines(decryptedFile), List.of(plain)); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/activities/util/FileUtilsTest.java b/src/test/java/it/gov/pagopa/payhub/activities/util/FileUtilsTest.java new file mode 100644 index 00000000..fec48dfa --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/util/FileUtilsTest.java @@ -0,0 +1,65 @@ +package it.gov.pagopa.payhub.activities.util; + +import it.gov.pagopa.payhub.activities.exception.InvalidIngestionFileException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +public class FileUtilsTest { + + private Path zipPath; + private Path outputDir; + + @BeforeEach + public void setUp() throws Exception { + zipPath = Files.createTempFile("testZip", ".zip"); + outputDir = Files.createTempDirectory("output"); + + // Crea un file ZIP di test + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) { + ZipEntry entry = new ZipEntry("testFile.xml"); + zos.putNextEntry(entry); + zos.write("This is a test file".getBytes()); + zos.closeEntry(); + } + } + + @AfterEach + public void tearDown() throws Exception { + // Pulisce i file temporanei + Files.deleteIfExists(zipPath); + Files.walk(outputDir) + .map(Path::toFile) + .forEach(file -> file.delete()); + } + + @Test + public void testValidateZip_doesNotThrowExceptionWhenValid(){ + assertDoesNotThrow(() -> FileUtils.validateZip(zipPath)); + } + + @Test + public void testValidateZip_throwsExceptionWhenInvalid() throws Exception { + + Path invalidZipPath = Files.createTempFile("invalidZip", ".zip"); + + assertThrows(InvalidIngestionFileException.class, () -> FileUtils.validateZip(invalidZipPath)); + + Files.deleteIfExists(invalidZipPath); + } + + @Test + public void testUnzipFile_extractsFileSuccessfully() { + Path result = FileUtils.unzipFile(zipPath, outputDir); + + Path expectedFilePath = outputDir.resolve("testFile.xml"); + assertEquals(expectedFilePath, result, "Returned path should match the extracted file path"); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/activities/util/SecureFileUtilsTest.java b/src/test/java/it/gov/pagopa/payhub/activities/util/SecureFileUtilsTest.java new file mode 100644 index 00000000..594c7abc --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/activities/util/SecureFileUtilsTest.java @@ -0,0 +1,34 @@ +package it.gov.pagopa.payhub.activities.util; + +import org.junit.jupiter.api.Test; + +import java.util.zip.ZipEntry; + +import static org.junit.jupiter.api.Assertions.*; + +class SecureFileUtilsTest { + + @Test + void givenValidFileNameThenOk() throws IllegalArgumentException { + String validFileName = "safeFile.txt"; + assertDoesNotThrow(() -> SecureFileUtils.checkFileName(validFileName)); + } + + @Test + void givenInvalidFileNameStartingWithNonAlphanumericThenException() { + String invalidFileName = "/unsafeFile.txt"; + assertThrows(IllegalArgumentException.class, () -> SecureFileUtils.checkFileName(invalidFileName)); + } + + @Test + void givenInvalidFileNameContainingDotDotThenException() { + String invalidFileName = "safe/../../unsafeFile.txt"; + assertThrows(IllegalArgumentException.class, () -> SecureFileUtils.checkFileName(invalidFileName)); + } + + @Test + void givenValidZipEntryThenReturnZipEntry() throws IllegalArgumentException { + ZipEntry validEntry = new ZipEntry("safeFile.txt"); + assertEquals(validEntry, SecureFileUtils.checkFileName(validEntry)); + } +} \ No newline at end of file