Skip to content

Commit

Permalink
ES-430 (mosip#42)
Browse files Browse the repository at this point in the history
* ES-430

Signed-off-by: ase-101 <[email protected]>

* ES-430

Signed-off-by: ase-101 <[email protected]>

* Updated template messages

---------

Signed-off-by: ase-101 <[email protected]>
Signed-off-by: Sreang Rathanak <[email protected]>
  • Loading branch information
ase-101 authored and Sreang Rathanak committed Jan 15, 2024
1 parent 64270b4 commit 8c21818
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -64,6 +63,8 @@ public ResponseWrapper<RegisterResponse> register(@Valid @RequestBody RequestWra
public ResponseWrapper<RegistrationStatusResponse> 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<RegistrationStatusResponse>
//As it will be easier to give out credential issuance status for each handle type.
ResponseWrapper<RegistrationStatusResponse> responseWrapper = new ResponseWrapper<RegistrationStatusResponse>();
responseWrapper.setResponse(registrationService.getRegistrationStatus(transactionId));
responseWrapper.setResponseTime(IdentityProviderUtil.getUTCDateTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public class Identity implements Serializable {
private String preferredLang;
private Password password;
private String registrationType;
private List<String> selectedHandles;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

@Data
public class RegistrationStatusResponse {

private RegistrationStatus status;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.mosip.signup.dto;

import io.mosip.esignet.core.util.IdentityProviderUtil;
import io.mosip.signup.util.RegistrationStatus;
import lombok.Data;

import java.io.Serializable;
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
Expand All @@ -19,14 +23,17 @@ public class RegistrationTransaction implements Serializable {
private LocalDateTime lastRetryAt;
private String challengeTransactionId;
private String applicationId;
private Map<String, RegistrationStatus> 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;
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand Down Expand Up @@ -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);
});
Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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);
});
Expand All @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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) {
Expand All @@ -323,6 +344,24 @@ private void validateTransaction(RegistrationTransaction transaction, String ide
}
}

private RegistrationStatus getRegistrationStatusFromServer(String applicationId) {
RestResponseWrapper<Map<String,String>> restResponseWrapper = selfTokenRestTemplate.exchange(getRegistrationStatusEndpoint,
HttpMethod.GET, null,
new ParameterizedTypeReference<RestResponseWrapper<Map<String,String>>>() {}, 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
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<RegistrationStatus> getEndStatuses() {
return Arrays.asList(RegistrationStatus.COMPLETED, RegistrationStatus.FAILED);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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";
public static final String TRANSACTION_ID = "TRANSACTION_ID";
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";


}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 8c21818

Please sign in to comment.