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();
}
}