diff --git a/.spectral.yaml b/.spectral.yaml index 5e6a3df..ac89487 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -11,4 +11,9 @@ overrides: - "src/main/resources/META-INF/openapi.yaml#/paths/~1.well-known~1jwks.json/get/security" - "src/main/resources/META-INF/openapi.yaml#/paths/~1.well-known~1openid-configuration/get/security" rules: - owasp:api2:2023-read-restricted: "off" \ No newline at end of file + owasp:api2:2023-read-restricted: "off" + - files: + - "src/main/resources/META-INF/openapi.yaml" + rules: + owasp:api3:2023-no-additionalProperties: "off" + owasp:api3:2023-constrained-additionalProperties: "off" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 544ec2c..d8909b4 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,11 @@ reactor-test test + + com.azure + azure-security-keyvault-keys + 4.8.7 + diff --git a/src/main/java/it/gov/pagopa/tpp/EmdTpp.java b/src/main/java/it/gov/pagopa/tpp/EmdTpp.java index e2bc6d3..84e0347 100644 --- a/src/main/java/it/gov/pagopa/tpp/EmdTpp.java +++ b/src/main/java/it/gov/pagopa/tpp/EmdTpp.java @@ -1,8 +1,10 @@ package it.gov.pagopa.tpp; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +@Slf4j @SpringBootApplication(scanBasePackages = "it.gov.pagopa") public class EmdTpp { diff --git a/src/main/java/it/gov/pagopa/tpp/controller/TppController.java b/src/main/java/it/gov/pagopa/tpp/controller/TppController.java index ea37aa4..efd6524 100644 --- a/src/main/java/it/gov/pagopa/tpp/controller/TppController.java +++ b/src/main/java/it/gov/pagopa/tpp/controller/TppController.java @@ -1,14 +1,13 @@ package it.gov.pagopa.tpp.controller; -import it.gov.pagopa.tpp.dto.TppDTO; -import it.gov.pagopa.tpp.dto.TppIdList; -import it.gov.pagopa.tpp.dto.TppUpdateState; +import it.gov.pagopa.tpp.dto.*; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; import java.util.List; + @RestController @RequestMapping("/emd/tpp") public interface TppController { @@ -22,7 +21,6 @@ public interface TppController { @PostMapping("/list") Mono>> getEnabledList(@Valid @RequestBody TppIdList tppIdList); - /** * Update a tpp * @@ -41,17 +39,36 @@ public interface TppController { @PostMapping("/save") Mono> save(@Valid @RequestBody TppDTO tppDTO); + @PutMapping("/update") - Mono> update(@Valid @RequestBody TppDTO tppDTO); + Mono> updateTppDetails(@Valid @RequestBody TppDTOWithoutTokenSection tppDTOWithoutTokenSection); + /** + * Update TokenSection of a TPP + * + * @param tppId of the TPP to update + * @param tokenSectionDTO updated token section + * @return outcome of the update + */ + @PutMapping("/update/{tppId}/token") + Mono> updateTokenSection(@Valid @PathVariable String tppId, @Valid @RequestBody TokenSectionDTO tokenSectionDTO); /** - * Get a tpp + * Get a tpp (without token section) * * @param tppId to get - * @return outcome of getting tpp + * @return outcome of getting tpp */ @GetMapping("/{tppId}") - Mono> get(@Valid @PathVariable String tppId); + Mono> getTppDetails(@Valid @PathVariable String tppId); + + /** + * Get TokenSection of a TPP + * + * @param tppId to get token section + * @return outcome of getting token section + */ + @GetMapping("/{tppId}/token") + Mono> getTokenSection(@Valid @PathVariable String tppId); } diff --git a/src/main/java/it/gov/pagopa/tpp/controller/TppControllerImpl.java b/src/main/java/it/gov/pagopa/tpp/controller/TppControllerImpl.java index 957dfe7..0179dd1 100644 --- a/src/main/java/it/gov/pagopa/tpp/controller/TppControllerImpl.java +++ b/src/main/java/it/gov/pagopa/tpp/controller/TppControllerImpl.java @@ -1,8 +1,6 @@ package it.gov.pagopa.tpp.controller; -import it.gov.pagopa.tpp.dto.TppDTO; -import it.gov.pagopa.tpp.dto.TppIdList; -import it.gov.pagopa.tpp.dto.TppUpdateState; +import it.gov.pagopa.tpp.dto.*; import it.gov.pagopa.tpp.service.TppServiceImpl; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -20,36 +18,45 @@ public TppControllerImpl(TppServiceImpl tppService) { this.tppService = tppService; } - @Override public Mono>> getEnabledList(TppIdList tppIdList) { return tppService.getEnabledList(tppIdList.getIds()) .map(ResponseEntity::ok); } - @Override public Mono> updateState(TppUpdateState tppUpdateState) { - return tppService.updateState(tppUpdateState.getTppId(),tppUpdateState.getState()) + return tppService.updateState(tppUpdateState.getTppId(), tppUpdateState.getState()) .map(ResponseEntity::ok); - } @Override public Mono> save(TppDTO tppDTO) { - return tppService.createNewTpp(tppDTO, String.format("%s_%d", UUID.randomUUID(), System.currentTimeMillis())) + return tppService.createNewTpp(tppDTO, String.format("%s-%d", UUID.randomUUID(), System.currentTimeMillis())) + .map(ResponseEntity::ok); + } + + @Override + public Mono> updateTppDetails(TppDTOWithoutTokenSection tppDTOWithoutTokenSection) { + return tppService.updateTppDetails(tppDTOWithoutTokenSection) + .map(ResponseEntity::ok); + } + + @Override + public Mono> updateTokenSection(String tppId, TokenSectionDTO tokenSectionDTO) { + return tppService.updateTokenSection(tppId, tokenSectionDTO) .map(ResponseEntity::ok); } @Override - public Mono> update(TppDTO tppDTO) { - return tppService.updateExistingTpp(tppDTO) + public Mono> getTppDetails(String tppId) { + return tppService.getTppDetails(tppId) .map(ResponseEntity::ok); } @Override - public Mono> get(String tppId) { - return tppService.get(tppId) + public Mono> getTokenSection(String tppId) { + return tppService.getTokenSection(tppId) .map(ResponseEntity::ok); } diff --git a/src/main/java/it/gov/pagopa/tpp/dto/TokenSectionDTO.java b/src/main/java/it/gov/pagopa/tpp/dto/TokenSectionDTO.java new file mode 100644 index 0000000..3ec828c --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/dto/TokenSectionDTO.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.tpp.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.Map; + +@Data +@SuperBuilder +@NoArgsConstructor +public class TokenSectionDTO { + private String contentType; + private Map pathAdditionalProperties; + private Map bodyAdditionalProperties; +} diff --git a/src/main/java/it/gov/pagopa/tpp/dto/TppDTO.java b/src/main/java/it/gov/pagopa/tpp/dto/TppDTO.java index 4df1678..cda4a84 100644 --- a/src/main/java/it/gov/pagopa/tpp/dto/TppDTO.java +++ b/src/main/java/it/gov/pagopa/tpp/dto/TppDTO.java @@ -48,5 +48,4 @@ public class TppDTO { private LocalDateTime lastUpdateDate; private TokenSection tokenSection; - } diff --git a/src/main/java/it/gov/pagopa/tpp/dto/TppDTOWithoutTokenSection.java b/src/main/java/it/gov/pagopa/tpp/dto/TppDTOWithoutTokenSection.java new file mode 100644 index 0000000..516f565 --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/dto/TppDTOWithoutTokenSection.java @@ -0,0 +1,47 @@ +package it.gov.pagopa.tpp.dto; + +import it.gov.pagopa.tpp.enums.AuthenticationType; +import it.gov.pagopa.tpp.model.Contact; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; + +@Data +@SuperBuilder +@NoArgsConstructor +public class TppDTOWithoutTokenSection { + private String tppId; + + @NotBlank(message = "Entity ID must not be blank") + @Pattern(regexp = "^(\\d{11}|[A-Za-z0-9]{16})$", message = "Entity ID must be 11 digits or up to 16 alphanumeric characters") + private String entityId; + + @NotBlank(message = "ID PSP must not be blank") + private String idPsp; + + @NotBlank(message = "Business name must not be blank") + private String businessName; + + @NotBlank(message = "Legal address must not be blank") + private String legalAddress; + + @Pattern(regexp = "^(https?|ftp)://[^ /$.?#].[^ ]*$", message = "Message URL must be a valid URL") + private String messageUrl; + + @Pattern(regexp = "^(https?|ftp)://[^ /$.?#].[^ ]*$", message = "Authentication URL must be a valid URL") + private String authenticationUrl; + + @NotNull(message = "Authentication type must not be null") + private AuthenticationType authenticationType; + + @NotNull(message = "Contact must not be null") + private Contact contact; + private Boolean state; + private LocalDateTime creationDate; + private LocalDateTime lastUpdateDate; +} diff --git a/src/main/java/it/gov/pagopa/tpp/dto/mapper/TokenSectionObjectToDTOMapper.java b/src/main/java/it/gov/pagopa/tpp/dto/mapper/TokenSectionObjectToDTOMapper.java new file mode 100644 index 0000000..7a294b0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/dto/mapper/TokenSectionObjectToDTOMapper.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.tpp.dto.mapper; + +import it.gov.pagopa.tpp.dto.TokenSectionDTO; +import it.gov.pagopa.tpp.model.TokenSection; +import org.springframework.stereotype.Service; + +@Service +public class TokenSectionObjectToDTOMapper { + + public TokenSectionDTO map(TokenSection tokenSection) { + return TokenSectionDTO.builder() + .contentType(tokenSection.getContentType()) + .pathAdditionalProperties(tokenSection.getPathAdditionalProperties()) + .bodyAdditionalProperties(tokenSection.getBodyAdditionalProperties()) + .build(); + + } +} diff --git a/src/main/java/it/gov/pagopa/tpp/dto/mapper/TppWithoutTokenSectionObjectToDTOMapper.java b/src/main/java/it/gov/pagopa/tpp/dto/mapper/TppWithoutTokenSectionObjectToDTOMapper.java new file mode 100644 index 0000000..c030e9c --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/dto/mapper/TppWithoutTokenSectionObjectToDTOMapper.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.tpp.dto.mapper; + +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; +import it.gov.pagopa.tpp.model.Tpp; +import org.springframework.stereotype.Service; + +@Service +public class TppWithoutTokenSectionObjectToDTOMapper { + + public TppDTOWithoutTokenSection map(Tpp tpp){ + return TppDTOWithoutTokenSection.builder() + .state(tpp.getState()) + .messageUrl(tpp.getMessageUrl()) + .authenticationUrl(tpp.getAuthenticationUrl()) + .authenticationType(tpp.getAuthenticationType()) + .tppId(tpp.getTppId()) + .idPsp(tpp.getIdPsp()) + .legalAddress(tpp.getLegalAddress()) + .businessName(tpp.getBusinessName()) + .contact(tpp.getContact()) + .entityId(tpp.getEntityId()) + .creationDate(tpp.getCreationDate()) + .lastUpdateDate(tpp.getLastUpdateDate()) + .build(); + } +} diff --git a/src/main/java/it/gov/pagopa/tpp/model/TokenSection.java b/src/main/java/it/gov/pagopa/tpp/model/TokenSection.java index 9efbc11..c5fe307 100644 --- a/src/main/java/it/gov/pagopa/tpp/model/TokenSection.java +++ b/src/main/java/it/gov/pagopa/tpp/model/TokenSection.java @@ -3,11 +3,14 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + import java.util.Map; @Data @NoArgsConstructor @AllArgsConstructor +@SuperBuilder public class TokenSection { private String contentType; private Map pathAdditionalProperties; diff --git a/src/main/java/it/gov/pagopa/tpp/model/Tpp.java b/src/main/java/it/gov/pagopa/tpp/model/Tpp.java index 73218c8..fac1936 100644 --- a/src/main/java/it/gov/pagopa/tpp/model/Tpp.java +++ b/src/main/java/it/gov/pagopa/tpp/model/Tpp.java @@ -3,6 +3,7 @@ import it.gov.pagopa.tpp.enums.AuthenticationType; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,6 +11,7 @@ @Document(collection = "tpp") @Data +@NoArgsConstructor @SuperBuilder public class Tpp { diff --git a/src/main/java/it/gov/pagopa/tpp/model/mapper/TokenSectionDTOToObjectMapper.java b/src/main/java/it/gov/pagopa/tpp/model/mapper/TokenSectionDTOToObjectMapper.java new file mode 100644 index 0000000..260bfdb --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/model/mapper/TokenSectionDTOToObjectMapper.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.tpp.model.mapper; + +import it.gov.pagopa.tpp.dto.TokenSectionDTO; +import it.gov.pagopa.tpp.model.TokenSection; +import org.springframework.stereotype.Service; + +@Service +public class TokenSectionDTOToObjectMapper { + + public TokenSection map(TokenSectionDTO tokenSectionDTO) { + return new TokenSection( + tokenSectionDTO.getContentType(), + tokenSectionDTO.getPathAdditionalProperties(), + tokenSectionDTO.getBodyAdditionalProperties() + ); + } +} diff --git a/src/main/java/it/gov/pagopa/tpp/service/TppService.java b/src/main/java/it/gov/pagopa/tpp/service/TppService.java index 45de77d..4375681 100644 --- a/src/main/java/it/gov/pagopa/tpp/service/TppService.java +++ b/src/main/java/it/gov/pagopa/tpp/service/TppService.java @@ -1,6 +1,8 @@ package it.gov.pagopa.tpp.service; +import it.gov.pagopa.tpp.dto.TokenSectionDTO; import it.gov.pagopa.tpp.dto.TppDTO; +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; import reactor.core.publisher.Mono; import java.util.List; @@ -11,9 +13,14 @@ public interface TppService { Mono createNewTpp(TppDTO tppDTO, String tppId); - Mono updateExistingTpp(TppDTO tppDTO); + Mono updateTppDetails(TppDTOWithoutTokenSection tppDTOWithoutTokenSection); + + Mono updateTokenSection(String tppId, TokenSectionDTO tokenSectionDTO); Mono updateState(String tppId, Boolean state); - Mono get(String tppId); + Mono getTppDetails(String tppId); + + Mono getTokenSection(String tppId); + } diff --git a/src/main/java/it/gov/pagopa/tpp/service/TppServiceImpl.java b/src/main/java/it/gov/pagopa/tpp/service/TppServiceImpl.java index efd3e6e..e828d7f 100644 --- a/src/main/java/it/gov/pagopa/tpp/service/TppServiceImpl.java +++ b/src/main/java/it/gov/pagopa/tpp/service/TppServiceImpl.java @@ -1,13 +1,23 @@ package it.gov.pagopa.tpp.service; +import com.azure.security.keyvault.keys.cryptography.CryptographyClient; +import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm; +import com.azure.security.keyvault.keys.models.KeyVaultKey; import it.gov.pagopa.tpp.configuration.ExceptionMap; import it.gov.pagopa.tpp.constants.TppConstants.ExceptionMessage; import it.gov.pagopa.tpp.constants.TppConstants.ExceptionName; +import it.gov.pagopa.tpp.dto.TokenSectionDTO; import it.gov.pagopa.tpp.dto.TppDTO; +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; +import it.gov.pagopa.tpp.dto.mapper.TokenSectionObjectToDTOMapper; import it.gov.pagopa.tpp.dto.mapper.TppObjectToDTOMapper; +import it.gov.pagopa.tpp.dto.mapper.TppWithoutTokenSectionObjectToDTOMapper; +import it.gov.pagopa.tpp.model.TokenSection; import it.gov.pagopa.tpp.model.Tpp; +import it.gov.pagopa.tpp.model.mapper.TokenSectionDTOToObjectMapper; import it.gov.pagopa.tpp.model.mapper.TppDTOToObjectMapper; import it.gov.pagopa.tpp.repository.TppRepository; +import it.gov.pagopa.tpp.service.keyvault.AzureEncryptService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -24,15 +34,23 @@ public class TppServiceImpl implements TppService { private final TppRepository tppRepository; private final TppObjectToDTOMapper mapperToDTO; + private final TppWithoutTokenSectionObjectToDTOMapper tppWithoutTokenSectionMapperToDTO; + private final TokenSectionObjectToDTOMapper tokenSectionMapperToDTO; private final TppDTOToObjectMapper mapperToObject; + private final TokenSectionDTOToObjectMapper tokenSectionMapperToObject; private final ExceptionMap exceptionMap; + private final AzureEncryptService azureEncryptService; - public TppServiceImpl(TppRepository tppRepository, TppObjectToDTOMapper mapperToDTO, - TppDTOToObjectMapper mapperToObject, ExceptionMap exceptionMap) { + public TppServiceImpl(TppRepository tppRepository, TppObjectToDTOMapper mapperToDTO, TppWithoutTokenSectionObjectToDTOMapper tppWithoutTokenSectionMapperToDTO, TokenSectionObjectToDTOMapper tokenSectionMapperToDTO, + TppDTOToObjectMapper mapperToObject, TokenSectionDTOToObjectMapper tokenSectionMapperToObject, ExceptionMap exceptionMap, AzureEncryptService azureEncryptService) { this.tppRepository = tppRepository; this.mapperToDTO = mapperToDTO; + this.tppWithoutTokenSectionMapperToDTO = tppWithoutTokenSectionMapperToDTO; + this.tokenSectionMapperToDTO = tokenSectionMapperToDTO; this.mapperToObject = mapperToObject; + this.tokenSectionMapperToObject = tokenSectionMapperToObject; this.exceptionMap = exceptionMap; + this.azureEncryptService = azureEncryptService; } @Override @@ -40,30 +58,29 @@ public Mono> getEnabledList(List tppIdList) { log.info("[TPP-SERVICE][GET-ENABLED] Received tppIdList: {}", tppIdList); return tppRepository.findByTppIdInAndStateTrue(tppIdList) + .map(tpp -> { keyDecrypt(tpp.getTokenSection(),tpp.getTppId()); + return mapperToDTO.map(tpp);}) .collectList() - .map(tppList -> tppList.stream() - .map(mapperToDTO::map) - .toList()) .doOnSuccess(tppDTOList -> log.info("[TPP-SERVICE][GET-ENABLED] Found TPPs: {}", tppDTOList)) .doOnError(error -> log.error("[TPP-SERVICE][GET-ENABLED] Error retrieving enabled TPPs: {}", error.getMessage(), error)); } @Override - public Mono updateExistingTpp(TppDTO tppDTO) { - if (tppDTO.getTppId() == null) + public Mono updateTppDetails(TppDTOWithoutTokenSection tppDTOWithoutTokenSection) { + if (tppDTOWithoutTokenSection.getTppId() == null) return Mono.error(exceptionMap.throwException(ExceptionName.GENERIC_ERROR, ExceptionMessage.GENERIC_ERROR)); - return tppRepository.findByTppId(tppDTO.getTppId()) + return tppRepository.findByTppId(tppDTOWithoutTokenSection.getTppId()) .flatMap(existingTpp -> { - log.info("[TPP-SERVICE][UPSERT] TPP with tppId [{}] already exists. Updating...", tppDTO.getTppId()); + log.info("[TPP-SERVICE][UPSERT] TPP with tppId [{}] already exists. Updating...", tppDTOWithoutTokenSection.getTppId()); existingTpp.setLastUpdateDate(LocalDateTime.now()); - existingTpp.setMessageUrl(tppDTO.getMessageUrl()); - existingTpp.setContact(tppDTO.getContact()); - existingTpp.setBusinessName(tppDTO.getBusinessName()); - existingTpp.setLegalAddress(tppDTO.getLegalAddress()); + existingTpp.setMessageUrl(tppDTOWithoutTokenSection.getMessageUrl()); + existingTpp.setContact(tppDTOWithoutTokenSection.getContact()); + existingTpp.setBusinessName(tppDTOWithoutTokenSection.getBusinessName()); + existingTpp.setLegalAddress(tppDTOWithoutTokenSection.getLegalAddress()); return tppRepository.save(existingTpp) - .map(mapperToDTO::map) + .map(tppWithoutTokenSectionMapperToDTO::map) .doOnSuccess(savedTpp -> log.info("[TPP-SERVICE][UPSERT] Updated existing TPP with tppId: {}", existingTpp.getTppId())) .doOnError(error -> log.error("[TPP-SERVICE][SAVE] Error saving TPP with tppId {}: {}", existingTpp.getTppId(), error.getMessage())); }) @@ -72,11 +89,34 @@ public Mono updateExistingTpp(TppDTO tppDTO) { } @Override - public Mono createNewTpp(TppDTO tppDTO, String tppId) { - if (tppDTO.getTokenSection() == null) + public Mono updateTokenSection(String tppId, TokenSectionDTO tokenSectionDTO) { + if (tppId == null) return Mono.error(exceptionMap.throwException(ExceptionName.GENERIC_ERROR, ExceptionMessage.GENERIC_ERROR)); + return tppRepository.findByTppId(tppId) + .flatMap(existingTpp -> { + log.info("[TPP-SERVICE][UPDATE] Updating TokenSection for TPP with tppId: {}", tppId); + + TokenSection tokenSection = tokenSectionMapperToObject.map(tokenSectionDTO); + KeyVaultKey keyVaultKey = azureEncryptService.getKey(tppId); + keyEncrypt(tokenSection, keyVaultKey); + + existingTpp.setLastUpdateDate(LocalDateTime.now()); + existingTpp.setTokenSection(tokenSection); + + return tppRepository.save(existingTpp) + .map(tpp -> tokenSectionMapperToDTO.map(tpp.getTokenSection())) + .doOnSuccess(updatedTokenSection -> log.info("[TPP-SERVICE][UPDATE] Updated TokenSection for tppId: {}", tppId)) + .doOnError(error -> log.error("[TPP-SERVICE][UPDATE] Error updating TokenSection for tppId {}: {}", tppId, error.getMessage())); + }) + .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_ONBOARDED, + ExceptionMessage.TPP_NOT_ONBOARDED))); + } + + @Override + public Mono createNewTpp(TppDTO tppDTO, String tppId) { + return tppRepository.findByEntityId(tppDTO.getEntityId()) .switchIfEmpty(Mono.defer(() -> createAndSaveNewTpp(tppDTO, tppId))) .flatMap(result -> { @@ -90,6 +130,8 @@ public Mono createNewTpp(TppDTO tppDTO, String tppId) { private Mono createAndSaveNewTpp(TppDTO tppDTO, String tppId) { log.info("[TPP-SERVICE][UPSERT] Creating new entry with generated tppId: {}", tppId); + KeyVaultKey keyVaultKey = azureEncryptService.createRsaKey(tppId); + keyEncrypt(tppDTO.getTokenSection(),keyVaultKey); Tpp tppToSave = mapperToObject.map(tppDTO); tppToSave.setTppId(tppId); tppToSave.setLastUpdateDate(LocalDateTime.now()); @@ -99,6 +141,27 @@ private Mono createAndSaveNewTpp(TppDTO tppDTO, String tppId) { .doOnError(error -> log.error("[TPP-SERVICE][SAVE] Error saving TPP with tppId {}: {}", tppToSave.getTppId(), error.getMessage())); } + private void keyEncrypt(TokenSection tokenSection,KeyVaultKey keyVaultKey) { + CryptographyClient cryptographyClient = azureEncryptService.buildCryptographyClient(keyVaultKey); + if(tokenSection.getPathAdditionalProperties() != null && !tokenSection.getBodyAdditionalProperties().isEmpty()){ + tokenSection.getPathAdditionalProperties().replaceAll((key, value) -> azureEncryptService.encrypt(value.getBytes(), EncryptionAlgorithm.RSA_OAEP_256,cryptographyClient)); + } + if(tokenSection.getBodyAdditionalProperties() != null && !tokenSection.getBodyAdditionalProperties().isEmpty()){ + tokenSection.getBodyAdditionalProperties().replaceAll((key, value) -> azureEncryptService.encrypt(value.getBytes(), EncryptionAlgorithm.RSA_OAEP_256,cryptographyClient)); + } + } + + private void keyDecrypt(TokenSection tokenSection,String tppId) { + KeyVaultKey keyVaultKey = azureEncryptService.getKey(tppId); + CryptographyClient cryptographyClient = azureEncryptService.buildCryptographyClient(keyVaultKey); + if(tokenSection.getPathAdditionalProperties() != null && !tokenSection.getBodyAdditionalProperties().isEmpty()){ + tokenSection.getPathAdditionalProperties().replaceAll((key, value) -> azureEncryptService.decrypt(value, EncryptionAlgorithm.RSA_OAEP_256,cryptographyClient)); + } + if(tokenSection.getBodyAdditionalProperties() != null && !tokenSection.getBodyAdditionalProperties().isEmpty()){ + tokenSection.getBodyAdditionalProperties().replaceAll((key, value) -> azureEncryptService.decrypt(value, EncryptionAlgorithm.RSA_OAEP_256,cryptographyClient)); + } + } + @Override public Mono updateState(String tppId, Boolean state) { log.info("[TPP-SERVICE][UPDATE-STATE] Received request to update state for tppId: {}", inputSanify(tppId)); @@ -116,15 +179,31 @@ public Mono updateState(String tppId, Boolean state) { } @Override - public Mono get(String tppId) { + public Mono getTppDetails(String tppId) { log.info("[TPP-SERVICE][GET] Received request to get TPP for tppId: {}", inputSanify(tppId)); return tppRepository.findByTppId(tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_ONBOARDED, "Tpp not found during get process"))) - .map(mapperToDTO::map) + .map(tppWithoutTokenSectionMapperToDTO::map) .doOnSuccess(tppDTO -> log.info("[TPP-SERVICE][GET] Found TPP with tppId: {}", tppId)) .doOnError(error -> log.error("[TPP-SERVICE][GET] Error retrieving TPP for tppId {}: {}", tppId, error.getMessage())); } + @Override + public Mono getTokenSection(String tppId) { + log.info("[TPP-SERVICE][GET] Received request to get TokenSection for tppId: {}", inputSanify(tppId)); + + return tppRepository.findByTppId(tppId) + .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_ONBOARDED, + "Tpp not found during get process"))) + .flatMap(tpp -> { + TokenSection tokenSection = tpp.getTokenSection(); + keyDecrypt(tokenSection, tppId); + return Mono.just(tokenSectionMapperToDTO.map(tokenSection)); + }) + .doOnSuccess(tokenSectionDTO -> log.info("[TPP-SERVICE][GET] Found TokenSection for tppId: {}", tppId)) + .doOnError(error -> log.error("[TPP-SERVICE][GET] Error retrieving TokenSection for tppId {}: {}", tppId, error.getMessage())); + } + } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/tpp/service/keyvault/AzureEncryptService.java b/src/main/java/it/gov/pagopa/tpp/service/keyvault/AzureEncryptService.java new file mode 100644 index 0000000..89dbaa6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/tpp/service/keyvault/AzureEncryptService.java @@ -0,0 +1,66 @@ +package it.gov.pagopa.tpp.service.keyvault; + +import com.azure.identity.DefaultAzureCredential; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.security.keyvault.keys.KeyClient; +import com.azure.security.keyvault.keys.KeyClientBuilder; +import com.azure.security.keyvault.keys.cryptography.CryptographyClient; +import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder; +import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm; +import com.azure.security.keyvault.keys.models.CreateRsaKeyOptions; +import com.azure.security.keyvault.keys.models.KeyVaultKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.util.Base64; + +@Service +public class AzureEncryptService { + + private static final DefaultAzureCredential DEFAULT_AZURE_CREDENTIAL = new DefaultAzureCredentialBuilder().build(); + + private KeyClient keyClient; + public AzureEncryptService(@Value("${crypto.azure.key-vault.url}") String keyVaultUrl){ + this.keyClient= new KeyClientBuilder() + .vaultUrl(keyVaultUrl) + .credential(DEFAULT_AZURE_CREDENTIAL) + .buildClient(); + } + + public KeyVaultKey getKey(String tppId){ + return keyClient.getKey(tppId); + } + + public KeyVaultKey createRsaKey(String tppId){ + return keyClient + .createRsaKey(new CreateRsaKeyOptions(tppId) + .setExpiresOn(OffsetDateTime.now().plusYears(1)) + .setKeySize(2048) + ); + } + + + public String encrypt(byte[] plainValue, EncryptionAlgorithm encryptionAlgorithm, CryptographyClient cryptoClient) { + return Base64.getEncoder().encodeToString(cryptoClient.encrypt(encryptionAlgorithm, plainValue).getCipherText()); + } + + public String decrypt(String encryptedValue, EncryptionAlgorithm encryptionAlgorithm, CryptographyClient cryptoClient) { + return new String(cryptoClient.decrypt(encryptionAlgorithm, Base64.getDecoder().decode(encryptedValue)).getPlainText()); + } + + public CryptographyClient buildCryptographyClient(KeyVaultKey key) { + return buildCryptographyClient(key.getId()); + } + + public CryptographyClient buildCryptographyClient(String keyId) { + return new CryptographyClientBuilder() + .credential(DEFAULT_AZURE_CREDENTIAL) + .keyIdentifier(keyId) + .buildClient(); + } + + public void setKeyClient(KeyClient keyClient) { + this.keyClient = keyClient; + } +} diff --git a/src/main/resources/META-INF/openapi.yaml b/src/main/resources/META-INF/openapi.yaml index eb81753..5174f2c 100644 --- a/src/main/resources/META-INF/openapi.yaml +++ b/src/main/resources/META-INF/openapi.yaml @@ -6,8 +6,8 @@ info: description: |- EMD TPP contact: - name: Stefano D'Elia - email: stefano11.delia@emeal.nttdata.com + name: PagoPA S.p.A. + email: cstar@pagopa.it servers: - description: Development Test @@ -52,7 +52,7 @@ paths: schema: $ref: '#/components/schemas/TppDTOUpdateState' example: - tppId: 0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900 + tppId: 0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900 state: false responses: '200': @@ -60,7 +60,427 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TppDTOWithDates' + $ref: '#/components/schemas/TppDTOWithDatesWithoutTokenSection' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The TPP was not found + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_NOT_ONBOARDED + message: Tpp not onboarded + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '/update': + put: + tags: + - TPP + summary: >- + ENG: Update TPP information - IT: Aggiorna le informazioni della TPP + operationId: updateTppDetails + description: Save TPP information + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + requestBody: + description: 'ENG: TPP details - IT: Dettagli della TPP' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TppDTOWithoutTokenSection' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TppDTOWithDatesWithoutTokenSection' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The TPP was not found + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_NOT_ONBOARDED + message: Tpp not onboarded + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '/update/{tppId}/token': + put: + tags: + - TPP + summary: >- + ENG: Update TPP token information - IT: Aggiorna i dati utili per la generazione del Token della specifica TPP + operationId: updateTokenSection + description: Update TPP TokenInfo + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + - name: tppId + in: path + description: 'ENG: Unique ID that identify TPP on PagoPA systems - IT: Identificativo univoco della TPP sui sistemi PagoPA' + required: true + schema: + type: string + pattern: "^[ -~]{1,50}$" + minLength: 50 + maxLength: 50 + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" + requestBody: + description: 'ENG: TPP details - IT: Dettagli della TPP' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TokenSectionDecryptedDTO' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TokenSectionEncryptedDTO' headers: Access-Control-Allow-Origin: description: Indicates whether the response can be shared with requesting code from the given origin @@ -232,13 +652,14 @@ paths: required: false schema: $ref: '#/components/schemas/RetryAfter' - '/update': - put: + + '/save': + post: tags: - TPP summary: >- - ENG: Update TPP information - IT: Aggiorna le informazioni della TPP - operationId: update + ENG: Save TPP information - IT: Salva le informazioni della TPP + operationId: save description: Save TPP information parameters: - name: Accept-Language @@ -258,7 +679,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TppDTOTppIdRequired' + $ref: '#/components/schemas/TppDTO' responses: '200': description: Ok @@ -347,6 +768,36 @@ paths: required: false schema: $ref: '#/components/schemas/RetryAfter' + '403': + description: The TPP already onboarded + content: + application/json: + schema: + $ref: '#/components/schemas/TPPErrorDTO' + example: + code: TPP_ALREADY_ONBOARDED + message: Tpp is already onboarded + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' '404': description: The TPP was not found content: @@ -437,14 +888,14 @@ paths: required: false schema: $ref: '#/components/schemas/RetryAfter' - '/save': - post: + '/{tppId}': + get: tags: - TPP summary: >- - ENG: Save TPP information - IT: Salva le informazioni della TPP - operationId: save - description: Save TPP information + ENG: Returns the TPP detail from tppId associated - IT: Ritorna il dettaglio della TPP attraverso il tppId + operationId: getTppDetails + description: Get TPP detail from tppId parameters: - name: Accept-Language in: header @@ -457,20 +908,23 @@ paths: example: it-IT default: it-IT required: true - requestBody: - description: 'ENG: TPP details - IT: Dettagli della TPP' - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TppDTO' + - name: tppId + in: path + description: 'ENG: Unique ID that identify TPP on PagoPA systems - IT: Identificativo univoco della TPP sui sistemi PagoPA' + required: true + schema: + type: string + pattern: "^[ -~]{1,50}$" + minLength: 50 + maxLength: 50 + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" responses: '200': description: Ok content: application/json: schema: - $ref: '#/components/schemas/TppDTOWithDates' + $ref: '#/components/schemas/TppDTOWithDatesWithoutTokenSection' headers: Access-Control-Allow-Origin: description: Indicates whether the response can be shared with requesting code from the given origin @@ -552,36 +1006,6 @@ paths: required: false schema: $ref: '#/components/schemas/RetryAfter' - '403': - description: The TPP already onboarded - content: - application/json: - schema: - $ref: '#/components/schemas/TPPErrorDTO' - example: - code: TPP_ALREADY_ONBOARDED - message: Tpp is already onboarded - headers: - Access-Control-Allow-Origin: - description: Indicates whether the response can be shared with requesting code from the given origin - required: false - schema: - $ref: '#/components/schemas/AccessControlAllowOrigin' - RateLimit-Limit: - description: The number of allowed requests in the current period - required: false - schema: - $ref: '#/components/schemas/RateLimitLimit' - RateLimit-Reset: - description: The number of seconds left in the current period - required: false - schema: - $ref: '#/components/schemas/RateLimitReset' - Retry-After: - description: The number of seconds to wait before allowing a follow-up request - required: false - schema: - $ref: '#/components/schemas/RetryAfter' '404': description: The TPP was not found content: @@ -672,14 +1096,15 @@ paths: required: false schema: $ref: '#/components/schemas/RetryAfter' - '/{tppId}': + + '/{tppId}/token': get: tags: - TPP summary: >- - ENG: Returns the TPP detalil from tppId associated - IT: Ritorna il dettaglio della TPP attraverso il tppId - operationId: get - description: Get TPP detail from tppId + ENG: Returns the TPP token section detail from tppId associated - IT: Ritorna il dettaglio della sezione del token della TPP attraverso il tppId + operationId: getTokenSection + description: Get TPP detail section from tppId parameters: - name: Accept-Language in: header @@ -701,14 +1126,14 @@ paths: pattern: "^[ -~]{1,50}$" minLength: 50 maxLength: 50 - example: "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" responses: '200': description: Ok content: application/json: schema: - $ref: '#/components/schemas/TppDTOWithDates' + $ref: '#/components/schemas/TokenSectionDecryptedDTO' headers: Access-Control-Allow-Origin: description: Indicates whether the response can be shared with requesting code from the given origin @@ -882,7 +1307,6 @@ paths: $ref: '#/components/schemas/RetryAfter' - components: schemas: @@ -927,7 +1351,7 @@ components: pattern: "^[ -~]{1,50}$" minLength: 50 maxLength: 50 - example: "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" state: type: boolean description: "Status active/inactive" @@ -952,7 +1376,7 @@ components: pattern: "^[ -~]{1,50}$" minLength: 1 maxLength: 50 - example: "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" entityId: type: string description: "Fiscal Code or P.IVA of the TPP" @@ -1005,11 +1429,78 @@ components: example: "OAUTH2" contact: $ref: '#/components/schemas/Contact' + tokenSection: + $ref: '#/components/schemas/TokenSectionEncryptedDTO' state: type: boolean description: "Status active/inactive" example: true + TokenSectionDecryptedDTO: + type: object + description: "Schema relativo al token" + properties: + contentType: + type: string + example: application/x-www-form-urlencoded + description: "Content-Type della richiesta" + pattern: "^[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+$" + maxLength: 100 + pathAdditionalProperties: + type: object + description: "path additional properties information" + additionalProperties: + type: string + maxLength: 1000000 + pattern: ".*" + example: + tenantId: "123424222" + bodyAdditionalProperties: + type: object + description: "body additional properties information" + additionalProperties: + type: string + maxLength: 1000000 + pattern: ".*" + example: + client_id: "242425-22222-1111-hssjsuc7" + grant_type: "client_credentials" + client_secret: "oz7shs8cjsbsy639939" + + + + TokenSectionEncryptedDTO: + type: object + description: "Schema relativo al token" + properties: + contentType: + type: string + example: application/x-www-form-urlencoded + description: "Content-Type della richiesta" + pattern: "^[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+$" + maxLength: 100 + pathAdditionalProperties: + type: object + description: "path additional properties information" + additionalProperties: + type: string + maxLength: 1000000 + pattern: ".*" + example: + tenantId: "V0keQukASwyR0Gx4QRwzn00ZiZW9EqKKq3BLLZuraIygG2L4n0JB/VYQRTlsWzqMnyNu3H/yCnccTLEBw1seXoXWH7C5TmnKyenoN/iewKo339tdCOkEUidjc+mXJiAJAlIGA32Qks2p0x6A3TxUR+UJv0jWrwNhUWJbCcyFdH5r3X0HiYlL1q3CCFfYoNzH+VddQUXKhPnsUd8y3+JWPfn5oP2E/oBDny8naYM+MexO2/zDaE8Aijl8/AEk2jbpopc6Us3ag4SibV3b5NZdjueWyMYItODKBCespt44bMM/DOrIQ==" + bodyAdditionalProperties: + type: object + description: "body additional properties information" + additionalProperties: + type: string + maxLength: 1000000 + pattern: ".*" + example: + client_id: "V0keQukASwyR0Gx4QRwzn00f1X4QL/topNhN+j3uJNp0R/Rpaj/Y4vGJC0MzZiZW9EqKKq3BLUd8y3+JWPfn5oP2E/oBDny8naYM+MexO2/zDaE8Aijl8/AEk2jbpopc6Us3ag4SibV3b5NZdjueWyMYItODKBCespt44bMM/DOrIQ==" + grant_type: "LnJy3ySSrK80h3mdNcWIBT2TRH0VX5frdP5ZEdBGgKi7EEkfpOq0jFPz1q/sdODHJszmLQx4YVkVvcUQ9+GsPt4fqPVfIJUZLfyCoqUTb9XUCyiU/c3L/N3GEfklmOH7E9exwezrJEFFU53YpX4g23dKoBjWQS501IDOjHmuMNZaUbHXinAYbuFXSvBHqPsM1SEgqwA70+7GfUdIDHr3cx+jUnKTHYJQ4c9hDU4kyfENfKysuq/7FZkmHOlePVDHn01QUYYjU0RfCiK8mNJ5LqW5VaGUk+rQ==" + client_secret: "wZc87+hoSchbGcR+4MTkFbpU/CJPTKeR+/kboTn1qVvEPyF/kw0hJCZQ4BmX8h+2GHzleaWAy3kCUHfln/pn3J4xlQW3+73J/WYI+lZhiqm34a2qjojnilZrdbv+TwytjVcxjNoO1vK/kwyUpeLC1CiyE/ZPUk0zfv07cAysJZ/yE1LQ16aKyg==" + + TppDTO: type: object required: @@ -1074,6 +1565,8 @@ components: example: "OAUTH2" contact: $ref: '#/components/schemas/Contact' + tokenSection: + $ref: '#/components/schemas/TokenSectionDecryptedDTO' state: type: boolean description: "Status active/inactive" @@ -1131,6 +1624,109 @@ components: maxLength: 30 pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + TppDTOWithDatesWithoutTokenSection: + allOf: + - $ref: '#/components/schemas/TppDTOWithoutTokenSection' + - type: object + description: "TPP information with date fields" + properties: + creationDate: + type: string + format: date-time + description: "The date and time when the TPP was created" + example: "2023-10-01T12:00:40.695Z" + maxLength: 30 + pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + lastUpdateDate: + type: string + format: date-time + description: "The date and time when the TPP was last updated" + example: "2023-10-15T08:30:40.695Z" + maxLength: 30 + pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + + TppDTOWithoutTokenSection: + type: object + required: + - tppId + - entityId + - businessName + - legalAddress + - messageUrl + - authenticationUrl + - authenticationType + - contact + description: "TPP information(Third Party Provider)" + properties: + tppId: + type: string + description: "Unique ID that identify TPP on PagoPA systems" + pattern: "^[ -~]{1,50}$" + minLength: 1 + maxLength: 50 + example: "0e3bee29-8753-447c-b0da-1f7965558ec2-1706867960900" + entityId: + type: string + description: "Fiscal Code or P.IVA of the TPP" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + example: "86363574890" + idPsp: + type: string + description: "Id of Payment Service Provider" + pattern: "^[A-Za-z0-9 ]+$" + minLength: 1 + maxLength: 50 + example: "MasterCard 123" + businessName: + type: string + description: "Company Name" + pattern: "^[ -~]{1,70}$" + minLength: 1 + maxLength: 70 + example: "MyBusiness Ltd" + legalAddress: + type: string + description: "Company Legal Address" + pattern: "^[ -~]{1,70}$" + minLength: 1 + maxLength: 70 + example: "Via Washington, 13" + messageUrl: + type: string + description: "URL to notify PUSH message" + format: uri + pattern: "^[ -~]{1,2048}$" + minLength: 1 + maxLength: 2048 + example: "https://api.tpp.com/message" + authenticationUrl: + type: string + description: "url for authentication" + format: uri + pattern: "^[ -~]{1,2048}$" + minLength: 1 + maxLength: 2048 + example: "https://api.tpp.com/auth" + authenticationType: + type: string + description: "authentication type" + enum: + - OAUTH2 + example: "OAUTH2" + contact: + $ref: '#/components/schemas/Contact' + state: + type: boolean + description: "Status active/inactive" + example: true + + + + + + TPPErrorDTO: type: object required: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ad8085a..a45723e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -38,3 +38,8 @@ management: exposure.include: "*" web: exposure.include: info, health + +crypto: + azure: + key-vault: + url: ${AZURE_KEYVAULT_URL:https://cstar-d-weu-mil-kv.vault.azure.net} diff --git a/src/test/java/it/gov/pagopa/tpp/controller/TppControllerTest.java b/src/test/java/it/gov/pagopa/tpp/controller/TppControllerTest.java index d53b2cd..48f5bb8 100644 --- a/src/test/java/it/gov/pagopa/tpp/controller/TppControllerTest.java +++ b/src/test/java/it/gov/pagopa/tpp/controller/TppControllerTest.java @@ -1,7 +1,9 @@ package it.gov.pagopa.tpp.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.tpp.dto.TokenSectionDTO; import it.gov.pagopa.tpp.dto.TppDTO; +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; import it.gov.pagopa.tpp.service.TppServiceImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -32,21 +34,40 @@ class TppControllerTest { @Test - void update_Ok() { + void updateTppDetails_Ok() { - Mockito.when(tppService.updateExistingTpp(MOCK_TPP_DTO)).thenReturn(Mono.just(MOCK_TPP_DTO)); + Mockito.when(tppService.updateTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)).thenReturn(Mono.just(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)); webClient.put() .uri("/emd/tpp/update") .contentType(MediaType.APPLICATION_JSON) - .bodyValue(MOCK_TPP_DTO) + .bodyValue(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION) .exchange() .expectStatus().isOk() - .expectBody(TppDTO.class) + .expectBody(TppDTOWithoutTokenSection.class) .consumeWith(response -> { - TppDTO resultResponse = response.getResponseBody(); + TppDTOWithoutTokenSection resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(MOCK_TPP_DTO, resultResponse); + Assertions.assertEquals(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION, resultResponse); + }); + } + + @Test + void updateTokenSection_Ok() { + + Mockito.when(tppService.updateTokenSection("tppId", MOCK_TOKEN_SECTION_DTO)).thenReturn(Mono.just(MOCK_TOKEN_SECTION_DTO)); + + webClient.put() + .uri("/emd/tpp/update/{tppId}/token", "tppId" ) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(MOCK_TOKEN_SECTION_DTO) + .exchange() + .expectStatus().isOk() + .expectBody(TokenSectionDTO.class) + .consumeWith(response -> { + TokenSectionDTO resultResponse = response.getResponseBody(); + Assertions.assertNotNull(resultResponse); + Assertions.assertEquals(MOCK_TOKEN_SECTION_DTO, resultResponse); }); } @@ -89,19 +110,36 @@ void stateUpdate_Ok() { } @Test - void get_Ok() { - Mockito.when(tppService.get(MOCK_TPP_DTO.getTppId())) - .thenReturn(Mono.just(MOCK_TPP_DTO)); + void getTppDetails_Ok() { + Mockito.when(tppService.getTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId())) + .thenReturn(Mono.just(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)); webClient.get() - .uri("/emd/tpp/{tppId}",MOCK_TPP_DTO.getTppId()) + .uri("/emd/tpp/{tppId}",MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId()) .exchange() .expectStatus().isOk() - .expectBody(TppDTO.class) + .expectBody(TppDTOWithoutTokenSection.class) .consumeWith(response -> { - TppDTO resultResponse = response.getResponseBody(); + TppDTOWithoutTokenSection resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(MOCK_TPP_DTO,resultResponse); + Assertions.assertEquals(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION,resultResponse); + }); + } + + @Test + void getTokenSection_Ok() { + Mockito.when(tppService.getTokenSection("tppId")) + .thenReturn(Mono.just(MOCK_TOKEN_SECTION_DTO)); + + webClient.get() + .uri("/emd/tpp/{tppId}/token", "tppId") + .exchange() + .expectStatus().isOk() + .expectBody(TokenSectionDTO.class) + .consumeWith(response -> { + TokenSectionDTO resultResponse = response.getResponseBody(); + Assertions.assertNotNull(resultResponse); + Assertions.assertEquals(MOCK_TOKEN_SECTION_DTO,resultResponse); }); } diff --git a/src/test/java/it/gov/pagopa/tpp/service/AzureEncryptServiceTest.java b/src/test/java/it/gov/pagopa/tpp/service/AzureEncryptServiceTest.java new file mode 100644 index 0000000..b145530 --- /dev/null +++ b/src/test/java/it/gov/pagopa/tpp/service/AzureEncryptServiceTest.java @@ -0,0 +1,103 @@ +package it.gov.pagopa.tpp.service; + +import com.azure.security.keyvault.keys.KeyClient; +import com.azure.security.keyvault.keys.cryptography.CryptographyClient; +import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm; +import com.azure.security.keyvault.keys.models.CreateRsaKeyOptions; +import com.azure.security.keyvault.keys.models.KeyVaultKey; +import it.gov.pagopa.tpp.service.keyvault.AzureEncryptService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.*; + +@SpringBootTest +@TestPropertySource(properties = "crypto.azure.key-vault.url=https://mock-vault-url") +class AzureEncryptServiceTest { + + @Mock + private KeyClient mockKeyClient; + + @Mock + private KeyVaultKey mockKeyVaultKey; + @Mock + private CryptographyClient mockCryptographyClient; + + @Autowired + private AzureEncryptService azureEncryptService; + + @BeforeEach + void setUp() { + azureEncryptService.setKeyClient(mockKeyClient); + } + + @Test + void testGetKey() { + when(mockKeyClient.getKey("tppId")).thenReturn(mock(KeyVaultKey.class)); + + KeyVaultKey key = azureEncryptService.getKey("tppId"); + assertNotNull(key); + } + + @Test + void testCreateRsaKey() { + when(mockKeyClient.createRsaKey(any(CreateRsaKeyOptions.class))).thenReturn(mock(KeyVaultKey.class)); + + KeyVaultKey key = azureEncryptService.createRsaKey("tppId"); + assertNotNull(key); + } + + @Test + void testEncrypt() { + byte[] plainText = "test message".getBytes(); + EncryptionAlgorithm algorithm = EncryptionAlgorithm.RSA_OAEP; + + byte[] encryptedBytes = "encrypted message".getBytes(); + when(mockCryptographyClient.encrypt(algorithm, plainText)) + .thenReturn(new com.azure.security.keyvault.keys.cryptography.models.EncryptResult(encryptedBytes, algorithm, "key")); + + String encrypted = azureEncryptService.encrypt(plainText, algorithm, mockCryptographyClient); + assertNotNull(encrypted); + assertEquals(Base64.getEncoder().encodeToString(encryptedBytes), encrypted); + } + + @Test + void testDecrypt() { + String encryptedValue = Base64.getEncoder().encodeToString("encrypted message".getBytes()); + EncryptionAlgorithm algorithm = EncryptionAlgorithm.RSA_OAEP; + + byte[] decryptedBytes = "test message".getBytes(); + when(mockCryptographyClient.decrypt(algorithm, Base64.getDecoder().decode(encryptedValue))) + .thenReturn(new com.azure.security.keyvault.keys.cryptography.models.DecryptResult(decryptedBytes, algorithm, "key")); + + String decrypted = azureEncryptService.decrypt(encryptedValue, algorithm, mockCryptographyClient); + assertNotNull(decrypted); + assertEquals(new String(decryptedBytes), decrypted); + } + + @Test + void testBuildCryptographyClient_WithString() { + String keyId = "https://test.vault.azure.net/keys/myKey/test"; + + CryptographyClient client1 = azureEncryptService.buildCryptographyClient(keyId); + assertNotNull(client1); + } + + @Test + void testBuildCryptographyClient_WithKeyClient() { + String keyId = "https://test.vault.azure.net/keys/myKey/test"; + + when(mockKeyVaultKey.getId()).thenReturn(keyId); + + CryptographyClient client2 = azureEncryptService.buildCryptographyClient(mockKeyVaultKey); + assertNotNull(client2); + } +} diff --git a/src/test/java/it/gov/pagopa/tpp/service/TppServiceTest.java b/src/test/java/it/gov/pagopa/tpp/service/TppServiceTest.java index 94afd54..2aa17bd 100644 --- a/src/test/java/it/gov/pagopa/tpp/service/TppServiceTest.java +++ b/src/test/java/it/gov/pagopa/tpp/service/TppServiceTest.java @@ -2,9 +2,13 @@ import it.gov.pagopa.common.web.exception.ClientExceptionWithBody; import it.gov.pagopa.tpp.configuration.ExceptionMap; +import it.gov.pagopa.tpp.dto.mapper.TokenSectionObjectToDTOMapper; import it.gov.pagopa.tpp.dto.mapper.TppObjectToDTOMapper; +import it.gov.pagopa.tpp.dto.mapper.TppWithoutTokenSectionObjectToDTOMapper; +import it.gov.pagopa.tpp.model.mapper.TokenSectionDTOToObjectMapper; import it.gov.pagopa.tpp.model.mapper.TppDTOToObjectMapper; import it.gov.pagopa.tpp.repository.TppRepository; +import it.gov.pagopa.tpp.service.keyvault.AzureEncryptService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; @@ -17,12 +21,17 @@ import reactor.test.StepVerifier; import static it.gov.pagopa.tpp.utils.TestUtils.*; +import static org.mockito.ArgumentMatchers.any; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { TppServiceImpl.class, TppObjectToDTOMapper.class, TppDTOToObjectMapper.class, + TokenSectionObjectToDTOMapper.class, + TokenSectionDTOToObjectMapper.class, + TppWithoutTokenSectionObjectToDTOMapper.class, + AzureEncryptService.class, ExceptionMap.class }) class TppServiceTest { @@ -33,13 +42,24 @@ class TppServiceTest { @MockBean private TppRepository tppRepository; + @MockBean + private AzureEncryptService azureEncryptService; + @Autowired private TppDTOToObjectMapper mapperToObject; + @Autowired + private TokenSectionObjectToDTOMapper tokenSectionObjectToDTOMapper; + + @Autowired + private TokenSectionDTOToObjectMapper tokenSectionDTOToObjectMapper; + + @Test void getEnabled_Ok() { Mockito.when(tppRepository.findByTppIdInAndStateTrue(MOCK_TPP_ID_STRING_LIST)) .thenReturn(Flux.fromIterable(MOCK_TPP_LIST)); + Mockito.when(azureEncryptService.decrypt(any(), any(), any())).thenReturn("test"); StepVerifier.create(tppService.getEnabledList(MOCK_TPP_ID_STRING_LIST)) .expectNextMatches(response -> response.equals(MOCK_TPP_DTO_LIST)) @@ -48,7 +68,7 @@ void getEnabled_Ok() { @Test void createTpp_AlreadyExist() { - Mockito.when(tppRepository.findByEntityId(Mockito.any())) + Mockito.when(tppRepository.findByEntityId(any())) .thenReturn(Mono.just(MOCK_TPP)); StepVerifier.create(tppService.createNewTpp(MOCK_TPP_DTO, MOCK_WRONG_ID)) @@ -60,10 +80,11 @@ void createTpp_AlreadyExist() { @Test void createTpp_Ok() { - Mockito.when(tppRepository.findByEntityId(Mockito.any())) + Mockito.when(tppRepository.findByEntityId(any())) .thenReturn(Mono.empty()); - Mockito.when(tppRepository.save(Mockito.any())) + Mockito.when(tppRepository.save(any())) .thenReturn(Mono.just(MOCK_TPP)); + Mockito.when(azureEncryptService.encrypt(any(), any(), any())).thenReturn("test"); StepVerifier.create(tppService.createNewTpp(MOCK_TPP_DTO, MOCK_TPP_DTO.getTppId())) .expectNextMatches(response -> { @@ -75,34 +96,37 @@ void createTpp_Ok() { @Test void createTpp_MissingTokenSection() { + Mockito.when(tppRepository.findByEntityId(MOCK_TPP_DTO_NO_TOKEN_SECTION.getEntityId())) + .thenReturn(Mono.empty()); + StepVerifier.create(tppService.createNewTpp(MOCK_TPP_DTO_NO_TOKEN_SECTION, MOCK_TPP_DTO.getTppId())) .expectErrorMatches(throwable -> throwable instanceof RuntimeException) .verify(); } @Test - void updateTpp_Ok() { - Mockito.when(tppRepository.findByTppId(Mockito.any())) + void updateTppDetails_Ok() { + Mockito.when(tppRepository.findByTppId(any())) .thenReturn(Mono.just(MOCK_TPP)); Mockito.when(tppRepository.save(Mockito.any())) .thenReturn(Mono.just(MOCK_TPP)); - StepVerifier.create(tppService.updateExistingTpp(MOCK_TPP_DTO)) + StepVerifier.create(tppService.updateTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)) .expectNextMatches(response -> { - response.setLastUpdateDate(null); // Normalize data for comparison - return response.equals(MOCK_TPP_DTO); + response.setLastUpdateDate(null); + return response.equals(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION); }) .verifyComplete(); } @Test - void updateTpp_TppNotFound() { + void updateTppDetails_TppNotFound() { Mockito.when(tppRepository.findByTppId(Mockito.any())) .thenReturn(Mono.just(MOCK_TPP)); Mockito.when(tppRepository.save(Mockito.any())) .thenReturn(Mono.empty()); - StepVerifier.create(tppService.updateExistingTpp(MOCK_TPP_DTO)) + StepVerifier.create(tppService.updateTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)) .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && ((ClientExceptionWithBody) throwable).getCode().equals("TPP_NOT_ONBOARDED")) @@ -110,18 +134,50 @@ void updateTpp_TppNotFound() { } @Test - void updateTpp_NoTppId() { - StepVerifier.create(tppService.updateExistingTpp(MOCK_TPP_DTO_NO_ID)) + void updateTppDetails_NoTppId() { + StepVerifier.create(tppService.updateTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION_NO_ID)) .expectErrorMatches(throwable -> throwable instanceof RuntimeException) .verify(); } @Test - void updateState_Ok() { + void updateTokenSection_Ok() { Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) .thenReturn(Mono.just(MOCK_TPP)); Mockito.when(tppRepository.save(Mockito.any())) .thenReturn(Mono.just(MOCK_TPP)); + Mockito.when(azureEncryptService.encrypt(any(), any(), any())).thenReturn("test"); + + StepVerifier.create(tppService.updateTokenSection(MOCK_TPP_DTO.getTppId(), MOCK_TOKEN_SECTION_DTO)) + .expectNextMatches(result -> result.equals(MOCK_TOKEN_SECTION_DTO)) + .verifyComplete(); + } + + @Test + void updateTokenSection_NoTppId() { + StepVerifier.create(tppService.updateTokenSection(null, MOCK_TOKEN_SECTION_DTO)) + .expectErrorMatches(throwable -> throwable instanceof RuntimeException) + .verify(); + } + + @Test + void updateTokenSection_TppNotFound() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + .thenReturn(Mono.empty()); + + StepVerifier.create(tppService.updateTokenSection(MOCK_TPP_DTO.getTppId(), MOCK_TOKEN_SECTION_DTO)) + .expectErrorMatches(throwable -> + throwable instanceof ClientExceptionWithBody && + ((ClientExceptionWithBody) throwable).getCode().equals("TPP_NOT_ONBOARDED")) + .verify(); + } + + @Test + void updateState_Ok() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + .thenReturn(Mono.just(MOCK_TPP)); + Mockito.when(tppRepository.save(any())) + .thenReturn(Mono.just(MOCK_TPP)); StepVerifier.create(tppService.updateState(MOCK_TPP_DTO.getTppId(), MOCK_TPP_DTO.getState())) .expectNextMatches(result -> result.equals(MOCK_TPP_DTO)) @@ -141,26 +197,49 @@ void updateState_TppNotOnboarded() { } @Test - void get_Ok() { - Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + void getTppDetails_Ok() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId())) .thenReturn(Mono.just(MOCK_TPP)); - StepVerifier.create(tppService.get(MOCK_TPP_DTO.getTppId())) - .expectNextMatches(result -> result.equals(MOCK_TPP_DTO)) + StepVerifier.create(tppService.getTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId())) + .expectNextMatches(result -> result.equals(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION)) .verifyComplete(); } @Test - void get_TppNotOnboarded() { - Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + void getTppDetails_TppNotOnboarded() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId())) .thenReturn(Mono.empty()); - StepVerifier.create(tppService.get(MOCK_TPP_DTO.getTppId())) + StepVerifier.create(tppService.getTppDetails(MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION.getTppId())) .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && ((ClientExceptionWithBody) throwable).getCode().equals("TPP_NOT_ONBOARDED")) .verify(); } + + @Test + void getTokenSection_Ok() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + .thenReturn(Mono.just(MOCK_TPP)); + Mockito.when(azureEncryptService.decrypt(any(), any(), any())).thenReturn("test"); + + StepVerifier.create(tppService.getTokenSection(MOCK_TPP_DTO.getTppId())) + .expectNextMatches(result -> result.equals(MOCK_TOKEN_SECTION_DTO)) + .verifyComplete(); + } + + @Test + void getTokenSection_TppNotFound() { + Mockito.when(tppRepository.findByTppId(MOCK_TPP_DTO.getTppId())) + .thenReturn(Mono.empty()); + + StepVerifier.create(tppService.getTokenSection(MOCK_TPP_DTO.getTppId())) + .expectErrorMatches(throwable -> + throwable instanceof RuntimeException && + throwable.getMessage().contains("Tpp not found during get process")) + .verify(); + } } diff --git a/src/test/java/it/gov/pagopa/tpp/utils/TestUtils.java b/src/test/java/it/gov/pagopa/tpp/utils/TestUtils.java index 4e5b8eb..e24a94d 100644 --- a/src/test/java/it/gov/pagopa/tpp/utils/TestUtils.java +++ b/src/test/java/it/gov/pagopa/tpp/utils/TestUtils.java @@ -1,8 +1,12 @@ package it.gov.pagopa.tpp.utils; +import it.gov.pagopa.tpp.dto.TokenSectionDTO; import it.gov.pagopa.tpp.dto.TppDTO; +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; import it.gov.pagopa.tpp.dto.TppIdList; +import it.gov.pagopa.tpp.utils.faker.TokenSectionDTOFaker; import it.gov.pagopa.tpp.utils.faker.TppDTOFaker; +import it.gov.pagopa.tpp.utils.faker.TppDTOWithoutTokenSectionFaker; import it.gov.pagopa.tpp.utils.faker.TppFaker; import it.gov.pagopa.tpp.model.Tpp; @@ -10,11 +14,11 @@ public class TestUtils { - public TestUtils (){} - public static final TppDTO MOCK_TPP_DTO = TppDTOFaker.mockInstance(true); - public static final TppDTO MOCK_TPP_DTO_NO_ID = TppDTOFaker.mockInstanceWithNoTppId(true); public static final TppDTO MOCK_TPP_DTO_NO_TOKEN_SECTION = TppDTOFaker.mockInstanceWithNoTokenSection(true); + public static final TppDTOWithoutTokenSection MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION = TppDTOWithoutTokenSectionFaker.mockInstance(true); + public static final TppDTOWithoutTokenSection MOCK_TPP_DTO_WITHOUT_TOKEN_SECTION_NO_ID = TppDTOWithoutTokenSectionFaker.mockInstanceWithNoTppId(true); + public static final TokenSectionDTO MOCK_TOKEN_SECTION_DTO = TokenSectionDTOFaker.mockInstance(); public static final Tpp MOCK_TPP = TppFaker.mockInstance(true); public static final List MOCK_TPP_DTO_LIST = List.of(MOCK_TPP_DTO); public static final List MOCK_TPP_LIST = List.of(TppFaker.mockInstance(true)); diff --git a/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionDTOFaker.java b/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionDTOFaker.java new file mode 100644 index 0000000..7add7ca --- /dev/null +++ b/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionDTOFaker.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.tpp.utils.faker; + +import it.gov.pagopa.tpp.dto.TokenSectionDTO; + +import java.util.HashMap; + +public class TokenSectionDTOFaker { + + private TokenSectionDTOFaker() {} + + public static TokenSectionDTO mockInstance() { + return TokenSectionDTO.builder() + .contentType("application/json") + .pathAdditionalProperties(new HashMap<>() {{ + put("pathKey1", "test"); + }}) + .bodyAdditionalProperties(new HashMap<>() {{ + put("bodyKey1", "test"); + }}) + .build(); + } +} diff --git a/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionFaker.java b/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionFaker.java new file mode 100644 index 0000000..fb5b036 --- /dev/null +++ b/src/test/java/it/gov/pagopa/tpp/utils/faker/TokenSectionFaker.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.tpp.utils.faker; + +import it.gov.pagopa.tpp.model.TokenSection; + +import java.util.HashMap; + +public class TokenSectionFaker { + + private TokenSectionFaker(){} + + public static TokenSection mockInstance() { + return TokenSection.builder() + .contentType("application/json") + .pathAdditionalProperties(new HashMap<>() {{ + put("pathKey1", "test"); + }}) + .bodyAdditionalProperties(new HashMap<>() {{ + put("bodyKey1", "test"); + }}) + .build(); + } +} diff --git a/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOFaker.java b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOFaker.java index 7022ec5..a4b37f7 100644 --- a/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOFaker.java +++ b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOFaker.java @@ -3,9 +3,6 @@ import it.gov.pagopa.tpp.dto.TppDTO; import it.gov.pagopa.tpp.enums.AuthenticationType; import it.gov.pagopa.tpp.model.Contact; -import it.gov.pagopa.tpp.model.TokenSection; - -import java.util.HashMap; public class TppDTOFaker { @@ -13,16 +10,6 @@ private TppDTOFaker(){} public static TppDTO mockInstance(Boolean bias) { - TokenSection tokenSection = new TokenSection( - "application/json", - new HashMap<>() {{ - put("pathKey1", "pathValue1"); - }}, - new HashMap<>() {{ - put("bodyKey1", "bodyValue1"); - }} - ); - Contact contact = new Contact("name","number", "email"); return TppDTO.builder() @@ -38,22 +25,14 @@ public static TppDTO mockInstance(Boolean bias) { .contact(contact) .lastUpdateDate(null) .creationDate(null) - .tokenSection(tokenSection) + .tokenSection(TokenSectionFaker.mockInstance()) .build(); } public static TppDTO mockInstanceWithNoTppId(Boolean bias) { Contact contact = new Contact("name","number", "email"); - TokenSection tokenSection = new TokenSection( - "application/json", - new HashMap<>() {{ - put("pathKey1", "pathValue1"); - }}, - new HashMap<>() {{ - put("bodyKey1", "bodyValue1"); - }} - ); + return TppDTO.builder() .tppId(null) .messageUrl("https://wwwmessageUrl.it") @@ -67,7 +46,7 @@ public static TppDTO mockInstanceWithNoTppId(Boolean bias) { .contact(contact) .lastUpdateDate(null) .creationDate(null) - .tokenSection(tokenSection) + .tokenSection(TokenSectionFaker.mockInstance()) .build(); } @@ -91,4 +70,5 @@ public static TppDTO mockInstanceWithNoTokenSection(Boolean bias) { .tokenSection(null) .build(); } + } diff --git a/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOWithoutTokenSectionFaker.java b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOWithoutTokenSectionFaker.java new file mode 100644 index 0000000..994a406 --- /dev/null +++ b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppDTOWithoutTokenSectionFaker.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.tpp.utils.faker; + +import it.gov.pagopa.tpp.dto.TppDTOWithoutTokenSection; +import it.gov.pagopa.tpp.enums.AuthenticationType; +import it.gov.pagopa.tpp.model.Contact; + +public class TppDTOWithoutTokenSectionFaker { + + private TppDTOWithoutTokenSectionFaker(){} + + public static TppDTOWithoutTokenSection mockInstance(Boolean bias) { + + Contact contact = new Contact("name","number", "email"); + + return TppDTOWithoutTokenSection.builder() + .tppId("tppId") + .messageUrl("https://wwwmessageUrl.it") + .authenticationUrl("https://www.AuthenticationUrl.it") + .idPsp("idPsp") + .legalAddress("legalAddress") + .authenticationType(AuthenticationType.OAUTH2) + .state(bias) + .entityId("entityId01234567") + .businessName("businessName") + .contact(contact) + .lastUpdateDate(null) + .creationDate(null) + .build(); + } + + public static TppDTOWithoutTokenSection mockInstanceWithNoTppId(Boolean bias) { + + Contact contact = new Contact("name","number", "email"); + + return TppDTOWithoutTokenSection.builder() + .tppId(null) + .messageUrl("https://wwwmessageUrl.it") + .authenticationUrl("https://www.AuthenticationUrl.it") + .idPsp("idPsp") + .legalAddress("legalAddress") + .authenticationType(AuthenticationType.OAUTH2) + .state(bias) + .entityId("entityId01234567") + .businessName("businessName") + .contact(contact) + .lastUpdateDate(null) + .creationDate(null) + .build(); + } + +} diff --git a/src/test/java/it/gov/pagopa/tpp/utils/faker/TppFaker.java b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppFaker.java index 4596dcb..63eea6f 100644 --- a/src/test/java/it/gov/pagopa/tpp/utils/faker/TppFaker.java +++ b/src/test/java/it/gov/pagopa/tpp/utils/faker/TppFaker.java @@ -2,26 +2,13 @@ import it.gov.pagopa.tpp.enums.AuthenticationType; import it.gov.pagopa.tpp.model.Contact; -import it.gov.pagopa.tpp.model.TokenSection; import it.gov.pagopa.tpp.model.Tpp; -import java.util.HashMap; - public class TppFaker { private TppFaker(){} public static Tpp mockInstance(Boolean bias){ - TokenSection tokenSection = new TokenSection( - "application/json", - new HashMap<>() {{ - put("pathKey1", "pathValue1"); - }}, - new HashMap<>() {{ - put("bodyKey1", "bodyValue1"); - }} - ); - Contact contact = new Contact("name","number", "email"); return Tpp.builder() @@ -38,7 +25,7 @@ public static Tpp mockInstance(Boolean bias){ .contact(contact) .lastUpdateDate(null) .creationDate(null) - .tokenSection(tokenSection) + .tokenSection(TokenSectionFaker.mockInstance()) .build(); } }