Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOSIP-29931 Fix to store hash of otp and key #1457

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
public enum SqlQueryConstants {
UPDATE("UPDATE"), ID("id"), NEW_OTP_STATUS("newOtpStatus"), NEW_NUM_OF_ATTEMPT("newNumOfAttempt"),
NEW_VALIDATION_TIME("newValidationTime");
NEW_VALIDATION_TIME("newValidationTime"), REF_ID("refId");

/**
* The property.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.mosip.kernel.otpmanager.repository;

import java.util.Optional;

import org.springframework.stereotype.Repository;

import io.mosip.kernel.core.dataaccess.spi.repository.BaseRepository;
Expand All @@ -16,4 +18,5 @@
*/
@Repository
public interface OtpRepository extends BaseRepository<OtpEntity, String> {
Optional<OtpEntity> findByRefId(String refId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
Expand Down Expand Up @@ -33,7 +32,6 @@ public class OtpGeneratorServiceImpl implements OtpGenerator<OtpGeneratorRequest
/**
* The reference that autowires OtpRepository class.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OtpGeneratorServiceImpl.class);
@Autowired
private OtpRepository otpRepository;

Expand Down Expand Up @@ -89,9 +87,10 @@ public OtpGeneratorResponseDto getOtp(OtpGeneratorRequestDto otpDto) {
/*
* Checking whether the key exists in the repository.
*/
OtpEntity keyCheck = otpRepository.findById(OtpEntity.class, otpDto.getKey());
if ((keyCheck != null) && (keyCheck.getStatusCode().equals(OtpStatusConstants.KEY_FREEZED.getProperty()))
&& (OtpManagerUtils.timeDifferenceInSeconds(keyCheck.getUpdatedDtimes(),
String refIdHash = OtpManagerUtils.getHash(otpDto.getKey());
Optional<OtpEntity> entityOpt = otpRepository.findByRefId(refIdHash);
if (entityOpt.isPresent() && (entityOpt.get().getStatusCode().equals(OtpStatusConstants.KEY_FREEZED.getProperty()))
&& (OtpManagerUtils.timeDifferenceInSeconds(entityOpt.get().getUpdatedDtimes(),
LocalDateTime.now(ZoneId.of("UTC"))) <= Integer.parseInt(keyFreezeTime))) {
response.setOtp(OtpStatusConstants.SET_AS_NULL_IN_STRING.getProperty());
response.setStatus(OtpStatusConstants.BLOCKED_USER.getProperty());
Expand All @@ -103,9 +102,9 @@ public OtpGeneratorResponseDto getOtp(OtpGeneratorRequestDto otpDto) {
}

OtpEntity otp = new OtpEntity();
otp.setId(otpDto.getKey());
otp.setId(OtpManagerUtils.getKeyOtpHash(otpDto.getKey(), generatedOtp));
otp.setRefId(refIdHash);
otp.setValidationRetryCount(0);
otp.setOtp(generatedOtp);
otpRepository.save(otp);
response.setOtp(generatedOtp);
response.setStatus(OtpStatusConstants.GENERATION_SUCCESSFUL.getProperty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -35,6 +36,11 @@
@RefreshScope
@Service
public class OtpValidatorServiceImpl implements OtpValidator<ResponseEntity<OtpValidatorResponseDto>> {

private static final String UPDATE_VALIDATION_RETRY_COUNT_QUERY = "%s %s SET validation_retry_count = :newNumOfAttempt,upd_dtimes = :newValidationTime WHERE refId=:refId";

private static final String UPDATE_STATUS_CODE_AND_RETRY_COUNT_QUERY = "%s %s SET status_code = :newOtpStatus, validation_retry_count = :newNumOfAttempt, upd_dtimes = :newValidationTime WHERE refId=:refId";

/**
* The reference that autowires OtpRepository.
*/
Expand Down Expand Up @@ -82,31 +88,33 @@ public ResponseEntity<OtpValidatorResponseDto> validateOtp(String key, String ot
OtpValidatorResponseDto responseDto;

// The OTP entity for a specific key.
OtpEntity otpResponse = otpRepository.findById(OtpEntity.class, key);
String refIdHash = OtpManagerUtils.getHash(key);
Optional<OtpEntity> otpEntityOpt = otpRepository.findByRefId(refIdHash);
responseDto = new OtpValidatorResponseDto();
responseDto.setMessage(OtpStatusConstants.FAILURE_MESSAGE.getProperty());
responseDto.setStatus(OtpStatusConstants.FAILURE_STATUS.getProperty());
validationResponseEntity = new ResponseEntity<>(responseDto, HttpStatus.OK);

requireKeyNotFound(otpResponse);
requireKeyNotFound(otpEntityOpt);
// This variable holds the update query to be performed.
String updateString;
// This variable holds the count of number
int attemptCount = otpResponse.getValidationRetryCount();
if ((OtpManagerUtils.timeDifferenceInSeconds(otpResponse.getGeneratedDtimes(),
OtpEntity otpEntity = otpEntityOpt.get();
int attemptCount = otpEntity.getValidationRetryCount();
if ((OtpManagerUtils.timeDifferenceInSeconds(otpEntity.getGeneratedDtimes(),
OtpManagerUtils.getCurrentLocalDateTime())) > (Integer.parseInt(otpExpiryLimit))) {

responseDto.setStatus(OtpStatusConstants.FAILURE_STATUS.getProperty());
responseDto.setMessage(OtpStatusConstants.OTP_EXPIRED_STATUS.getProperty());
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
String keyOtpHash = OtpManagerUtils.getKeyOtpHash(key, otp);
// This condition increases the validation attempt count.
if ((attemptCount < Integer.parseInt(numberOfValidationAttemptsAllowed))
&& (otpResponse.getStatusCode().equals(OtpStatusConstants.UNUSED_OTP.getProperty()))) {
updateString = SqlQueryConstants.UPDATE.getProperty() + " " + OtpEntity.class.getSimpleName()
+ " SET validation_retry_count = :newNumOfAttempt,"
+ "upd_dtimes = :newValidationTime WHERE id=:id";
HashMap<String, Object> updateMap = createUpdateMap(key, null, attemptCount + 1,
&& (otpEntity.getStatusCode().equals(OtpStatusConstants.UNUSED_OTP.getProperty()))) {
updateString = String.format(UPDATE_VALIDATION_RETRY_COUNT_QUERY, SqlQueryConstants.UPDATE.getProperty(),
OtpEntity.class.getSimpleName());
HashMap<String, Object> updateMap = createUpdateMap(otpEntity.getRefId(), null, attemptCount + 1,
LocalDateTime.now(ZoneId.of("UTC")));
updateData(updateString, updateMap);
}
Expand All @@ -115,11 +123,10 @@ public ResponseEntity<OtpValidatorResponseDto> validateOtp(String key, String ot
* reaches the maximum allowed limit.
*/
if ((attemptCount == Integer.parseInt(numberOfValidationAttemptsAllowed) - 1)
&& (!otp.equals(otpResponse.getOtp()))) {
updateString = SqlQueryConstants.UPDATE.getProperty() + " " + OtpEntity.class.getSimpleName()
+ " SET status_code = :newOtpStatus," + "upd_dtimes = :newValidationTime,"
+ "validation_retry_count = :newNumOfAttempt WHERE id=:id";
HashMap<String, Object> updateMap = createUpdateMap(key, OtpStatusConstants.KEY_FREEZED.getProperty(), 0,
&& (!keyOtpHash.equals(otpEntity.getId()))) {
updateString = String.format(UPDATE_STATUS_CODE_AND_RETRY_COUNT_QUERY, SqlQueryConstants.UPDATE.getProperty(),
OtpEntity.class.getSimpleName());
HashMap<String, Object> updateMap = createUpdateMap(otpEntity.getRefId(), OtpStatusConstants.KEY_FREEZED.getProperty(), 0,
OtpManagerUtils.getCurrentLocalDateTime());
updateData(updateString, updateMap);
responseDto.setStatus(OtpStatusConstants.FAILURE_STATUS.getProperty());
Expand All @@ -128,7 +135,7 @@ public ResponseEntity<OtpValidatorResponseDto> validateOtp(String key, String ot
return validationResponseEntity;

}
validationResponseEntity = unFreezeKey(key, otp, otpResponse, attemptCount, responseDto,
validationResponseEntity = unFreezeKey(keyOtpHash, otpEntity, attemptCount, responseDto,
validationResponseEntity);
/*
* This condition validates the OTP if neither the key is in freezed condition,
Expand All @@ -137,24 +144,24 @@ public ResponseEntity<OtpValidatorResponseDto> validateOtp(String key, String ot
* is expired, the specific message is returned as response and the entire
* record is deleted.
*/
if ((otpResponse.getOtp().equals(otp))
&& (otpResponse.getStatusCode().equals(OtpStatusConstants.UNUSED_OTP.getProperty())
&& ((OtpManagerUtils.timeDifferenceInSeconds(otpResponse.getGeneratedDtimes(),
if ((otpEntity.getId().equals(keyOtpHash))
&& (otpEntity.getStatusCode().equals(OtpStatusConstants.UNUSED_OTP.getProperty())
&& ((OtpManagerUtils.timeDifferenceInSeconds(otpEntity.getGeneratedDtimes(),
OtpManagerUtils.getCurrentLocalDateTime())) <= (Integer.parseInt(otpExpiryLimit))))) {
responseDto.setStatus(OtpStatusConstants.SUCCESS_STATUS.getProperty());
responseDto.setMessage(OtpStatusConstants.SUCCESS_MESSAGE.getProperty());
otpRepository.deleteById(key);
otpRepository.deleteById(keyOtpHash);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
return validationResponseEntity;
}

private void requireKeyNotFound(OtpEntity otpResponse) {
private void requireKeyNotFound(Optional<OtpEntity> entityOpt) {
/*
* Checking whether the key exists in repository or not. If not, throw an
* exception.
*/
if (otpResponse == null) {
if (entityOpt.isEmpty()) {
List<ServiceError> validationErrorsList = new ArrayList<>();
validationErrorsList.add(new ServiceError(OtpErrorConstants.OTP_VAL_KEY_NOT_FOUND.getErrorCode(),
OtpErrorConstants.OTP_VAL_KEY_NOT_FOUND.getErrorMessage()));
Expand Down Expand Up @@ -186,29 +193,28 @@ private ResponseEntity<OtpValidatorResponseDto> proxyForLocalProfile(String otp)
*
* @param key the key.
* @param otp the OTP.
* @param otpResponse the OTP response.
* @param otpEntity the OTP response.
* @param attemptCount the attempt count.
* @param responseDto the response dto.
* @param validationResponseEntity the validation response entity.
* @return the response entity.
*/
private ResponseEntity<OtpValidatorResponseDto> unFreezeKey(String key, String otp, OtpEntity otpResponse,
private ResponseEntity<OtpValidatorResponseDto> unFreezeKey(String keyOtpHash, OtpEntity otpEntity,
int attemptCount, OtpValidatorResponseDto responseDto,
ResponseEntity<OtpValidatorResponseDto> validationResponseEntity) {
String updateString;
if (otpResponse.getStatusCode().equals(OtpStatusConstants.KEY_FREEZED.getProperty())) {
if ((OtpManagerUtils.timeDifferenceInSeconds(otpResponse.getUpdatedDtimes(),
if (otpEntity.getStatusCode().equals(OtpStatusConstants.KEY_FREEZED.getProperty())) {
if ((OtpManagerUtils.timeDifferenceInSeconds(otpEntity.getUpdatedDtimes(),
OtpManagerUtils.getCurrentLocalDateTime())) > (Integer.parseInt(keyFreezeDuration))) {
updateString = SqlQueryConstants.UPDATE.getProperty() + " " + OtpEntity.class.getSimpleName()
+ " SET status_code = :newOtpStatus," + " validation_retry_count = :newNumOfAttempt,"
+ " upd_dtimes = :newValidationTime WHERE id=:id";
HashMap<String, Object> updateMap = createUpdateMap(key, OtpStatusConstants.UNUSED_OTP.getProperty(),
updateString = String.format(UPDATE_STATUS_CODE_AND_RETRY_COUNT_QUERY, SqlQueryConstants.UPDATE.getProperty(),
OtpEntity.class.getSimpleName());
HashMap<String, Object> updateMap = createUpdateMap(otpEntity.getRefId(), OtpStatusConstants.UNUSED_OTP.getProperty(),
Integer.valueOf(attemptCount + 1), OtpManagerUtils.getCurrentLocalDateTime());
if (otp.equals(otpResponse.getOtp())) {
if (keyOtpHash.equals(otpEntity.getId())) {
responseDto.setStatus(OtpStatusConstants.SUCCESS_STATUS.getProperty());
responseDto.setMessage(OtpStatusConstants.SUCCESS_MESSAGE.getProperty());
validationResponseEntity = new ResponseEntity<>(responseDto, HttpStatus.OK);
otpRepository.deleteById(key);
otpRepository.deleteById(keyOtpHash);
} else {
updateData(updateString, updateMap);
}
Expand All @@ -233,7 +239,7 @@ private HashMap<String, Object> createUpdateMap(String key, String status, Integ
LocalDateTime localDateTime) {
HashMap<String, Object> updateMap = new HashMap<>();
if (key != null) {
updateMap.put(SqlQueryConstants.ID.getProperty(), key);
updateMap.put(SqlQueryConstants.REF_ID.getProperty(), key);
}
if (status != null) {
updateMap.put(SqlQueryConstants.NEW_OTP_STATUS.getProperty(), status);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.mosip.kernel.otpmanager.util;

import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
Expand All @@ -10,7 +11,9 @@
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import io.mosip.kernel.core.exception.BaseUncheckedException;
import io.mosip.kernel.core.exception.ServiceError;
import io.mosip.kernel.core.util.HMACUtils2;
import io.mosip.kernel.core.util.StringUtils;
import io.mosip.kernel.otpmanager.constant.OtpErrorConstants;
import io.mosip.kernel.otpmanager.exception.OtpInvalidArgumentException;
Expand All @@ -33,6 +36,8 @@ public class OtpManagerUtils {

@Value("${mosip.kernel.otp.max-key-length}")
String keyMaxLength;

private static final String KEY_OTP_SEPARATOR = ":";

/**
* This method returns the difference between two LocalDateTime objects in
Expand Down Expand Up @@ -88,4 +93,17 @@ public void validateOtpRequestArguments(String key, String otp) {
throw new OtpInvalidArgumentException(validationErrorsList);
}
}

public static String getKeyOtpHash(String key, String otp) {
return getHash(key + KEY_OTP_SEPARATOR + otp);
}

public static String getHash(String string) {
try {
return HMACUtils2.digestAsPlainText(string.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new BaseUncheckedException(OtpErrorConstants.OTP_GEN_ALGO_FAILURE.getErrorCode(),
OtpErrorConstants.OTP_GEN_ALGO_FAILURE.getErrorMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -40,12 +41,14 @@ public class OtpValidatorServiceTest {
@Test
public void testOtpValidatorServicePositiveCase() throws Exception {
OtpEntity entity = new OtpEntity();
entity.setOtp("1234");
entity.setId("testKey");
//Hash of testKey:1234
entity.setId("6DB5C886D3E9375E2C7BFBCE326A708734836151585059CD38F3CF586A125732");
// Hash of testKey
entity.setRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F");
entity.setValidationRetryCount(0);
entity.setStatusCode("OTP_UNUSED");
entity.setUpdatedDtimes(LocalDateTime.now(ZoneId.of("UTC")).plusSeconds(50));
when(repository.findById(OtpEntity.class, "testKey")).thenReturn(entity);
when(repository.findByRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F")).thenReturn(Optional.of(entity));
mockMvc.perform(get("/otp/validate?key=testKey&otp=1234").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.response.status", is("success")));
}
Expand All @@ -54,12 +57,14 @@ public void testOtpValidatorServicePositiveCase() throws Exception {
@Test
public void testOtpValidatorServiceNegativeCase() throws Exception {
OtpEntity entity = new OtpEntity();
entity.setOtp("1234");
entity.setId("testKey");
//Hash of testKey:1234
entity.setId("6DB5C886D3E9375E2C7BFBCE326A708734836151585059CD38F3CF586A125732");
// Hash of testKey
entity.setRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F");
entity.setValidationRetryCount(0);
entity.setStatusCode("OTP_UNUSED");
entity.setUpdatedDtimes(LocalDateTime.now());
when(repository.findById(OtpEntity.class, "testKey")).thenReturn(entity);
when(repository.findByRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F")).thenReturn(Optional.of(entity));
mockMvc.perform(get("/otp/validate?key=testKey&otp=5431").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.response.status", is("failure")));
}
Expand All @@ -68,12 +73,14 @@ public void testOtpValidatorServiceNegativeCase() throws Exception {
@Test
public void testOtpValidatorServiceWhenMaxAttemptReached() throws Exception {
OtpEntity entity = new OtpEntity();
entity.setOtp("1234");
entity.setId("testKey");
//Hash of 1234:testKey
entity.setId("6DB5C886D3E9375E2C7BFBCE326A708734836151585059CD38F3CF586A125732");
// Hash of testKey
entity.setRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F");
entity.setValidationRetryCount(3);
entity.setStatusCode("OTP_UNUSED");
entity.setUpdatedDtimes(LocalDateTime.now());
when(repository.findById(OtpEntity.class, "testKey")).thenReturn(entity);
when(repository.findByRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F")).thenReturn(Optional.of(entity));
mockMvc.perform(get("/otp/validate?key=testKey&otp=5431").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.response.status", is("failure")));
}
Expand All @@ -82,12 +89,14 @@ public void testOtpValidatorServiceWhenMaxAttemptReached() throws Exception {
@Test
public void testOtpValidatorServiceWhenKeyFreezedPositiveCase() throws Exception {
OtpEntity entity = new OtpEntity();
entity.setOtp("1234");
entity.setId("testKey");
//Hash of 1234:testKey
entity.setId("6DB5C886D3E9375E2C7BFBCE326A708734836151585059CD38F3CF586A125732");
// Hash of testKey
entity.setRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F");
entity.setValidationRetryCount(3);
entity.setStatusCode("KEY_FREEZED");
entity.setUpdatedDtimes(LocalDateTime.now(ZoneId.of("UTC")).minus(1, ChronoUnit.MINUTES));
when(repository.findById(OtpEntity.class, "testKey")).thenReturn(entity);
when(repository.findByRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F")).thenReturn(Optional.of(entity));
mockMvc.perform(get("/otp/validate?key=testKey&otp=2345").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.response.status", is("failure")));
}
Expand All @@ -96,12 +105,14 @@ public void testOtpValidatorServiceWhenKeyFreezedPositiveCase() throws Exception
@Test
public void testOtpValidatorServiceWhenKeyFreezedNegativeCase() throws Exception {
OtpEntity entity = new OtpEntity();
entity.setOtp("1234");
entity.setId("testKey");
//Hash of 1234:testKey
entity.setId("6DB5C886D3E9375E2C7BFBCE326A708734836151585059CD38F3CF586A125732");
// Hash of testKey
entity.setRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F");
entity.setValidationRetryCount(0);
entity.setStatusCode("KEY_FREEZED");
entity.setUpdatedDtimes(LocalDateTime.now().minus(20, ChronoUnit.SECONDS));
when(repository.findById(OtpEntity.class, "testKey")).thenReturn(entity);
when(repository.findByRefId("15291F67D99EA7BC578C3544DADFBB991E66FA69CB36FF70FE30E798E111FF5F")).thenReturn(Optional.of(entity));
mockMvc.perform(get("/otp/validate?key=testKey&otp=1234").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$.response.status", is("failure")));
}
Expand Down