Skip to content

Commit

Permalink
[SELC-5466] feat: added filename handling to persist signed contract …
Browse files Browse the repository at this point in the history
…with correct extension (#486)
  • Loading branch information
empassaro authored Sep 19, 2024
1 parent 9546e53 commit e927e71
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum CustomError {

DENIED_NO_BILLING("0040","Recipient code linked to an institution with invoicing service not active"),
DENIED_NO_ASSOCIATION("0041","Recipient code not linked to any institution"),
TOO_MANY_CONTRACTS("0043","Too many contracts provided"),
;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.*;
import lombok.AllArgsConstructor;
import org.apache.http.HttpStatus;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;

import java.io.File;
import java.util.List;
import java.util.Objects;

import static it.pagopa.selfcare.onboarding.util.Utils.retrieveContractFromFormData;

@Authenticated
@Path("/v1/onboarding")
@Tag(name = "Onboarding Controller")
Expand Down Expand Up @@ -164,7 +164,6 @@ public Uni<OnboardingResponse> onboardingPgCompletion(@Valid OnboardingPgRequest
}

private Uni<String> readUserIdFromToken(SecurityContext ctx) {

return currentIdentityAssociation.getDeferredIdentity()
.onItem().transformToUni(identity -> {
if (ctx.getUserPrincipal() == null || !ctx.getUserPrincipal().getName().equals(identity.getPrincipal().getName())) {
Expand All @@ -189,8 +188,8 @@ private Uni<String> readUserIdFromToken(SecurityContext ctx) {
@Path("/{onboardingId}/complete")
@Tag(name = "internal-v1")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Response> complete(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file) {
return onboardingService.complete(onboardingId, file)
public Uni<Response> complete(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file, @Context ResteasyReactiveRequestContext ctx) {
return onboardingService.complete(onboardingId, retrieveContractFromFormData(ctx.getFormData(), file))
.map(ignore -> Response
.status(HttpStatus.SC_NO_CONTENT)
.build());
Expand All @@ -204,8 +203,8 @@ public Uni<Response> complete(@PathParam(value = "onboardingId") String onboardi
@PUT
@Path("/{onboardingId}/completeOnboardingUsers")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Response> completeOnboardingUser(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file) {
return onboardingService.completeOnboardingUsers(onboardingId, file)
public Uni<Response> completeOnboardingUser(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file, @Context ResteasyReactiveRequestContext ctx) {
return onboardingService.completeOnboardingUsers(onboardingId, retrieveContractFromFormData(ctx.getFormData(), file))
.map(ignore -> Response
.status(HttpStatus.SC_NO_CONTENT)
.build());
Expand All @@ -218,8 +217,8 @@ public Uni<Response> completeOnboardingUser(@PathParam(value = "onboardingId") S
@Tag(name = "internal-v1")
@Tag(name = "Onboarding")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Response> consume(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file) {
return onboardingService.completeWithoutSignatureVerification(onboardingId, file)
public Uni<Response> consume(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file, @Context ResteasyReactiveRequestContext ctx) {
return onboardingService.completeWithoutSignatureVerification(onboardingId, retrieveContractFromFormData(ctx.getFormData(), file))
.map(ignore -> Response
.status(HttpStatus.SC_NO_CONTENT)
.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package it.pagopa.selfcare.onboarding.model;

import lombok.Builder;
import lombok.Getter;

import java.io.File;

@Builder
@Getter
public class FormItem {
private File file;
private String fileName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import it.pagopa.selfcare.onboarding.controller.response.OnboardingGetResponse;
import it.pagopa.selfcare.onboarding.controller.response.OnboardingResponse;
import it.pagopa.selfcare.onboarding.entity.Onboarding;
import it.pagopa.selfcare.onboarding.model.FormItem;
import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters;

import java.io.File;
import java.util.List;

public interface OnboardingService {
Expand All @@ -30,11 +29,11 @@ public interface OnboardingService {

Uni<OnboardingGet> approve(String onboardingId);

Uni<Onboarding> complete(String tokenId, File contract);
Uni<Onboarding> complete(String tokenId, FormItem formItem);

Uni<Onboarding> completeOnboardingUsers(String tokenId, File contract);
Uni<Onboarding> completeOnboardingUsers(String tokenId, FormItem formItem);

Uni<Onboarding> completeWithoutSignatureVerification(String tokenId, File contract);
Uni<Onboarding> completeWithoutSignatureVerification(String tokenId, FormItem formItem);

Uni<OnboardingGetResponse> onboardingGet(OnboardingGetFilters filters);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import it.pagopa.selfcare.onboarding.mapper.InstitutionMapper;
import it.pagopa.selfcare.onboarding.mapper.OnboardingMapper;
import it.pagopa.selfcare.onboarding.mapper.UserMapper;
import it.pagopa.selfcare.onboarding.model.FormItem;
import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters;
import it.pagopa.selfcare.onboarding.service.strategy.OnboardingValidationStrategy;
import it.pagopa.selfcare.onboarding.service.util.OnboardingUtils;
Expand Down Expand Up @@ -57,7 +58,6 @@
import org.openapi.quarkus.user_registry_json.api.UserApi;
import org.openapi.quarkus.user_registry_json.model.*;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -729,7 +729,7 @@ public Uni<OnboardingGet> approve(String onboardingId) {
}

@Override
public Uni<Onboarding> complete(String onboardingId, File contract) {
public Uni<Onboarding> complete(String onboardingId, FormItem formItem) {

if (Boolean.TRUE.equals(isVerifyEnabled)) {
//Retrieve as Tuple: managers fiscal-code from user registry and contract digest
Expand All @@ -739,23 +739,21 @@ public Uni<Onboarding> complete(String onboardingId, File contract) {
.asTuple()
.onItem().transformToUni(inputSignatureVerification ->
Uni.createFrom().item(() -> {
signatureService.verifySignature(contract,
signatureService.verifySignature(formItem.getFile(),
inputSignatureVerification.getItem2(),
inputSignatureVerification.getItem1());
return onboarding;
})
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
);

return complete(onboardingId, contract, verification);
return complete(onboardingId, formItem, verification);
} else {
return completeWithoutSignatureVerification(onboardingId, contract);
return completeWithoutSignatureVerification(onboardingId, formItem);
}
}

@Override
public Uni<Onboarding> completeOnboardingUsers(String onboardingId, File contract) {

public Uni<Onboarding> completeOnboardingUsers(String onboardingId, FormItem formItem) {
if (Boolean.TRUE.equals(isVerifyEnabled)) {
//Retrieve as Tuple: managers fiscal-code from user registry and contract digest
//At least, verify contract signature using both
Expand All @@ -764,32 +762,32 @@ public Uni<Onboarding> completeOnboardingUsers(String onboardingId, File contrac
.asTuple()
.onItem().transformToUni(inputSignatureVerification ->
Uni.createFrom().item(() -> {
signatureService.verifySignature(contract,
signatureService.verifySignature(formItem.getFile(),
inputSignatureVerification.getItem2(),
inputSignatureVerification.getItem1());
return onboarding;
})
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
);

return completeOnboardingUsers(onboardingId, contract, verification);
return completeOnboardingUsers(onboardingId, formItem, verification);
} else {
return completeOnboardingUsersWithoutSignatureVerification(onboardingId, contract);
return completeOnboardingUsersWithoutSignatureVerification(onboardingId, formItem);
}
}

public Uni<Onboarding> completeOnboardingUsersWithoutSignatureVerification(String onboardingId, File contract) {
public Uni<Onboarding> completeOnboardingUsersWithoutSignatureVerification(String onboardingId, FormItem formItem) {
Function<Onboarding, Uni<Onboarding>> verification = ignored -> Uni.createFrom().item(ignored);
return completeOnboardingUsers(onboardingId, contract, verification);
return completeOnboardingUsers(onboardingId, formItem, verification);
}

@Override
public Uni<Onboarding> completeWithoutSignatureVerification(String onboardingId, File contract) {
public Uni<Onboarding> completeWithoutSignatureVerification(String onboardingId, FormItem formItem) {
Function<Onboarding, Uni<Onboarding>> verification = ignored -> Uni.createFrom().item(ignored);
return complete(onboardingId, contract, verification);
return complete(onboardingId, formItem, verification);
}

private Uni<Onboarding> complete(String onboardingId, File contract, Function<Onboarding, Uni<Onboarding>> verificationContractSignature) {
private Uni<Onboarding> complete(String onboardingId, FormItem formItem, Function<Onboarding, Uni<Onboarding>> verificationContractSignature) {

return retrieveOnboardingAndCheckIfExpired(onboardingId)
.onItem().transformToUni(verificationContractSignature::apply)
Expand All @@ -801,7 +799,7 @@ private Uni<Onboarding> complete(String onboardingId, File contract, Function<On
.replaceWith(onboarding)
)
//Upload contract on storage
.onItem().transformToUni(onboarding -> uploadSignedContractAndUpdateToken(onboardingId, contract)
.onItem().transformToUni(onboarding -> uploadSignedContractAndUpdateToken(onboardingId, formItem)
.map(ignore -> onboarding))
// Start async activity if onboardingOrchestrationEnabled is true
.onItem().transformToUni(onboarding -> onboardingOrchestrationEnabled
Expand All @@ -810,7 +808,7 @@ private Uni<Onboarding> complete(String onboardingId, File contract, Function<On
: Uni.createFrom().item(onboarding));
}

private Uni<Onboarding> completeOnboardingUsers(String onboardingId, File contract, Function<Onboarding, Uni<Onboarding>> verificationContractSignature) {
private Uni<Onboarding> completeOnboardingUsers(String onboardingId, FormItem formItem, Function<Onboarding, Uni<Onboarding>> verificationContractSignature) {

return retrieveOnboardingAndCheckIfExpired(onboardingId)
.onItem().transformToUni(verificationContractSignature::apply)
Expand All @@ -822,7 +820,7 @@ private Uni<Onboarding> completeOnboardingUsers(String onboardingId, File contra
.replaceWith(onboarding)
)
//Upload contract on storage
.onItem().transformToUni(onboarding -> uploadSignedContractAndUpdateToken(onboardingId, contract)
.onItem().transformToUni(onboarding -> uploadSignedContractAndUpdateToken(onboardingId, formItem)
.map(ignore -> onboarding))
// Start async activity if onboardingOrchestrationEnabled is true
.onItem().transformToUni(onboarding -> onboardingOrchestrationEnabled
Expand All @@ -831,14 +829,17 @@ private Uni<Onboarding> completeOnboardingUsers(String onboardingId, File contra
: Uni.createFrom().item(onboarding));
}

private Uni<String> uploadSignedContractAndUpdateToken(String onboardingId, File contract) {
private Uni<String> uploadSignedContractAndUpdateToken(String onboardingId, FormItem formItem) {
return retrieveToken(onboardingId)
.onItem().transformToUni(token -> Uni.createFrom().item(Unchecked.supplier(() -> {
final String path = String.format("%s%s", pathContracts, onboardingId);
final String filename = String.format("signed_%s", Optional.ofNullable(token.getContractFilename()).orElse(onboardingId));
final String signedContractExtension = getFileExtension(formItem.getFileName());
final String persistedContractFileName = Optional.ofNullable(token.getContractFilename()).orElse(onboardingId);
final String signedContractFileName = replaceFileExtension(persistedContractFileName, signedContractExtension);
final String filename = String.format("signed_%s", signedContractFileName);

try {
return azureBlobClient.uploadFile(path, filename, Files.readAllBytes(contract.toPath()));
return azureBlobClient.uploadFile(path, filename, Files.readAllBytes(formItem.getFile().toPath()));
} catch (IOException e) {
throw new OnboardingNotAllowedException(GENERIC_ERROR.getCode(),
"Error on upload contract for onboarding with id " + onboardingId);
Expand All @@ -851,6 +852,30 @@ private Uni<String> uploadSignedContractAndUpdateToken(String onboardingId, File
);
}

private String getFileExtension(String name) {
String[] parts = name.split("\\.");
String ext = "";

if (parts.length == 2) {
return parts[1];
}

if(parts.length > 2) {
// join all parts except the first one
ext = String.join(".", Arrays.copyOfRange(parts, 1, parts.length));
}

return ext;
}

private String replaceFileExtension(String originalFilename, String newExtension) {
int lastIndexOf = originalFilename.lastIndexOf(".");
if (lastIndexOf == -1) {
return originalFilename + newExtension;
} else {
return originalFilename.substring(0, lastIndexOf) + "." + newExtension;
}
}

private Uni<Onboarding> retrieveOnboardingAndCheckIfExpired(String onboardingId) {
//Retrieve Onboarding if exists
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package it.pagopa.selfcare.onboarding.util;

import it.pagopa.selfcare.onboarding.constants.CustomError;
import it.pagopa.selfcare.onboarding.exception.InvalidRequestException;
import it.pagopa.selfcare.onboarding.model.FormItem;
import org.jboss.resteasy.reactive.server.core.multipart.FormData;
import org.jboss.resteasy.reactive.server.multipart.FormValue;

import java.io.File;
import java.util.Deque;

public class Utils {
private static final String DEFAULT_CONTRACT_FORM_DATA_NAME = "contract";
private Utils() {
}

public static FormItem retrieveContractFromFormData(FormData formData, File file) {
Deque<FormValue> deck = formData.get(DEFAULT_CONTRACT_FORM_DATA_NAME);
if(deck.size() > 1) {
throw new InvalidRequestException(CustomError.TOO_MANY_CONTRACTS.getMessage(), CustomError.TOO_MANY_CONTRACTS.getCode());
}

return FormItem.builder()
.file(file)
.fileName(deck.getFirst().getFileName())
.build();
}
}
Loading

0 comments on commit e927e71

Please sign in to comment.