From 8c21818b9e9d4230c8f6d9a80e3101912d4049f3 Mon Sep 17 00:00:00 2001 From: Anusha Sunkada Date: Thu, 14 Dec 2023 14:01:53 +0530 Subject: [PATCH] ES-430 (#42) * ES-430 Signed-off-by: ase-101 * ES-430 Signed-off-by: ase-101 * Updated template messages --------- Signed-off-by: ase-101 Signed-off-by: Sreang Rathanak --- .../controllers/RegistrationController.java | 3 +- .../java/io/mosip/signup/dto/Identity.java | 1 + .../dto/RegistrationStatusResponse.java | 1 + .../signup/dto/RegistrationTransaction.java | 18 +++- .../signup/services/RegistrationService.java | 85 +++++++++++++++---- .../mosip/signup/util/RegistrationStatus.java | 9 +- .../io/mosip/signup/util/SignUpConstants.java | 4 + .../resources/application-default.properties | 9 +- .../services/RegistrationServiceTest.java | 7 +- 9 files changed, 112 insertions(+), 25 deletions(-) diff --git a/signup-service/src/main/java/io/mosip/signup/controllers/RegistrationController.java b/signup-service/src/main/java/io/mosip/signup/controllers/RegistrationController.java index 52960989..05879bff 100644 --- a/signup-service/src/main/java/io/mosip/signup/controllers/RegistrationController.java +++ b/signup-service/src/main/java/io/mosip/signup/controllers/RegistrationController.java @@ -14,7 +14,6 @@ import javax.validation.Valid; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; import org.springframework.web.bind.annotation.RestController; @@ -64,6 +63,8 @@ public ResponseWrapper register(@Valid @RequestBody RequestWra public ResponseWrapper getRegistrationStatus( @Valid @NotBlank(message = ErrorConstants.INVALID_TRANSACTION) @CookieValue(value = SignUpConstants.VERIFIED_TRANSACTION_ID, defaultValue = EMTPY) String transactionId) { + //TODO Need to change the response to List + //As it will be easier to give out credential issuance status for each handle type. ResponseWrapper responseWrapper = new ResponseWrapper(); responseWrapper.setResponse(registrationService.getRegistrationStatus(transactionId)); responseWrapper.setResponseTime(IdentityProviderUtil.getUTCDateTime()); diff --git a/signup-service/src/main/java/io/mosip/signup/dto/Identity.java b/signup-service/src/main/java/io/mosip/signup/dto/Identity.java index 881e9163..72bc3caa 100644 --- a/signup-service/src/main/java/io/mosip/signup/dto/Identity.java +++ b/signup-service/src/main/java/io/mosip/signup/dto/Identity.java @@ -20,4 +20,5 @@ public class Identity implements Serializable { private String preferredLang; private Password password; private String registrationType; + private List selectedHandles; } diff --git a/signup-service/src/main/java/io/mosip/signup/dto/RegistrationStatusResponse.java b/signup-service/src/main/java/io/mosip/signup/dto/RegistrationStatusResponse.java index aaa8924b..265fa095 100644 --- a/signup-service/src/main/java/io/mosip/signup/dto/RegistrationStatusResponse.java +++ b/signup-service/src/main/java/io/mosip/signup/dto/RegistrationStatusResponse.java @@ -5,5 +5,6 @@ @Data public class RegistrationStatusResponse { + private RegistrationStatus status; } diff --git a/signup-service/src/main/java/io/mosip/signup/dto/RegistrationTransaction.java b/signup-service/src/main/java/io/mosip/signup/dto/RegistrationTransaction.java index a3d3bf50..d2e14903 100644 --- a/signup-service/src/main/java/io/mosip/signup/dto/RegistrationTransaction.java +++ b/signup-service/src/main/java/io/mosip/signup/dto/RegistrationTransaction.java @@ -1,5 +1,6 @@ package io.mosip.signup.dto; +import io.mosip.esignet.core.util.IdentityProviderUtil; import io.mosip.signup.util.RegistrationStatus; import lombok.Data; @@ -7,6 +8,9 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.UUID; @Data @@ -19,14 +23,17 @@ public class RegistrationTransaction implements Serializable { private LocalDateTime lastRetryAt; private String challengeTransactionId; private String applicationId; + private Map handlesStatus; private RegistrationStatus registrationStatus; private String locale; public RegistrationTransaction(String identifier) { - this.identifier = identifier; + this.identifier = IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, + identifier.toLowerCase(Locale.ROOT)); this.startedAt = LocalDateTime.now(ZoneOffset.UTC); this.challengeTransactionId = UUID.randomUUID().toString(); this.applicationId = UUID.randomUUID().toString(); + this.handlesStatus = new HashMap<>(); this.registrationStatus = null; this.challengeHash = null; this.challengeRetryAttempts = 0; @@ -43,4 +50,13 @@ public void increaseAttempt() { this.challengeRetryAttempts += 1; this.lastRetryAt = LocalDateTime.now(ZoneOffset.UTC); } + + public boolean isValidIdentifier(String inputIdentifier) { + return this.identifier.equals(IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, + inputIdentifier)); + } + + public void setIdentifier(String identifier) { + this.identifier = IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, identifier); + } } diff --git a/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java b/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java index 23ffc777..05fa4daa 100644 --- a/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java +++ b/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java @@ -1,9 +1,9 @@ package io.mosip.signup.services; import io.mosip.esignet.core.util.IdentityProviderUtil; +import io.mosip.kernel.core.util.HMACUtils2; import io.mosip.signup.dto.*; import io.mosip.signup.exception.ChallengeFailedException; -import io.mosip.signup.exception.InvalidIdentifierException; import io.mosip.signup.exception.InvalidTransactionException; import io.mosip.signup.exception.SignUpException; import io.mosip.signup.util.ActionStatus; @@ -27,9 +27,14 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashMap; +import java.util.Locale; +import java.util.Map; -import static io.mosip.signup.util.SignUpConstants.CONSENT_DISAGREE; +import static io.mosip.signup.util.SignUpConstants.*; @Slf4j @Service @@ -81,10 +86,6 @@ public class RegistrationService { @Value("${mosip.signup.challenge.resend-delay}") private long resendDelay; - private String otpRegistrationMessageTemplatekey = "mosip.signup.sms-notification-template.send-otp"; - - private String registrationSMSNotificationTemplate = "mosip.signup.sms-notification-template.success-registration"; - @Value("${mosip.signup.unauthenticated.txn.timeout}") private int unauthenticatedTransactionTimeout; @@ -94,6 +95,9 @@ public class RegistrationService { @Value("${mosip.signup.status-check.txn.timeout}") private int statusCheckTransactionTimeout; + @Value("${mosip.signup.get-registration-status.endpoint}") + private String getRegistrationStatusEndpoint; + /** * Generate and regenerate challenge based on the "regenerate" flag in the request. * if regenerate is false - always creates a new transaction and set-cookie header is sent in the response. @@ -132,7 +136,7 @@ public GenerateChallengeResponse generateChallenge(GenerateChallengeRequest gene cacheUtilService.setChallengeGeneratedTransaction(transactionId, transaction); notificationHelper.sendSMSNotificationAsync(generateChallengeRequest.getIdentifier(), transaction.getLocale(), - otpRegistrationMessageTemplatekey, new HashMap<>(){{put("{challenge}", challenge);}}) + SEND_OTP_SMS_NOTIFICATION_TEMPLATE_KEY, new HashMap<>(){{put("{challenge}", challenge);}}) .thenAccept(notificationResponseRestResponseWrapper -> { log.debug("Notification response -> {}", notificationResponseRestResponseWrapper); }); @@ -148,7 +152,7 @@ public VerifyChallengeResponse verifyChallenge(VerifyChallengeRequest verifyChal log.error("Transaction {} : not found in ChallengeGeneratedTransaction cache", transactionId); throw new InvalidTransactionException(); } - if(!verifyChallengeRequest.getIdentifier().equals(transaction.getIdentifier())) { + if(!transaction.isValidIdentifier(verifyChallengeRequest.getIdentifier())) { log.error("Transaction {} : contain identifier not the same with identifier user request", transactionId); throw new SignUpException(ErrorConstants.IDENTIFIER_MISMATCH); } @@ -175,7 +179,7 @@ public RegisterResponse register(RegisterRequest registerRequest, String transac log.error("Transaction {} : not found in ChallengeVerifiedTransaction cache", transactionId); throw new InvalidTransactionException(); } - if(!transaction.getIdentifier().equals(registerRequest.getUsername()) || + if(!transaction.isValidIdentifier(registerRequest.getUsername()) || !registerRequest.getUsername().equals(registerRequest.getUserInfo().getPhone())) { log.error("Transaction {} : given unsupported username in L1", transactionId); throw new SignUpException(ErrorConstants.IDENTIFIER_MISMATCH); @@ -185,13 +189,13 @@ public RegisterResponse register(RegisterRequest registerRequest, String transac throw new SignUpException(ErrorConstants.CONSENT_REQUIRED); } - saveIdentityData(registerRequest, transactionId, transaction.getApplicationId()); + saveIdentityData(registerRequest, transactionId, transaction); transaction.setRegistrationStatus(RegistrationStatus.PENDING); cacheUtilService.setRegisteredTransaction(transactionId, transaction); - notificationHelper.sendSMSNotificationAsync(transaction.getIdentifier(), transaction.getLocale(), - registrationSMSNotificationTemplate, null) + notificationHelper.sendSMSNotificationAsync(registerRequest.getUserInfo().getPhone(), transaction.getLocale(), + REGISTRATION_SMS_NOTIFICATION_TEMPLATE_KEY, null) .thenAccept(notificationResponseRestResponseWrapper -> { log.debug("Notification response -> {}", notificationResponseRestResponseWrapper); }); @@ -212,12 +216,24 @@ public RegistrationStatusResponse getRegistrationStatus(String transactionId) if (registrationTransaction == null) throw new InvalidTransactionException(); + //For L1 only phone is considered to be handle, later other fields can also be used as handles. + //We should know the credential issuance status of each handle. + for(String handleRequestId : registrationTransaction.getHandlesStatus().keySet()) { + if(!RegistrationStatus.getEndStatuses().contains(registrationTransaction.getHandlesStatus().get(handleRequestId))) { + RegistrationStatus registrationStatus = getRegistrationStatusFromServer(registrationTransaction.getApplicationId()); + registrationTransaction.getHandlesStatus().put(handleRequestId, registrationStatus); + //TODO This is temporary fix, we need to remove this field later from registrationTransaction DTO. + registrationTransaction.setRegistrationStatus(registrationStatus); + } + } + registrationTransaction = cacheUtilService.setRegisteredTransaction(transactionId, registrationTransaction); RegistrationStatusResponse registrationStatusResponse = new RegistrationStatusResponse(); registrationStatusResponse.setStatus(registrationTransaction.getRegistrationStatus()); return registrationStatusResponse; } - private void saveIdentityData(RegisterRequest registerRequest, String transactionId, String applicationId) throws SignUpException{ + private void saveIdentityData(RegisterRequest registerRequest, String transactionId, + RegistrationTransaction transaction) throws SignUpException{ UserInfoMap userInfoMap = registerRequest.getUserInfo(); @@ -234,8 +250,13 @@ private void saveIdentityData(RegisterRequest registerRequest, String transactio Password password = generateSaltedHash(registerRequest.getPassword(), transactionId); identity.setPassword(password); + //By default, phone is set as the selected handle. + identity.setSelectedHandles(Arrays.asList("phone")); + transaction.getHandlesStatus().put(getHandleRequestId(transaction.getApplicationId(), + "phone", userInfoMap.getPhone()), RegistrationStatus.PENDING); + AddIdentityRequest addIdentityRequest = new AddIdentityRequest(); - addIdentityRequest.setRegistrationId(applicationId); + addIdentityRequest.setRegistrationId(transaction.getApplicationId()); addIdentityRequest.setIdentity(identity); addIdentity(addIdentityRequest, transactionId); @@ -296,7 +317,7 @@ private String getUniqueIdentifier(String transactionId) throws SignUpException return restResponseWrapper.getResponse().getUIN(); } - log.error("Transaction {} : Get unique identifier failed with response {}", transactionId, restResponseWrapper); + log.error("Transaction {} : Get unique identifier(UIN) failed with response {}", transactionId, restResponseWrapper); throw new SignUpException(restResponseWrapper != null && !CollectionUtils.isEmpty(restResponseWrapper.getErrors()) ? restResponseWrapper.getErrors().get(0).getErrorCode() : ErrorConstants.GET_UIN_FAILED); } @@ -307,9 +328,9 @@ private void validateTransaction(RegistrationTransaction transaction, String ide throw new InvalidTransactionException(); } - if(!transaction.getIdentifier().equals(identifier)) { + if(!transaction.isValidIdentifier(identifier)) { log.error("generate-challenge failed: invalid identifier"); - throw new InvalidIdentifierException(); + throw new SignUpException(ErrorConstants.IDENTIFIER_MISMATCH); } if(transaction.getChallengeRetryAttempts() >= resendAttempts) { @@ -323,6 +344,24 @@ private void validateTransaction(RegistrationTransaction transaction, String ide } } + private RegistrationStatus getRegistrationStatusFromServer(String applicationId) { + RestResponseWrapper> restResponseWrapper = selfTokenRestTemplate.exchange(getRegistrationStatusEndpoint, + HttpMethod.GET, null, + new ParameterizedTypeReference>>() {}, applicationId).getBody(); + + if (restResponseWrapper != null && restResponseWrapper.getResponse() != null && + !StringUtils.isEmpty(restResponseWrapper.getResponse().get("statusCode")) ) { + switch (restResponseWrapper.getResponse().get("statusCode")) { + case "STORED" : return RegistrationStatus.COMPLETED; + case "FAILED" : return RegistrationStatus.FAILED; + case "ISSUED" : + default: return RegistrationStatus.PENDING; + } + } + log.error("Transaction {} : Get registration status failed with response {}", applicationId, restResponseWrapper); + return RegistrationStatus.PENDING; + } + private void addCookieInResponse(String transactionId, int maxAge) { Cookie cookie = new Cookie(SignUpConstants.TRANSACTION_ID, transactionId); cookie.setMaxAge(maxAge); // 60 = 1 minute @@ -342,4 +381,16 @@ private void addVerifiedCookieInResponse(String transactionId, int maxAge) { unsetCookie.setMaxAge(0); response.addCookie(unsetCookie); } + + private String getHandleRequestId(String requestId, String handleFieldId, String handle) { + //TODO need to take the tag from configuration based on fieldId + String handleWithTaggedHandleType = handle.concat("@").concat(handleFieldId).toLowerCase(Locale.ROOT); + String handleRequestId = requestId.concat(handleWithTaggedHandleType); + try { + return HMACUtils2.digestAsPlainText(handleRequestId.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + log.error("Failed to generate handleRequestId", e); + } + return requestId; + } } diff --git a/signup-service/src/main/java/io/mosip/signup/util/RegistrationStatus.java b/signup-service/src/main/java/io/mosip/signup/util/RegistrationStatus.java index 7006c082..f643e9b1 100644 --- a/signup-service/src/main/java/io/mosip/signup/util/RegistrationStatus.java +++ b/signup-service/src/main/java/io/mosip/signup/util/RegistrationStatus.java @@ -1,7 +1,14 @@ package io.mosip.signup.util; +import java.util.Arrays; +import java.util.List; + public enum RegistrationStatus { PENDING, COMPLETED, - FAILED + FAILED; + + public static List getEndStatuses() { + return Arrays.asList(RegistrationStatus.COMPLETED, RegistrationStatus.FAILED); + } } diff --git a/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java b/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java index 873788f1..5268e3f8 100644 --- a/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java +++ b/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java @@ -1,6 +1,7 @@ package io.mosip.signup.util; public class SignUpConstants { + public static final String CHALLENGE_GENERATED = "challenge_generated"; public static final String CHALLENGE_VERIFIED = "challenge_verified"; public static final String REGISTERED_CACHE = "registered"; @@ -8,5 +9,8 @@ public class SignUpConstants { public static final String VERIFIED_TRANSACTION_ID = "VERIFIED_TRANSACTION_ID"; public static final String CONSENT_DISAGREE = "DISAGREE"; public static final String EMTPY = ""; + public static final String SEND_OTP_SMS_NOTIFICATION_TEMPLATE_KEY = "mosip.signup.sms-notification-template.send-otp"; + public static final String REGISTRATION_SMS_NOTIFICATION_TEMPLATE_KEY = "mosip.signup.sms-notification-template.registration"; + } diff --git a/signup-service/src/main/resources/application-default.properties b/signup-service/src/main/resources/application-default.properties index bd3535ec..9782626a 100644 --- a/signup-service/src/main/resources/application-default.properties +++ b/signup-service/src/main/resources/application-default.properties @@ -79,6 +79,7 @@ mosip.signup.add-identity.endpoint=https://api-internal.camdgc-dev.mosip.net/idr mosip.signup.generate-hash.endpoint=https://api-internal.camdgc-dev.mosip.net/v1/keymanager/generateArgon2Hash mosip.signup.get-uin.endpoint=https://api-internal.camdgc-dev.mosip.net/v1/idgenerator/uin mosip.signup.send-notification.endpoint=https://api-internal.camdgc-dev.mosip.net/v1/notifier/sms/send +mosip.signup.get-registration-status.endpoint=https://api-internal.camdgc-dev.mosip.net/v1/credentialrequest/get/{applicationId} mosip.signup.add-identity.request.id=mosip.id.create mosip.signup.add-identity.request.version=v1 @@ -110,10 +111,12 @@ mosip.signup.ui.config.key-values={\ 'signin.redirect-url': 'https://esignet.camdgc-dev.mosip.net/authorize' \ } -## ----------------------------- SMS-message ----------------------------------------------------------------------------- +## ----------------------------- Notification templates ---------------------------------------------------------------- + mosip.signup.sms-notification-template.send-otp.khm=ប្រើ {challenge} ដើម្បីផ្ទៀងផ្ទាត់គណនី KhID របស់អ្នក។ mosip.signup.sms-notification-template.send-otp.eng=Use {challenge} to verify your KhID account. -mosip.signup.sms-notification-template.success-registration.khm=អ្នកបានចុះឈ្មោះគណនី KhID ដោយជោគជ័យ។ -mosip.signup.sms-notification-template.success-registration.eng=You successfully registered to KhID account. +mosip.signup.sms-notification-template.registration.khm=អ្នកបានចុះឈ្មោះគណនី KhID ដោយជោគជ័យ។ +mosip.signup.sms-notification-template.registration.eng=You successfully registered to KhID account. + #------------------------------------------ Others --------------------------------------------------------------------- logging.level.io.mosip.signup=DEBUG diff --git a/signup-service/src/test/java/io/mosip/signup/services/RegistrationServiceTest.java b/signup-service/src/test/java/io/mosip/signup/services/RegistrationServiceTest.java index 9e05688b..2215e6d1 100644 --- a/signup-service/src/test/java/io/mosip/signup/services/RegistrationServiceTest.java +++ b/signup-service/src/test/java/io/mosip/signup/services/RegistrationServiceTest.java @@ -751,8 +751,8 @@ public void doGenerateChallenge_withIdentifierNotMatchTransactionId_thenFail() t try { registrationService.generateChallenge(generateChallengeRequest, transactionId); Assert.fail(); - } catch (InvalidIdentifierException ex) { - Assert.assertEquals("invalid_identifier", ex.getErrorCode()); + } catch (SignUpException ex) { + Assert.assertEquals(ErrorConstants.IDENTIFIER_MISMATCH, ex.getErrorCode()); } } @@ -808,6 +808,7 @@ public void doGetRegistrationStatus_withCompletedTransaction_thenPass() { RegistrationTransaction registrationTransaction = new RegistrationTransaction("+85577410541"); registrationTransaction.setRegistrationStatus(RegistrationStatus.COMPLETED); when(cacheUtilService.getRegisteredTransaction(transactionId)).thenReturn(registrationTransaction); + when(cacheUtilService.setRegisteredTransaction(transactionId, registrationTransaction)).thenReturn(registrationTransaction); RegistrationStatusResponse registrationStatusResponse = registrationService.getRegistrationStatus(transactionId); Assert.assertNotNull(registrationStatusResponse); @@ -820,6 +821,7 @@ public void doGetRegistrationStatus_withPendingTransaction_thenPass() { RegistrationTransaction registrationTransaction = new RegistrationTransaction("+85577410541"); registrationTransaction.setRegistrationStatus(RegistrationStatus.PENDING); when(cacheUtilService.getRegisteredTransaction(transactionId)).thenReturn(registrationTransaction); + when(cacheUtilService.setRegisteredTransaction(transactionId, registrationTransaction)).thenReturn(registrationTransaction); RegistrationStatusResponse registrationStatusResponse = registrationService.getRegistrationStatus(transactionId); Assert.assertNotNull(registrationStatusResponse); @@ -832,6 +834,7 @@ public void doGetRegistrationStatus_withFailedTransaction_thenPass() { RegistrationTransaction registrationTransaction = new RegistrationTransaction("+85577410541"); registrationTransaction.setRegistrationStatus(RegistrationStatus.FAILED); when(cacheUtilService.getRegisteredTransaction(transactionId)).thenReturn(registrationTransaction); + when(cacheUtilService.setRegisteredTransaction(transactionId, registrationTransaction)).thenReturn(registrationTransaction); RegistrationStatusResponse registrationStatusResponse = registrationService.getRegistrationStatus(transactionId); Assert.assertNotNull(registrationStatusResponse);