Skip to content

Commit

Permalink
[SELC-3389] feat: signing process integration (#42)
Browse files Browse the repository at this point in the history
* initializer sdk-crypto with env rather than properties

* java 17 and quarkus 3.5.1

* added signature but disabled

* crypto_source named as pagopa_signature_source

* unit test

* remove unused import
  • Loading branch information
manuraf authored Nov 17, 2023
1 parent 189e1be commit c9f663e
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 53 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Applications under apps/ depend on shared code under libs/. test-coverage/ is us
   ├── onboarding-sdk-common
   ├── onboarding-sdk-azure-storage
   ├── onboarding-sdk-product
   ├── onboarding-sdk-crypto
└── test-coverage
```

Expand All @@ -30,6 +31,12 @@ The [`.container_apps/`] sub folder contains terraform files for deploying infra

The [`.github/`] sub folder contains a self-contained ci-stack for building the monorepo with Github Actions.

## Usage

```shell script
mvn clean package install
```

## Maven basic actions for monorep

Maven is really not a monorepo-*native* build tool (e.g. lacks
Expand Down
7 changes: 6 additions & 1 deletion apps/onboarding-functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ These functions handle all asynchronous activities related to preparing and comp

It is triggered by http request at GET or POST `/api/StartOnboardingOrchestration?onboardingId={onboardingId}` where onboardingId is a reference to onboarding which you want to process.

### Contract Signature

You can enable the signature inside contracts when there are builded setting PAGOPA_SIGNATURE_SOURCE env (default value is `disabled`) as `local` if you want to use Pkcs7HashSignService or `aruba` for ArubaPkcs7HashSignService. Look at this [README](https://github.com/pagopa/selfcare-onboarding/tree/develop/libs/onboarding-sdk-crypto#readme) for more informations.


## Running locally


Expand All @@ -27,7 +32,7 @@ Before running you must set these properties as environment variables.

### Storage emulator: Azurite

Use the Azurite emulator for local Azure Storage development. Once installed, you must create `selc-d-contracts-blob` and `selc-product` container.
Use the Azurite emulator for local Azure Storage development. Once installed, you must create `selc-d-contracts-blob` and `selc-d-product` container. Inside last one you have to put products.json file.

([guide](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio))

Expand Down
9 changes: 7 additions & 2 deletions apps/onboarding-functions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

<properties>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.3.3</quarkus.platform.version>
<quarkus.platform.version>3.5.1</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.1.2</surefire-plugin.version>
<onboarding-sdk.version>0.1.0</onboarding-sdk.version>
Expand Down Expand Up @@ -80,6 +80,11 @@
<artifactId>onboarding-sdk-common</artifactId>
<version>${onboarding-sdk.version}</version>
</dependency>
<dependency>
<groupId>it.pagopa.selfcare</groupId>
<artifactId>onboarding-sdk-crypto</artifactId>
<version>${onboarding-sdk.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.quarkus.arc.DefaultBean;
import io.quarkus.arc.properties.IfBuildProperty;
import io.vertx.core.json.jackson.DatabindCodec;
import it.pagopa.selfcare.azurestorage.AzureBlobClientDefault;
import it.pagopa.selfcare.azurestorage.AzureBlobClient;
import it.pagopa.selfcare.onboarding.crypto.*;
import it.pagopa.selfcare.onboarding.exception.GenericOnboardingException;
import it.pagopa.selfcare.product.service.ProductService;
import it.pagopa.selfcare.product.service.ProductServiceDefault;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;

import java.io.IOException;
import java.io.InputStream;

@ApplicationScoped
public class OnboardingFunctionConfig {



@Produces
public ObjectMapper objectMapper(){
ObjectMapper mapper = DatabindCodec.mapper();
Expand Down Expand Up @@ -48,4 +56,31 @@ public ProductService productService(AzureStorageConfig azureStorageConfig){
}
}

@Produces
@IfBuildProperty(name = "onboarding-functions.pagopa-signature.source", stringValue = "aruba")
public Pkcs7HashSignService arubaPkcs7HashSignService(){
return new ArubaPkcs7HashSignServiceImpl(new ArubaSignServiceImpl());
}


@Produces
@IfBuildProperty(name = "onboarding-functions.pagopa-signature.source", stringValue = "disabled")
public Pkcs7HashSignService disabledPkcs7HashSignService(){
return new Pkcs7HashSignService(){
@Override
public byte[] sign(InputStream inputStream) throws IOException {
return new byte[0];
}
};
}
@Produces
@DefaultBean
public Pkcs7HashSignService pkcs7HashSignService(){
return new Pkcs7HashSignServiceImpl();
}
@Produces
public PadesSignService padesSignService(Pkcs7HashSignService pkcs7HashSignService){
return new PadesSignServiceImpl(pkcs7HashSignService);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package it.pagopa.selfcare.onboarding.config;


import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "onboarding-functions.pagopa-signature")
public interface PagoPaSignatureConfig {

String source();

String signer();

String location();

boolean applyOnboardingEnabled();

String applyOnboardingTemplateReason();

boolean verifyEnabled();

String euListOfTrustedListsURL();

String euOfficialJournalUrl();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import it.pagopa.selfcare.azurestorage.AzureBlobClient;
import it.pagopa.selfcare.onboarding.common.InstitutionType;
import it.pagopa.selfcare.onboarding.config.AzureStorageConfig;
import it.pagopa.selfcare.onboarding.config.PagoPaSignatureConfig;
import it.pagopa.selfcare.onboarding.crypto.PadesSignService;
import it.pagopa.selfcare.onboarding.crypto.entity.SignatureInformation;
import it.pagopa.selfcare.onboarding.entity.Institution;
import it.pagopa.selfcare.onboarding.entity.Onboarding;
import it.pagopa.selfcare.onboarding.exception.GenericOnboardingException;
import it.pagopa.selfcare.onboarding.utils.ClassPathStream;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.commons.text.StringSubstitutor;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
Expand Down Expand Up @@ -41,14 +43,23 @@ public class ContractServiceDefault implements ContractService {

private static final Logger log = LoggerFactory.getLogger(ContractServiceDefault.class);
public static final String PDF_FORMAT_FILENAME = "%s.pdf";
public static final String PAGOPA_SIGNATURE_DISABLED = "disabled";

private final AzureStorageConfig azureStorageConfig;

private final AzureBlobClient azureBlobClient;

public ContractServiceDefault(AzureStorageConfig azureStorageConfig, AzureBlobClient azureBlobClient) {
private final PadesSignService padesSignService;

private final PagoPaSignatureConfig pagoPaSignatureConfig;

public ContractServiceDefault(AzureStorageConfig azureStorageConfig,
AzureBlobClient azureBlobClient, PadesSignService padesSignService,
PagoPaSignatureConfig pagoPaSignatureConfig) {
this.azureStorageConfig = azureStorageConfig;
this.azureBlobClient = azureBlobClient;
this.padesSignService = padesSignService;
this.pagoPaSignatureConfig = pagoPaSignatureConfig;
}

/**
Expand Down Expand Up @@ -77,7 +88,7 @@ public File createContractPDF(String contractTemplatePath, Onboarding onboarding
// Read the content of the contract template file.
String contractTemplateText = azureBlobClient.getFileAsText(contractTemplatePath);
// Create a temporary PDF file to store the contract.
Path files = Files.createTempFile(builder, ".pdf");
Path temporaryPdfFile = Files.createTempFile(builder, ".pdf");
// Prepare common data for the contract document.
Map<String, Object> data = setUpCommonData(validManager, users, institution, onboarding.getBilling(), List.of());

Expand All @@ -95,20 +106,45 @@ public File createContractPDF(String contractTemplatePath, Onboarding onboarding
setupSAProdInteropData(data, institution);
}
log.debug("data Map for PDF: {}", data);
getPDFAsFile(files, contractTemplateText, data);
fillPDFAsFile(temporaryPdfFile, contractTemplateText, data);

// Define the filename and path for storage.
/* return signContract(institution, request, files.toFile()); */
final String filename = String.format(PDF_FORMAT_FILENAME, onboarding.getOnboardingId());
final String path = String.format("%s%s", azureStorageConfig.contractPath(), onboarding.getOnboardingId());
azureBlobClient.uploadFile(path, filename, Files.readAllBytes(files));

return files.toFile();
File signedPath = signPdf(temporaryPdfFile.toFile(), institution.getDescription(), productId);
azureBlobClient.uploadFile(path, filename, Files.readAllBytes(signedPath.toPath()));

return signedPath;
} catch (IOException e) {
throw new GenericOnboardingException(String.format("Can not create contract PDF, message: %s", e.getMessage()));
}
}

private File signPdf(File pdf, String institutionDescription, String productId) throws IOException {
if(PAGOPA_SIGNATURE_DISABLED.equals(pagoPaSignatureConfig.source())) {
log.info("Skipping PagoPA contract pdf sign due to global disabling");
return pdf;
}

String signReason = pagoPaSignatureConfig.applyOnboardingTemplateReason()
.replace("${institutionName}", institutionDescription)
.replace("${productName}", productId);

log.info("Signing input file {} using reason {}", pdf.getName(), signReason);
Path signedPdf = Files.createTempFile("signed", ".pdf");
padesSignService.padesSign(pdf, signedPdf.toFile(), buildSignatureInfo(signReason));
return signedPdf.toFile();
}

private SignatureInformation buildSignatureInfo(String signReason) {
return new SignatureInformation(
pagoPaSignatureConfig.signer(),
pagoPaSignatureConfig.location(),
signReason
);
}

@Override
public File loadContractPDF(String contractTemplatePath, String onboardingId) {
try {
Expand All @@ -124,7 +160,7 @@ public File loadContractPDF(String contractTemplatePath, String onboardingId) {
}
}

private void getPDFAsFile(Path files, String contractTemplate, Map<String, Object> data) {
private void fillPDFAsFile(Path file, String contractTemplate, Map<String, Object> data) {
log.debug("Getting PDF for HTML template...");
String html = StringSubstitutor.replace(contractTemplate, data);
PdfRendererBuilder builder = new PdfRendererBuilder();
Expand All @@ -144,7 +180,7 @@ private void getPDFAsFile(Path files, String contractTemplate, Map<String, Objec
builder.withW3cDocument(dom, null);
builder.useSVGDrawer(new BatikSVGDrawer());

try(FileOutputStream fileOutputStream = new FileOutputStream(files.toFile())) {
try(FileOutputStream fileOutputStream = new FileOutputStream(file.toFile())) {
builder.toStream(fileOutputStream);
builder.run();
} catch (IOException e){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ quarkus.mailer.start-tls=${MAIL_SERVER_SMTP_TLS_ENABLE:REQUIRED}
#aws.ses.secret-key=${AWS_SES_SECRET_ACCESS_KEY:secret-key-example}
#aws.ses.region=${AWS_SES_REGION:eu-south-1}

## SIGNATURE

onboarding-functions.pagopa-signature.verify-enabled=${SIGNATURE_VALIDATION_ENABLED:true}
onboarding-functions.pagopa-signature.source=${PAGOPA_SIGNATURE_SOURCE:disabled}
onboarding-functions.pagopa-signature.signer=${PAGOPA_SIGNATURE_SIGNER:PagoPA S.p.A.}
onboarding-functions.pagopa-signature.location=${PAGOPA_SIGNATURE_LOCATION:Roma}
onboarding-functions.pagopa-signature.apply-onboarding-enabled=${PAGOPA_SIGNATURE_ONBOARDING_ENABLED:false}
onboarding-functions.pagopa-signature.apply-onboarding-template-reason=${PAGOPA_SIGNATURE_ONBOARDING_REASON_TEMPLATE:Firma contratto adesione prodotto}
onboarding-functions.pagopa-signature.eu-list-of-trusted-lists-url=https://ec.europa.eu/tools/lotl/eu-lotl.xml
onboarding-functions.pagopa-signature.eu-official-journal-url=https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.C_.2019.276.01.0001.01.ENG

## Jacoco
quarkus.jacoco.includes=it/pagopa/selfcare/onboarding/*,it/pagopa/selfcare/onboarding/service/**,it/pagopa/selfcare/onboarding/repository/**
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import it.pagopa.selfcare.azurestorage.AzureBlobClient;
import it.pagopa.selfcare.onboarding.common.InstitutionType;
import it.pagopa.selfcare.onboarding.config.AzureStorageConfig;
import it.pagopa.selfcare.onboarding.config.PagoPaSignatureConfig;
import it.pagopa.selfcare.onboarding.crypto.PadesSignService;
import it.pagopa.selfcare.onboarding.entity.Institution;
import it.pagopa.selfcare.onboarding.entity.Onboarding;
import jakarta.inject.Inject;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
Expand All @@ -24,18 +30,31 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.*;

@QuarkusTest
class ContractServiceDefaultTest {

@Inject
AzureStorageConfig azureStorageConfig;

@InjectMock
AzureBlobClient azureBlobClient;
PadesSignService padesSignService;

@Inject
ContractService contractService;

@Inject
PagoPaSignatureConfig pagoPaSignatureConfig;


@BeforeEach
void setup(){
padesSignService = mock(PadesSignService.class);
contractService = new ContractServiceDefault(azureStorageConfig, azureBlobClient, padesSignService, pagoPaSignatureConfig);
}


private Onboarding createOnboarding() {
Onboarding onboarding = new Onboarding();
Expand Down Expand Up @@ -98,6 +117,27 @@ void createContractPDFSA() {
assertNotNull(contractService.createContractPDF(contractFilepath, onboarding, manager, List.of(), List.of()));
}

@Test
void createContractPDFAndSigned() {
final String contractFilepath = "contract";
final String contractHtml = "contract";

UserResource manager = createDummyUserResource();
Onboarding onboarding = createOnboarding();

PagoPaSignatureConfig pagoPaSignatureConfig = Mockito.spy(this.pagoPaSignatureConfig);
when(pagoPaSignatureConfig.source()).thenReturn("local");
contractService = new ContractServiceDefault(azureStorageConfig, azureBlobClient, padesSignService, pagoPaSignatureConfig);

Mockito.when(azureBlobClient.getFileAsText(contractFilepath)).thenReturn(contractHtml);

Mockito.doNothing().when(padesSignService).padesSign(any(),any(),any());

Mockito.when(azureBlobClient.uploadFile(any(),any(),any())).thenReturn(contractHtml);

assertNotNull(contractService.createContractPDF(contractFilepath, onboarding, manager, List.of(), List.of()));
}



@Test
Expand Down
Loading

0 comments on commit c9f663e

Please sign in to comment.