Skip to content

Commit

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

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

* ES-427

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

* Added logback.xml

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

---------

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 efed621 commit b369041
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 146 deletions.
21 changes: 17 additions & 4 deletions signup-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -147,15 +153,22 @@
<artifactId>kernel-core</artifactId>
<version>1.2.0.1-B1</version>
</dependency>
<dependency>
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-logger-logback</artifactId>
<version>1.2.0.1-B1</version>
<exclusions>
<exclusion>
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>io.mosip.esignet</groupId>
<artifactId>esignet-integration-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.mosip.signup.dto;

import io.mosip.signup.validator.Identifier;
import io.mosip.signup.validator.Language;
import lombok.Data;

@Data
Expand All @@ -9,5 +10,7 @@ public class GenerateChallengeRequest {
@Identifier
private String identifier;
private String captchaToken;
@Language(required = false)
private String locale;
private boolean regenerate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
import java.io.Serializable;

@Data
public class ApiChallengeRequest implements Serializable {
public class OtpRequest implements Serializable {
private String key;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.mosip.signup.dto;

import lombok.Data;

@Data
public class OtpResponse {

private String otp;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

@Data
public class RegistrationTransaction implements Serializable {

private String challengeHash;
private String identifier;
private LocalDateTime startedAt;
Expand All @@ -18,10 +20,12 @@ public class RegistrationTransaction implements Serializable {
private String challengeTransactionId;
private String applicationId;
private RegistrationStatus registrationStatus;
public RegistrationTransaction(String identifier, String transactionId) {
private String locale;

public RegistrationTransaction(String identifier) {
this.identifier = identifier;
this.startedAt = LocalDateTime.now();
this.challengeTransactionId = transactionId;
this.startedAt = LocalDateTime.now(ZoneOffset.UTC);
this.challengeTransactionId = UUID.randomUUID().toString();
this.applicationId = UUID.randomUUID().toString();
this.registrationStatus = null;
this.challengeHash = null;
Expand All @@ -32,11 +36,11 @@ public RegistrationTransaction(String identifier, String transactionId) {
public long getLastRetryToNow() {
if (this.lastRetryAt == null) return 0;

return this.lastRetryAt.until(LocalDateTime.now(), ChronoUnit.SECONDS);
return this.lastRetryAt.until(LocalDateTime.now(ZoneOffset.UTC), ChronoUnit.SECONDS);
}

public void increaseAttempt() {
this.challengeRetryAttempts += 1;
this.lastRetryAt = LocalDateTime.now();
this.lastRetryAt = LocalDateTime.now(ZoneOffset.UTC);
}
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,64 @@
package io.mosip.signup.services;

import io.mosip.esignet.core.util.IdentityProviderUtil;
import io.mosip.signup.dto.ApiChallengeRequest;
import io.mosip.signup.dto.RegistrationTransaction;
import io.mosip.signup.dto.RestRequestWrapper;
import io.mosip.signup.dto.RestResponseWrapper;
import io.mosip.signup.dto.*;
import io.mosip.signup.exception.SignUpException;
import io.mosip.signup.util.ErrorConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.LinkedHashMap;

@Service
@Slf4j
public class ChallengeManagerService {

@Autowired
@Qualifier("selfTokenRestTemplate")
private RestTemplate selfTokenRestTemplate;

@Value("${mosip.signup.generate-challenge.endpoint}")
private String generateChallengeUrl;

@Value("${mosip.signup.supported.challenge-type:OTP}")
private String challengeType;


public String generateChallenge(RegistrationTransaction transaction) throws SignUpException {
ApiChallengeRequest apiChallengeRequest = new ApiChallengeRequest();
apiChallengeRequest.setKey(transaction.getChallengeTransactionId());
RestRequestWrapper<ApiChallengeRequest> restRequest = new RestRequestWrapper<>();
restRequest.setRequesttime(IdentityProviderUtil.getUTCDateTime());
restRequest.setRequest(apiChallengeRequest);
RestResponseWrapper<LinkedHashMap<String, String>> restResponseWrapper = (RestResponseWrapper<LinkedHashMap<String, String>>) selfTokenRestTemplate
.postForObject(generateChallengeUrl, restRequest, RestResponseWrapper.class);
if (restResponseWrapper == null) {
log.error("generate-challenge Failed wrapper returned null");
throw new SignUpException(ErrorConstants.SEND_CHALLENGE_FAILED);
switch (challengeType) {
case "OTP" :
return generateOTPChallenge(transaction.getChallengeTransactionId());
}
throw new SignUpException(ErrorConstants.UNSUPPORTED_CHALLENGE_TYPE);
}

if(restResponseWrapper.getErrors() != null) {
log.error("generate-challenge Failed wrapper returned errors {}!", restResponseWrapper.getErrors());
throw new SignUpException(ErrorConstants.SEND_CHALLENGE_FAILED);
}
private String generateOTPChallenge(String challengeTransactionId) {
OtpRequest otpRequest = new OtpRequest();
otpRequest.setKey(challengeTransactionId);
RestRequestWrapper<OtpRequest> restRequestWrapper = new RestRequestWrapper<>();
restRequestWrapper.setRequesttime(IdentityProviderUtil.getUTCDateTime());
restRequestWrapper.setRequest(otpRequest);

RestResponseWrapper<OtpResponse> restResponseWrapper = selfTokenRestTemplate
.exchange(generateChallengeUrl, HttpMethod.POST,
new HttpEntity<>(restRequestWrapper),
new ParameterizedTypeReference<RestResponseWrapper<OtpResponse>>() {}).getBody();

String challenge = restResponseWrapper.getResponse().get("otp");
if (challenge == null || challenge.isEmpty()) {
log.error("generate-challenge Failed challenge returned null");
throw new SignUpException(ErrorConstants.SEND_CHALLENGE_FAILED);
if (restResponseWrapper != null && restResponseWrapper.getResponse() != null &&
!StringUtils.isEmpty(restResponseWrapper.getResponse().getOtp()) &&
!restResponseWrapper.getResponse().getOtp().equals("null")) {
return restResponseWrapper.getResponse().getOtp();
}

return challenge;
log.error("Generate OTP failed with response {}", restResponseWrapper);
throw new SignUpException(restResponseWrapper != null && !CollectionUtils.isEmpty(restResponseWrapper.getErrors()) ?
restResponseWrapper.getErrors().get(0).getErrorCode() : ErrorConstants.GENERATE_CHALLENGE_FAILED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

@ConditionalOnProperty(value = "mosip.signup.integration.captcha-validator", havingValue = "GoogleRecaptchaValidatorService")
Expand Down Expand Up @@ -39,6 +40,9 @@ public boolean validateCaptcha(String captchaToken) {

if (!requiredCaptcha) return true;

if(StringUtils.isEmpty(captchaToken))
return false;

MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
param.add("secret", verifierSecret);
param.add("response", captchaToken.trim());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,44 @@ public class RegistrationService {
@Value("${mosip.signup.challenge.resend-delay}")
private long resendDelay;

/**
* 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.
* if regenerate is true - expects a valid transaction Id in the cookie
* @param generateChallengeRequest
* @param transactionId
* @return
* @throws SignUpException
*/
public GenerateChallengeResponse generateChallenge(GenerateChallengeRequest generateChallengeRequest, String transactionId) throws SignUpException {
if (!googleRecaptchaValidatorService.validateCaptcha(generateChallengeRequest.getCaptchaToken())) {
log.error("generate-challenge failed: invalid captcha");
throw new CaptchaException(ErrorConstants.INVALID_CAPTCHA);
}

String identifier = generateChallengeRequest.getIdentifier();
RegistrationTransaction transaction = null;

if(generateChallengeRequest.isRegenerate() == false) {
transactionId = IdentityProviderUtil.createTransactionId(null);
transaction = new RegistrationTransaction(identifier);
}
else {
transaction = cacheUtilService.getChallengeGeneratedTransaction(transactionId);
validateTransaction(transaction, identifier);
}

// generate Challenge
String challenge = challengeManagerService.generateChallenge(transaction);
String challengeHash = IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, challenge);
addCookieResponse(transactionId);
transaction.setChallengeHash(challengeHash);
transaction.increaseAttempt();
transaction.setLocale(generateChallengeRequest.getLocale());
cacheUtilService.setChallengeGeneratedTransaction(transactionId, transaction);
return new GenerateChallengeResponse(ActionStatus.SUCCESS);
}

public VerifyChallengeResponse verifyChallenge(VerifyChallengeRequest verifyChallengeRequest,
String transactionId) throws SignUpException {

Expand Down Expand Up @@ -128,6 +166,21 @@ public RegisterResponse register(RegisterRequest registerRequest, String transac
return registration;
}

public RegistrationStatusResponse getRegistrationStatus(String transactionId)
throws SignUpException {
if (transactionId == null || transactionId.isEmpty())
throw new InvalidTransactionException();

RegistrationTransaction registrationTransaction = cacheUtilService.getRegisteredTransaction(
transactionId);
if (registrationTransaction == null)
throw new InvalidTransactionException();

RegistrationStatusResponse registrationStatusResponse = new RegistrationStatusResponse();
registrationStatusResponse.setStatus(registrationTransaction.getRegistrationStatus());
return registrationStatusResponse;
}

private void saveIdentityData(RegisterRequest registerRequest, String transactionId, String applicationId) throws SignUpException{

UserInfoMap userInfoMap = registerRequest.getUserInfo();
Expand Down Expand Up @@ -212,49 +265,6 @@ private String getUniqueIdentifier(String transactionId) throws SignUpException
restResponseWrapper.getErrors().get(0).getErrorCode() : ErrorConstants.GET_UIN_FAILED);
}

public GenerateChallengeResponse generateChallenge(GenerateChallengeRequest generateChallengeRequest, String transactionId) throws SignUpException {
if (!googleRecaptchaValidatorService.validateCaptcha(generateChallengeRequest.getCaptchaToken())) {
log.error("generate-challenge failed: invalid captcha");
throw new CaptchaException(ErrorConstants.INVALID_CAPTCHA);
}

String identifier = generateChallengeRequest.getIdentifier();
RegistrationTransaction transaction = null;
if (!transactionId.isEmpty()) {
transaction = cacheUtilService.getChallengeGeneratedTransaction(transactionId);
validateTransaction(transaction, identifier);
}

if(transaction == null) {
transactionId = IdentityProviderUtil.createTransactionId(null);
transaction = new RegistrationTransaction(identifier, transactionId);
}

// generate Challenge
String challenge = challengeManagerService.generateChallenge(transaction);
String challengeHash = IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, challenge);
addCookieResponse(transactionId);
transaction.setChallengeHash(challengeHash);
transaction.increaseAttempt();
cacheUtilService.setChallengeGeneratedTransaction(transactionId, transaction);
return new GenerateChallengeResponse(ActionStatus.SUCCESS);
}

public RegistrationStatusResponse getRegistrationStatus(String transactionId)
throws SignUpException {
if (transactionId == null || transactionId.isEmpty())
throw new InvalidTransactionException();

RegistrationTransaction registrationTransaction = cacheUtilService.getRegisteredTransaction(
transactionId);
if (registrationTransaction == null)
throw new InvalidTransactionException();

RegistrationStatusResponse registrationStatusResponse = new RegistrationStatusResponse();
registrationStatusResponse.setStatus(registrationTransaction.getRegistrationStatus());
return registrationStatusResponse;
}

private void validateTransaction(RegistrationTransaction transaction, String identifier) {
if(transaction == null) {
log.error("generate-challenge failed: validate transaction null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public class ErrorConstants {
public static final String UNSUPPORTED_USERNAME = "unsupported_username";
public static final String CONSENT_REQUIRED = "consent_required";
public static final String INVALID_TRANSACTION="invalid_transaction";
public static final String INVALID_CHALLENGE_CHANNEL ="invalid_challenge_channel";
public static final String UNSUPPORTED_CHALLENGE_TYPE ="unsupported_challenge_type";
public static final String INVALID_CAPTCHA="invalid_captcha";
public static final String SEND_CHALLENGE_FAILED ="send_challenge_failed";
public static final String GENERATE_CHALLENGE_FAILED ="generate_challenge_failed";
public static final String ACTIVE_CHALLENGE_FOUND="active_challenge_found";
public static final String UNKNOWN_ERROR="unknown_error";
public static final String TOO_MANY_ATTEMPTS="too_many_attempts";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@
String message() default ErrorConstants.UNSUPPORTED_LANGUAGE;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };

boolean required() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ public class LanguageValidator implements ConstraintValidator<Language, String>
@Value("#{${mosip.signup.supported-languages}}")
private List<String> supportedLanguages;

private boolean required;

@Override
public void initialize(Language constraintAnnotation) {
this.required = constraintAnnotation.required();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if(value == null || value.isBlank())
return false;
if(value == null)
return this.required ? false : true;

return supportedLanguages.contains(value);
}
}
Loading

0 comments on commit b369041

Please sign in to comment.