From 31c7d7f88abff203b0cb8b853ec407c9d324e8cd Mon Sep 17 00:00:00 2001 From: macacia Date: Mon, 7 Oct 2024 11:45:41 +0200 Subject: [PATCH] feat: P4ADEV-791-P4PA-AUTH-API-censimento-client-id (#80) --- helm/values.yaml | 1 + openapi/p4pa-auth.openapi.yaml | 86 ++++++++++++++- .../auth/controller/AuthzControllerImpl.java | 13 ++- .../payhub/auth/mapper/ClientMapper.java | 35 ++++++ .../gov/pagopa/payhub/auth/model/Client.java | 24 ++++ .../auth/repository/ClientRepository.java | 8 ++ .../payhub/auth/service/AuthzService.java | 5 +- .../payhub/auth/service/AuthzServiceImpl.java | 18 ++- .../auth/service/DataCipherService.java | 40 ++++++- .../auth/service/a2a/ClientService.java | 8 ++ .../auth/service/a2a/ClientServiceImpl.java | 28 +++++ .../ClientRegistrationService.java | 37 +++++++ .../pagopa/payhub/auth/utils/AESUtils.java | 103 ++++++++++++++++++ src/main/resources/application.yml | 3 +- ...ntrollerNoOrganizzationAccessModeTest.java | 87 +++++++++++++-- .../payhub/auth/mapper/ClientMapperTest.java | 93 ++++++++++++++++ .../payhub/auth/service/AuthzServiceTest.java | 36 ++++-- .../auth/service/DataCipherServiceTest.java | 29 ++++- .../auth/service/a2a/ClientServiceTest.java | 56 ++++++++++ .../ClientRegistrationServiceTest.java | 54 +++++++++ .../ExternalUserIdObfuscatorServiceTest.java | 5 +- .../payhub/auth/utils/AESUtilsTest.java | 20 ++++ 22 files changed, 751 insertions(+), 38 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/mapper/ClientMapper.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/model/Client.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/repository/ClientRepository.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientService.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceImpl.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationService.java create mode 100644 src/main/java/it/gov/pagopa/payhub/auth/utils/AESUtils.java create mode 100644 src/test/java/it/gov/pagopa/payhub/auth/mapper/ClientMapperTest.java create mode 100644 src/test/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceTest.java create mode 100644 src/test/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationServiceTest.java create mode 100644 src/test/java/it/gov/pagopa/payhub/auth/utils/AESUtilsTest.java diff --git a/helm/values.yaml b/helm/values.yaml index c071f3e8..a6c52c46 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -87,6 +87,7 @@ microservice-chart: MYPIVOT_DB_PASSWORD: db-mypay-login-password DATA_CIPHER_P4PA_AUTH_HASH_KEY: p4pa-auth-hash-key + DATA_CIPHER_P4PA_AUTH_ENCRYPT_PSW: p4pa-auth-encrypt-psw # nodeSelector: {} # tolerations: [] diff --git a/openapi/p4pa-auth.openapi.yaml b/openapi/p4pa-auth.openapi.yaml index 07bb3c67..9f0a41e0 100644 --- a/openapi/p4pa-auth.openapi.yaml +++ b/openapi/p4pa-auth.openapi.yaml @@ -26,7 +26,8 @@ paths: required: true schema: enum: [ - "urn:ietf:params:oauth:grant-type:token-exchange" + "urn:ietf:params:oauth:grant-type:token-exchange", + "client_credentials" ] type: string - name: subject_token @@ -154,6 +155,66 @@ paths: description: Invalid request '401': description: Invalid client_id + /auth/clients/{organizationIpaCode}: + get: + tags: + - authz + operationId: getClients + parameters: + - name: organizationIpaCode + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ClientDTO' + '400': + description: Invalid request + '401': + description: Unauthorized + '403': + description: Forbidden + '412': + description: ToS acceptance missing + '429': + description: Too Many Requests + post: + tags: + - authz + operationId: registerClient + parameters: + - name: organizationIpaCode + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateClientRequest' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + $ref: '#/components/schemas/ClientDTO' + '400': + description: Invalid request + '401': + description: Unauthorized + '403': + description: Forbidden + '429': + description: Too Many Requests /am/users: post: tags: @@ -351,6 +412,29 @@ components: default: Bearer expires_in: type: integer + ClientDTO: + type: object + required: + - clientId + - clientName + - organizationIpaCode + - clientSecret + properties: + clientId: + type: string + clientName: + type: string + organizationIpaCode: + type: string + clientSecret: + type: string + CreateClientRequest: + type: object + required: + - clientName + properties: + clientName: + type: string UserDTO: type: object required: diff --git a/src/main/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerImpl.java index 93157227..48fa4b1b 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerImpl.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerImpl.java @@ -4,10 +4,7 @@ import it.gov.pagopa.payhub.auth.service.AuthzService; import it.gov.pagopa.payhub.auth.utils.SecurityUtils; import it.gov.pagopa.payhub.controller.generated.AuthzApi; -import it.gov.pagopa.payhub.model.generated.CreateOperatorRequest; -import it.gov.pagopa.payhub.model.generated.OperatorDTO; -import it.gov.pagopa.payhub.model.generated.OperatorsPage; -import it.gov.pagopa.payhub.model.generated.UserDTO; +import it.gov.pagopa.payhub.model.generated.*; import it.gov.pagopa.payhub.model.generated.UserInfo; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -99,4 +96,12 @@ public ResponseEntity createUser(UserDTO user) { } return ResponseEntity.ok(authzService.createUser(user)); } + + @Override + public ResponseEntity registerClient(String organizationIpaCode, CreateClientRequest createClientRequest) { + if(!SecurityUtils.isPrincipalAdmin(organizationIpaCode)){ + throw new UserUnauthorizedException("User not allowed to create client"); + } + return ResponseEntity.ok(authzService.registerClient(organizationIpaCode, createClientRequest)); + } } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/mapper/ClientMapper.java b/src/main/java/it/gov/pagopa/payhub/auth/mapper/ClientMapper.java new file mode 100644 index 00000000..fd0d085b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/mapper/ClientMapper.java @@ -0,0 +1,35 @@ +package it.gov.pagopa.payhub.auth.mapper; + +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.service.DataCipherService; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import org.springframework.stereotype.Service; + +@Service +public class ClientMapper { + + private final DataCipherService dataCipherService; + + public ClientMapper(DataCipherService dataCipherService) { + this.dataCipherService = dataCipherService; + } + + public ClientDTO mapToDTO(Client client) { + return ClientDTO.builder() + .clientId(client.getClientId()) + .clientName(client.getClientName()) + .organizationIpaCode(client.getOrganizationIpaCode()) + .clientSecret(dataCipherService.decrypt(client.getClientSecret())) + .build(); + } + + public Client mapToModel(ClientDTO clientDTO) { + return Client.builder() + .clientId(clientDTO.getClientId()) + .clientName(clientDTO.getClientName()) + .organizationIpaCode(clientDTO.getOrganizationIpaCode()) + .clientSecret(dataCipherService.encrypt(clientDTO.getClientSecret())) + .build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/model/Client.java b/src/main/java/it/gov/pagopa/payhub/auth/model/Client.java new file mode 100644 index 00000000..29ddd48e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/model/Client.java @@ -0,0 +1,24 @@ +package it.gov.pagopa.payhub.auth.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@Document("clients") +@NoArgsConstructor +@AllArgsConstructor +@Builder +@FieldNameConstants +public class Client { + + @Id + private String clientId; + private String clientName; + private String organizationIpaCode; + private byte[] clientSecret; +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/repository/ClientRepository.java b/src/main/java/it/gov/pagopa/payhub/auth/repository/ClientRepository.java new file mode 100644 index 00000000..1447ce41 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/repository/ClientRepository.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payhub.auth.repository; + +import it.gov.pagopa.payhub.auth.model.Client; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ClientRepository extends MongoRepository { + +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzService.java index dacf48b4..3058a86b 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzService.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzService.java @@ -1,8 +1,6 @@ package it.gov.pagopa.payhub.auth.service; -import it.gov.pagopa.payhub.model.generated.CreateOperatorRequest; -import it.gov.pagopa.payhub.model.generated.OperatorDTO; -import it.gov.pagopa.payhub.model.generated.UserDTO; +import it.gov.pagopa.payhub.model.generated.*; import it.gov.pagopa.payhub.model.generated.UserInfo; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,4 +13,5 @@ public interface AuthzService { OperatorDTO createOrganizationOperator(String organizationIpaCode, CreateOperatorRequest createOperatorRequest); UserDTO createUser(UserDTO user); UserInfo getUserInfoFromMappedExternalUserId(String mappedExternalUserId); + ClientDTO registerClient(String organizationIpaCode, CreateClientRequest createClientRequest); } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzServiceImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzServiceImpl.java index ac535bbd..00ef7d8e 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/AuthzServiceImpl.java @@ -6,13 +6,12 @@ import it.gov.pagopa.payhub.auth.model.User; import it.gov.pagopa.payhub.auth.repository.OperatorsRepository; import it.gov.pagopa.payhub.auth.repository.UsersRepository; +import it.gov.pagopa.payhub.auth.service.a2a.ClientService; import it.gov.pagopa.payhub.auth.service.user.UserService; import it.gov.pagopa.payhub.auth.service.user.retrieve.Operator2UserInfoMapper; import it.gov.pagopa.payhub.auth.service.user.retrieve.OperatorDTOMapper; import it.gov.pagopa.payhub.auth.service.user.retrieve.UserDTOMapper; -import it.gov.pagopa.payhub.model.generated.CreateOperatorRequest; -import it.gov.pagopa.payhub.model.generated.OperatorDTO; -import it.gov.pagopa.payhub.model.generated.UserDTO; +import it.gov.pagopa.payhub.model.generated.*; import it.gov.pagopa.payhub.model.generated.UserInfo; import jakarta.transaction.Transactional; import java.util.HashSet; @@ -28,6 +27,7 @@ public class AuthzServiceImpl implements AuthzService { private final UserService userService; + private final ClientService clientService; private final UsersRepository usersRepository; private final OperatorsRepository operatorsRepository; private final OperatorDTOMapper operatorDTOMapper; @@ -35,10 +35,11 @@ public class AuthzServiceImpl implements AuthzService { private final Operator2UserInfoMapper operator2UserInfoMapper; private static final String MYPAYIAMISSUERS = "MYPAY"; - public AuthzServiceImpl(UserService userService, UsersRepository usersRepository, - OperatorsRepository operatorsRepository, OperatorDTOMapper operatorDTOMapper, - UserDTOMapper userDTOMapper, Operator2UserInfoMapper operator2UserInfoMapper) { + public AuthzServiceImpl(UserService userService, ClientService clientService, UsersRepository usersRepository, + OperatorsRepository operatorsRepository, OperatorDTOMapper operatorDTOMapper, UserDTOMapper userDTOMapper, + Operator2UserInfoMapper operator2UserInfoMapper) { this.userService = userService; + this.clientService = clientService; this.usersRepository = usersRepository; this.operatorsRepository = operatorsRepository; this.operatorDTOMapper = operatorDTOMapper; @@ -101,4 +102,9 @@ public UserInfo getUserInfoFromMappedExternalUserId(String mappedExternalUserId) List operators = operatorsRepository.findAllByUserId(user.getUserId()); return operator2UserInfoMapper.apply(user, operators); } + + @Override + public ClientDTO registerClient(String organizationIpaCode, CreateClientRequest createClientRequest) { + return clientService.registerClient(createClientRequest.getClientName(), organizationIpaCode); + } } diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/DataCipherService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/DataCipherService.java index e01e1c4b..3c9921c9 100644 --- a/src/main/java/it/gov/pagopa/payhub/auth/service/DataCipherService.java +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/DataCipherService.java @@ -1,5 +1,8 @@ package it.gov.pagopa.payhub.auth.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.payhub.auth.utils.AESUtils; import it.gov.pagopa.payhub.auth.utils.HashAlgorithm; import java.util.Base64; import org.springframework.beans.factory.annotation.Value; @@ -8,10 +11,43 @@ @Service public class DataCipherService { + private final String encryptPsw; private final HashAlgorithm hashAlgorithm; + private final ObjectMapper objectMapper; - public DataCipherService(@Value("${data-cipher.p4pa-auth-hash-key}") String hashPepper) { - hashAlgorithm = new HashAlgorithm("SHA-256", Base64.getUrlDecoder().decode(hashPepper)); + public DataCipherService( + @Value("${p4pa-auth-encrypt-psw}") String encryptPsw, + @Value("${p4pa-auth-hash-pepper}") String hashPepper, + ObjectMapper objectMapper + ) { + this.encryptPsw = encryptPsw; + this.objectMapper = objectMapper; + + hashAlgorithm = new HashAlgorithm("SHA-256", Base64.getDecoder().decode(hashPepper)); + } + + public byte[] encrypt(String plainText){ + return AESUtils.encrypt(encryptPsw, plainText); + } + + public String decrypt(byte[] cipherData){ + return AESUtils.decrypt(encryptPsw, cipherData); + } + + public byte[] encryptObj(T obj){ + try { + return encrypt(objectMapper.writeValueAsString(obj)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Cannot serialize object as JSON", e); + } + } + + public T decryptObj(byte[] cipherData, Class clazz) { + try { + return objectMapper.readValue(decrypt(cipherData), clazz); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Cannot deserialize object as JSON", e); + } } public byte[] hash(String value) { diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientService.java new file mode 100644 index 00000000..f20cac43 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientService.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payhub.auth.service.a2a; + +import it.gov.pagopa.payhub.model.generated.ClientDTO; + +public interface ClientService { + + ClientDTO registerClient(String clientName, String organizationIpaCode); +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceImpl.java b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceImpl.java new file mode 100644 index 00000000..4a0ccf4f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceImpl.java @@ -0,0 +1,28 @@ +package it.gov.pagopa.payhub.auth.service.a2a; + +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.service.a2a.registration.ClientRegistrationService; +import it.gov.pagopa.payhub.auth.mapper.ClientMapper; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class ClientServiceImpl implements ClientService { + + private final ClientRegistrationService clientRegistrationService; + + private final ClientMapper clientMapper; + + public ClientServiceImpl(ClientRegistrationService clientRegistrationService, ClientMapper clientMapper) { + this.clientRegistrationService = clientRegistrationService; + this.clientMapper = clientMapper; + } + + @Override + public ClientDTO registerClient(String clientName, String organizationIpaCode) { + Client client = clientRegistrationService.registerClient(clientName, organizationIpaCode); + return clientMapper.mapToDTO(client); + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationService.java b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationService.java new file mode 100644 index 00000000..eb7aa321 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationService.java @@ -0,0 +1,37 @@ +package it.gov.pagopa.payhub.auth.service.a2a.registration; + +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.repository.ClientRepository; +import it.gov.pagopa.payhub.auth.mapper.ClientMapper; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@Slf4j +public class ClientRegistrationService { + + private final ClientMapper clientMapper; + private final ClientRepository clientRepository; + + public ClientRegistrationService(ClientMapper clientMapper, ClientRepository clientRepository) { + this.clientMapper = clientMapper; + this.clientRepository = clientRepository; + } + + public Client registerClient(String clientName, String organizationIpaCode) { + + Client client = clientMapper.mapToModel( + ClientDTO.builder() + .clientId(organizationIpaCode + clientName) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(UUID.randomUUID().toString()) + .build() + ); + log.info("Registering client having clientName {} and organizationIpaCode {}", clientName, organizationIpaCode); + return clientRepository.insert(client); + } +} diff --git a/src/main/java/it/gov/pagopa/payhub/auth/utils/AESUtils.java b/src/main/java/it/gov/pagopa/payhub/auth/utils/AESUtils.java new file mode 100644 index 00000000..02309fea --- /dev/null +++ b/src/main/java/it/gov/pagopa/payhub/auth/utils/AESUtils.java @@ -0,0 +1,103 @@ +package it.gov.pagopa.payhub.auth.utils; + +import javax.crypto.*; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +public class AESUtils { + private AESUtils() { } + + private static final String ALGORITHM = "AES/GCM/NoPadding"; + private static final String FACTORY_INSTANCE = "PBKDF2WithHmacSHA256"; + private static final int TAG_LENGTH_BIT = 128; + private static final int IV_LENGTH_BYTE = 12; + private static final int SALT_LENGTH_BYTE = 16; + private static final String ALGORITHM_TYPE = "AES"; + private static final int KEY_LENGTH = 256; + private static final int ITERATION_COUNT = 65536; + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + public static byte[] getRandomNonce(int length) { + byte[] nonce = new byte[length]; + new SecureRandom().nextBytes(nonce); + return nonce; + } + + public static SecretKey getSecretKey(String password, byte[] salt) { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); + + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(FACTORY_INSTANCE); + return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM_TYPE); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException("Cannot initialize cryptographic data", e); + } + } + + public static byte[] encrypt(String password, String plainMessage) { + byte[] salt = getRandomNonce(SALT_LENGTH_BYTE); + SecretKey secretKey = getSecretKey(password, salt); + + // GCM recommends 12 bytes iv + byte[] iv = getRandomNonce(IV_LENGTH_BYTE); + Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, secretKey, iv); + + byte[] encryptedMessageByte = executeCipherOp(cipher, plainMessage.getBytes(UTF_8)); + + // prefix IV and Salt to cipher text + return ByteBuffer.allocate(iv.length + salt.length + encryptedMessageByte.length) + .put(iv) + .put(salt) + .put(encryptedMessageByte) + .array(); + } + + public static String decrypt(String password, byte[] cipherMessage) { + ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage); + + byte[] iv = new byte[IV_LENGTH_BYTE]; + byteBuffer.get(iv); + + byte[] salt = new byte[SALT_LENGTH_BYTE]; + byteBuffer.get(salt); + + byte[] encryptedByte = new byte[byteBuffer.remaining()]; + byteBuffer.get(encryptedByte); + + SecretKey secretKey = getSecretKey(password, salt); + Cipher cipher = initCipher(Cipher.DECRYPT_MODE, secretKey, iv); + + byte[] decryptedMessageByte = executeCipherOp(cipher, encryptedByte); + return new String(decryptedMessageByte, UTF_8); + } + + private static byte[] executeCipherOp(Cipher cipher, byte[] encryptedByte) { + try { + return cipher.doFinal(encryptedByte); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new IllegalStateException("Cannot execute cipher op", e); + } + } + + private static Cipher initCipher(int mode, SecretKey secretKey, byte[] iv) { + try { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(mode, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + return cipher; + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException e) { + throw new IllegalStateException("Cannot initialize cipher data", e); + } + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b5581259..a5196dc7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -65,4 +65,5 @@ app: enable-access-organization-mode: "\${ACCESS_ORGANIZATION_MODE_ENABLED:true}" data-chiper: - p4pa-auth-hash-key: "\${DATA_CIPHER_P4PA_AUTH_HASH_KEY:PEPPER}" \ No newline at end of file + p4pa-auth-hash-key: "\${DATA_CIPHER_P4PA_AUTH_HASH_KEY:PEPPER}" + p4pa-auth-encrypt-psw: "\${DATA_CIPHER_P4PA_AUTH_ENCRYPT_PSW:PSW}" diff --git a/src/test/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerNoOrganizzationAccessModeTest.java b/src/test/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerNoOrganizzationAccessModeTest.java index de496f7f..1684a0cb 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerNoOrganizzationAccessModeTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/controller/AuthzControllerNoOrganizzationAccessModeTest.java @@ -1,8 +1,5 @@ package it.gov.pagopa.payhub.auth.controller; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.nimbusds.jose.shaded.gson.Gson; import it.gov.pagopa.payhub.auth.exception.AuthExceptionHandler; import it.gov.pagopa.payhub.auth.security.JwtAuthenticationFilter; @@ -10,11 +7,8 @@ import it.gov.pagopa.payhub.auth.service.AuthnService; import it.gov.pagopa.payhub.auth.service.AuthzService; import it.gov.pagopa.payhub.auth.utils.Constants; -import it.gov.pagopa.payhub.model.generated.CreateOperatorRequest; -import it.gov.pagopa.payhub.model.generated.UserDTO; -import it.gov.pagopa.payhub.model.generated.UserInfo; -import it.gov.pagopa.payhub.model.generated.UserOrganizationRoles; -import java.util.List; +import it.gov.pagopa.payhub.model.generated.*; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -25,11 +19,23 @@ import org.springframework.http.MediaType; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(AuthzControllerImpl.class) @Import({AuthExceptionHandler.class, WebSecurityConfig.class, JwtAuthenticationFilter.class}) @TestPropertySource(properties = { "app.enable-access-organization-mode=false" }) class AuthzControllerNoOrganizzationAccessModeTest { + @Autowired private MockMvc mockMvc; @@ -140,4 +146,69 @@ void givenUnauthorizedUserWhenCreateUserThenOk() throws Exception { ).andExpect(status().isUnauthorized()); } //end region + + @Test + void givenUnauthorizedUserWhenRegisterClientThenUnauthorizedException() throws Exception { + String organizationIpaCode = "IPACODE"; + CreateClientRequest request = new CreateClientRequest(); + request.setClientName("CLIENTNAME"); + Gson gson = new Gson(); + String body = gson.toJson(request); + Mockito.when(authnServiceMock.getUserInfo("accessToken")) + .thenReturn(UserInfo.builder() + .organizations(List.of(UserOrganizationRoles.builder() + .organizationIpaCode("ORG2") + .roles(List.of(Constants.ROLE_OPER)) + .build())) + .build()); + mockMvc.perform( + post("/payhub/auth/clients/{organizationIpaCode}", organizationIpaCode) + .header(HttpHeaders.AUTHORIZATION, "Bearer accessToken") + .contentType(MediaType.APPLICATION_JSON) + .content(String.valueOf((body))) + ).andExpect(status().isUnauthorized()); + } + + @Test + void givenAuthorizedUserWhenRegisterClientThenOk() throws Exception { + String uuidRandomForSecret = UUID.randomUUID().toString(); + String uuidRegex = + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"; + String organizationIpaCode = "IPA_TEST_2"; + CreateClientRequest createClientRequest = new CreateClientRequest(); + createClientRequest.setClientName("CLIENTNAME"); + + UserInfo expectedUser = UserInfo.builder() + .userId("USERID") + .organizationAccess(organizationIpaCode) + .organizations(List.of(UserOrganizationRoles.builder() + .organizationIpaCode(organizationIpaCode) + .roles(List.of(Constants.ROLE_ADMIN)) + .build())) + .build(); + + Mockito.when(authnServiceMock.getUserInfo("accessToken")) + .thenReturn(expectedUser); + + doReturn(new ClientDTO(organizationIpaCode + createClientRequest.getClientName(), createClientRequest.getClientName(), organizationIpaCode, uuidRandomForSecret)) + .when(authzServiceMock).registerClient(organizationIpaCode, createClientRequest); + + MvcResult result = mockMvc.perform( + post("/payhub/auth/clients/{organizationIpaCode}", organizationIpaCode) + .header(HttpHeaders.AUTHORIZATION, "Bearer accessToken") + .contentType(MediaType.APPLICATION_JSON) + .content(new Gson().toJson(createClientRequest)) + ).andExpect(status().isOk()) + .andReturn(); + + ClientDTO clientDTO = new Gson().fromJson(result.getResponse().getContentAsString(), ClientDTO.class); + + Assertions.assertNotNull(result); + assertEquals(organizationIpaCode + createClientRequest.getClientName(), clientDTO.getClientId()); + assertEquals(createClientRequest.getClientName(), clientDTO.getClientName()); + assertEquals(organizationIpaCode, clientDTO.getOrganizationIpaCode()); + assertTrue(Pattern.compile(uuidRegex).matcher(clientDTO.getClientSecret()).matches()); + assertEquals(uuidRandomForSecret, clientDTO.getClientSecret()); + } + } diff --git a/src/test/java/it/gov/pagopa/payhub/auth/mapper/ClientMapperTest.java b/src/test/java/it/gov/pagopa/payhub/auth/mapper/ClientMapperTest.java new file mode 100644 index 00000000..538a50bf --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/mapper/ClientMapperTest.java @@ -0,0 +1,93 @@ +package it.gov.pagopa.payhub.auth.mapper; + +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.service.DataCipherService; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Random; +import java.util.UUID; + +@ExtendWith(MockitoExtension.class) +class ClientMapperTest { + + @Mock + private DataCipherService dataCipherServiceMock; + + private ClientMapper service; + + @BeforeEach + void init(){ + service = new ClientMapper(dataCipherServiceMock); + } + + @Test + void WhenMapThenGetClientMapped() { + // Given + String plainClientSecret = UUID.randomUUID().toString(); + String organizationIpaCode = "organizationIpaCode"; + String clientName = "clientName"; + String clientId = organizationIpaCode + clientName; + byte[] encryptedClientSecretMock = new byte[0]; + + Client client = Client.builder() + .clientId(clientId) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(encryptedClientSecretMock) + .build(); + + ClientDTO clientDTO = ClientDTO.builder() + .clientId(clientId) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(plainClientSecret) + .build(); + + Mockito.when(dataCipherServiceMock.encrypt(plainClientSecret)).thenReturn(encryptedClientSecretMock); + // When + Client modelMapped = service.mapToModel(clientDTO); + + // Then + Assertions.assertEquals(client, modelMapped); + } + + @Test + void WhenMapThenGetClientDTOMapped() { + // Given + byte[] encryptedClientSecret = new byte[16]; + new Random().nextBytes(encryptedClientSecret); + String decryptedClientSecret = UUID.randomUUID().toString(); + String organizationIpaCode = "organizationIpaCode"; + String clientName = "clientName"; + String clientId = organizationIpaCode + clientName; + + ClientDTO clientDTO = ClientDTO.builder() + .clientId(clientId) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(decryptedClientSecret) + .build(); + + Client client = Client.builder() + .clientId(clientId) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(encryptedClientSecret) + .build(); + + Mockito.when(dataCipherServiceMock.decrypt(encryptedClientSecret)).thenReturn(decryptedClientSecret); + + // When + ClientDTO dtoMapped = service.mapToDTO(client); + // Then + Assertions.assertEquals(clientDTO, dtoMapped); + } + +} diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/AuthzServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/AuthzServiceTest.java index 1c440dc2..4d4b98ca 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/AuthzServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/AuthzServiceTest.java @@ -6,14 +6,12 @@ import it.gov.pagopa.payhub.auth.model.User; import it.gov.pagopa.payhub.auth.repository.OperatorsRepository; import it.gov.pagopa.payhub.auth.repository.UsersRepository; +import it.gov.pagopa.payhub.auth.service.a2a.ClientService; import it.gov.pagopa.payhub.auth.service.user.UserService; import it.gov.pagopa.payhub.auth.service.user.retrieve.Operator2UserInfoMapper; import it.gov.pagopa.payhub.auth.service.user.retrieve.OperatorDTOMapper; import it.gov.pagopa.payhub.auth.service.user.retrieve.UserDTOMapper; -import it.gov.pagopa.payhub.model.generated.CreateOperatorRequest; -import it.gov.pagopa.payhub.model.generated.OperatorDTO; -import it.gov.pagopa.payhub.model.generated.UserDTO; -import it.gov.pagopa.payhub.model.generated.UserInfo; +import it.gov.pagopa.payhub.model.generated.*; import java.util.HashSet; import java.util.Optional; import org.junit.jupiter.api.AfterEach; @@ -37,6 +35,9 @@ class AuthzServiceTest { @Mock private UserService userServiceMock; + @Mock + private ClientService clientServiceMock; + @Mock private OperatorsRepository operatorsRepository; @@ -56,14 +57,14 @@ class AuthzServiceTest { @BeforeEach void init(){ - service = new AuthzServiceImpl(userServiceMock, usersRepository, operatorsRepository, - operatorDTOMapper, userDTOMapper, operator2UserInfoMapper); + service = new AuthzServiceImpl(userServiceMock, clientServiceMock, usersRepository, operatorsRepository, operatorDTOMapper, userDTOMapper, operator2UserInfoMapper); } @AfterEach void verifyNotMoreInteractions(){ Mockito.verifyNoMoreInteractions( - userServiceMock + userServiceMock, + clientServiceMock ); } @@ -217,6 +218,22 @@ void whenCreateUserThenVerifyuser() { Assertions.assertEquals(expectedUser, actualUserDTO); } + @Test + void whenCreateClientThenVerifyClient() { + //Given + String organizationIpaCode = "organizationIpaCode"; + CreateClientRequest createClientRequest = new CreateClientRequest(); + createClientRequest.setClientName("clientname"); + ClientDTO expectedClientDTO = new ClientDTO(); + + Mockito.when(clientServiceMock.registerClient(createClientRequest.getClientName(), organizationIpaCode)).thenReturn(expectedClientDTO); + + //When + ClientDTO actualClientDTO = service.registerClient(organizationIpaCode, createClientRequest); + //Then + Assertions.assertEquals(expectedClientDTO, actualClientDTO); + } + @Test void whenGetUserInfoFromMappedExternalUserIdThenGetUserInfo() { //Given @@ -227,7 +244,7 @@ void whenGetUserInfoFromMappedExternalUserIdThenGetUserInfo() { UserInfo expectedUserInfo = new UserInfo(); Mockito.when(usersRepository.findByMappedExternalUserId(mappedExternalUserId)) - .thenReturn(Optional.of(userMock)); + .thenReturn(Optional.of(userMock)); Mockito.when(operatorsRepository.findAllByUserId(userMock.getUserId())).thenReturn(operators); Mockito.when(operator2UserInfoMapper.apply(userMock, operators)).thenReturn(expectedUserInfo); @@ -243,7 +260,7 @@ void whenGetUserInfoFromMappedExternalUserIdThenUserNotFound() { //Given String mappedExternalUserId = "MAPPEDEXTERNALUSERID"; Mockito.when(usersRepository.findByMappedExternalUserId(mappedExternalUserId)) - .thenReturn(Optional.empty()); + .thenReturn(Optional.empty()); //When Exception exception = Assertions.assertThrows(UserNotFoundException.class, () -> { @@ -252,5 +269,4 @@ void whenGetUserInfoFromMappedExternalUserIdThenUserNotFound() { //Then Assertions.assertTrue(exception.getMessage().contains("Cannot found user having mappedExternalId:" + mappedExternalUserId)); } - } diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/DataCipherServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/DataCipherServiceTest.java index 7837ebea..322ab26c 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/DataCipherServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/DataCipherServiceTest.java @@ -2,12 +2,39 @@ import java.util.Base64; + +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class DataCipherServiceTest { + private final DataCipherService service = new DataCipherService("PSW","PEPPER", new ObjectMapper()); + + @Test + void testEncryption() { + // Given + String plain = "PLAINTEXT"; + + // When + byte[] cipher = service.encrypt(plain); + String result = service.decrypt(cipher); + + // Then + Assertions.assertEquals(plain, result); + } + + @Test + void testEncryptionAsObject() { + // Given + String plain = "PLAINTEXT"; - private final DataCipherService service = new DataCipherService("PEPPER"); + // When + byte[] cipher = service.encryptObj(plain); + String result = service.decryptObj(cipher, String.class); + + // Then + Assertions.assertEquals(plain, result); + } @Test void givenTextWhenHashThenOk() { diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceTest.java new file mode 100644 index 00000000..38047f51 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/ClientServiceTest.java @@ -0,0 +1,56 @@ +package it.gov.pagopa.payhub.auth.service.a2a; + +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.service.a2a.registration.ClientRegistrationService; +import it.gov.pagopa.payhub.auth.mapper.ClientMapper; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ClientServiceTest { + + @Mock + private ClientRegistrationService clientRegistrationServiceMock; + + @Mock + private ClientMapper clientMapperMock; + + private ClientService service; + + @BeforeEach + void init(){ + service = new ClientServiceImpl(clientRegistrationServiceMock, clientMapperMock); + } + + @AfterEach + void verifyNotMoreInteractions(){ + Mockito.verifyNoMoreInteractions( + clientRegistrationServiceMock, + clientMapperMock + ); + } + + @Test + void whenRegisterClientThenIInvokeClientRegistrationService() { + // Given + String organizationIpaCode = "organizationIpaCode"; + String clientName = "clientName"; + + Client mockClient = new Client(); + ClientDTO expectedClientDTO = new ClientDTO(); + + Mockito.when(clientRegistrationServiceMock.registerClient(clientName, organizationIpaCode)).thenReturn(mockClient); + Mockito.when(clientMapperMock.mapToDTO(mockClient)).thenReturn(expectedClientDTO); + // When + ClientDTO actualClientDTO = service.registerClient(clientName, organizationIpaCode); + // Then + Assertions.assertEquals(expectedClientDTO, actualClientDTO); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationServiceTest.java new file mode 100644 index 00000000..109e6437 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/a2a/registration/ClientRegistrationServiceTest.java @@ -0,0 +1,54 @@ +package it.gov.pagopa.payhub.auth.service.a2a.registration; + +import it.gov.pagopa.payhub.auth.mapper.ClientMapper; +import it.gov.pagopa.payhub.auth.model.Client; +import it.gov.pagopa.payhub.auth.repository.ClientRepository; +import it.gov.pagopa.payhub.model.generated.ClientDTO; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.UUID; + +@ExtendWith(MockitoExtension.class) +class ClientRegistrationServiceTest { + + @Mock + private ClientMapper clientMapperMock; + @Mock + private ClientRepository clientRepositoryMock; + + @InjectMocks + private ClientRegistrationService service; + + @Test + void whenRegisterClientThenReturnStoredClient(){ + // Given + String organizationIpaCode = "organizationIpaCode"; + String clientName = "clientName"; + String clientId = organizationIpaCode + clientName; + String uuidForClientSecret = UUID.randomUUID().toString(); + + ClientDTO clientDTO = ClientDTO.builder() + .clientId(clientId) + .clientName(clientName) + .organizationIpaCode(organizationIpaCode) + .clientSecret(uuidForClientSecret) + .build(); + + Client clientMapped = clientMapperMock.mapToModel(clientDTO); + Client storedClient = new Client(); + + Mockito.when(clientRepositoryMock.insert(clientMapped)).thenReturn(storedClient); + + // When + Client result = service.registerClient(clientName, organizationIpaCode); + + // Then + Assertions.assertSame(storedClient, result); + } +} diff --git a/src/test/java/it/gov/pagopa/payhub/auth/service/user/registration/ExternalUserIdObfuscatorServiceTest.java b/src/test/java/it/gov/pagopa/payhub/auth/service/user/registration/ExternalUserIdObfuscatorServiceTest.java index d31987e1..0afb22d8 100644 --- a/src/test/java/it/gov/pagopa/payhub/auth/service/user/registration/ExternalUserIdObfuscatorServiceTest.java +++ b/src/test/java/it/gov/pagopa/payhub/auth/service/user/registration/ExternalUserIdObfuscatorServiceTest.java @@ -1,7 +1,6 @@ package it.gov.pagopa.payhub.auth.service.user.registration; import it.gov.pagopa.payhub.auth.service.DataCipherService; -import java.util.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -10,11 +9,13 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Base64; + @ExtendWith(MockitoExtension.class) class ExternalUserIdObfuscatorServiceTest { @Mock - private DataCipherService dataCipherService = new DataCipherService("PEPPER"); + private DataCipherService dataCipherService; @InjectMocks private ExternalUserIdObfuscatorService service; diff --git a/src/test/java/it/gov/pagopa/payhub/auth/utils/AESUtilsTest.java b/src/test/java/it/gov/pagopa/payhub/auth/utils/AESUtilsTest.java new file mode 100644 index 00000000..1f185280 --- /dev/null +++ b/src/test/java/it/gov/pagopa/payhub/auth/utils/AESUtilsTest.java @@ -0,0 +1,20 @@ +package it.gov.pagopa.payhub.auth.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class AESUtilsTest { + + @Test + void test() { + // Given + final String plainToEncrypt = "PLAINTEXT"; + final String passwordTest = "PSW"; + // When + byte[] cipher = AESUtils.encrypt(passwordTest, plainToEncrypt); + String result = AESUtils.decrypt(passwordTest, cipher); + + // Then + Assertions.assertEquals(result, plainToEncrypt); + } +} \ No newline at end of file