diff --git a/binding-service-impl/pom.xml b/binding-service-impl/pom.xml
index 83f3c6db9..000295b03 100644
--- a/binding-service-impl/pom.xml
+++ b/binding-service-impl/pom.xml
@@ -29,5 +29,5 @@
esignet-core
${esignet.core.version}
-
+
diff --git a/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java b/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java
index 6f8fd1ec1..497eb729c 100644
--- a/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java
+++ b/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java
@@ -20,6 +20,7 @@
import io.mosip.esignet.api.spi.KeyBinder;
import io.mosip.esignet.core.dto.*;
import io.mosip.esignet.core.exception.EsignetException;
+import io.mosip.esignet.core.util.CaptchaHelper;
import io.mosip.esignet.repository.PublicKeyRegistryRepository;
import io.mosip.kernel.keymanagerservice.util.KeymanagerUtil;
import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
@@ -54,12 +55,16 @@ public class KeyBindingServiceImpl implements KeyBindingService {
@Autowired
private KeyBindingHelperService keyBindingHelperService;
+ @Autowired
+ private CaptchaHelper captchaHelper;
+
@Value("${mosip.esignet.binding.encrypt-binding-id:true}")
private boolean encryptBindingId;
@Override
- public BindingOtpResponse sendBindingOtp(BindingOtpRequest bindingOtpRequest, Map requestHeaders) throws EsignetException {
+ public BindingOtpResponse sendBindingOtp(BindingOtpRequest bindingOtpRequest, Map requestHeaders)
+ throws EsignetException {
log.debug("sendBindingOtp :: Request headers >> {}", requestHeaders);
SendOtpResult sendOtpResult;
try {
@@ -69,18 +74,24 @@ public BindingOtpResponse sendBindingOtp(BindingOtpRequest bindingOtpRequest, Ma
log.error("Failed to send binding otp: {}", e);
throw new EsignetException(e.getErrorCode());
}
-
if (sendOtpResult == null) {
log.error("send-otp Failed wrapper returned null result!");
throw new EsignetException(SEND_OTP_FAILED);
}
-
BindingOtpResponse otpResponse = new BindingOtpResponse();
otpResponse.setMaskedEmail(sendOtpResult.getMaskedEmail());
otpResponse.setMaskedMobile(sendOtpResult.getMaskedMobile());
return otpResponse;
}
+ @Override
+ public BindingOtpResponse sendBindingOtpV2(BindingOtpRequestV2 bindingOtpRequestV2, Map requestHeaders)
+ throws EsignetException {
+ captchaHelper.validateCaptchaToken(bindingOtpRequestV2.getCaptchaToken(), "binding-otp");
+ return sendBindingOtp(bindingOtpRequestV2, requestHeaders);
+ }
+
+
private void validateChallengeListAuthFormat(List challengeList){
if(!challengeList.stream().allMatch(challenge->keyBindingWrapper.getSupportedChallengeFormats(challenge.getAuthFactorType()).
contains(challenge.getFormat()))) {
diff --git a/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java b/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java
index 582ce1eb2..5703cf4e2 100644
--- a/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java
+++ b/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java
@@ -5,21 +5,9 @@
*/
package io.mosip.esignet;
-import static io.mosip.esignet.api.util.ErrorConstants.SEND_OTP_FAILED;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.time.LocalDateTime;
-import java.util.*;
-
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import io.mosip.esignet.api.dto.AuthChallenge;
@@ -28,10 +16,14 @@
import io.mosip.esignet.api.exception.KeyBindingException;
import io.mosip.esignet.api.exception.SendOtpException;
import io.mosip.esignet.api.spi.KeyBinder;
-import io.mosip.esignet.entity.PublicKeyRegistry;
-import io.mosip.esignet.core.dto.*;
-import io.mosip.esignet.core.exception.EsignetException;
import io.mosip.esignet.core.constants.ErrorConstants;
+import io.mosip.esignet.core.dto.BindingOtpRequest;
+import io.mosip.esignet.core.dto.BindingOtpRequestV2;
+import io.mosip.esignet.core.dto.BindingOtpResponse;
+import io.mosip.esignet.core.dto.WalletBindingRequest;
+import io.mosip.esignet.core.exception.EsignetException;
+import io.mosip.esignet.core.util.CaptchaHelper;
+import io.mosip.esignet.entity.PublicKeyRegistry;
import io.mosip.esignet.repository.PublicKeyRegistryRepository;
import io.mosip.esignet.services.KeyBindingHelperService;
import io.mosip.esignet.services.KeyBindingServiceImpl;
@@ -47,9 +39,21 @@
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.client.RestTemplate;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.nimbusds.jose.jwk.JWK;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static io.mosip.esignet.api.util.ErrorConstants.SEND_OTP_FAILED;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
@@ -70,10 +74,16 @@ public class KeyBindingServiceTest {
@Mock
KeymanagerUtil keymanagerUtil;
+ CaptchaHelper captchaHelper;
+
+ @Mock
+ RestTemplate restTemplate;
+
private JWK clientJWK = generateJWK_RSA();
private ObjectMapper objectMapper = new ObjectMapper();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -82,13 +92,17 @@ public void setUp() {
mockKeyBindingWrapperService = mock(KeyBinder.class);
when(mockKeyBindingWrapperService.getSupportedChallengeFormats(Mockito.anyString()))
.thenReturn(Arrays.asList("jwt", "alpha-numeric"));
+
+ captchaHelper = new CaptchaHelper(restTemplate, "https://api-internal.camdgc-dev1.mosip.net/v1/captcha/validatecaptcha",
+ "esignet", List.of("binding-otp"));
+
ReflectionTestUtils.setField(keyBindingService, "keyBindingWrapper", mockKeyBindingWrapperService);
keyBindingHelperService = mock(KeyBindingHelperService.class);
ReflectionTestUtils.setField(keyBindingHelperService, "saltLength", 10);
ReflectionTestUtils.setField(keyBindingHelperService, "publicKeyRegistryRepository", publicKeyRegistryRepository);
ReflectionTestUtils.setField(keyBindingHelperService, "keymanagerUtil", keymanagerUtil);
-
+ ReflectionTestUtils.setField(keyBindingService, "captchaHelper", captchaHelper);
ReflectionTestUtils.setField(keyBindingService, "keyBindingHelperService", keyBindingHelperService);
}
@@ -106,7 +120,8 @@ public void sendBindingOtp_withValidDetails_thenPass() throws SendOtpException {
BindingOtpResponse otpResponse = keyBindingService.sendBindingOtp(otpRequest, headers);
Assert.assertNotNull(otpResponse);
}
-
+
+
@Test(expected = EsignetException.class)
public void sendBindingOtp_withInvalidRequest_thenFail() throws SendOtpException {
BindingOtpRequest otpRequest = new BindingOtpRequest();
@@ -119,6 +134,41 @@ public void sendBindingOtp_withInvalidRequest_thenFail() throws SendOtpException
keyBindingService.sendBindingOtp(otpRequest, headers);
}
+
+ @Test
+ public void sendBindingOtpV2_withInvalidCaptcha_thenFail() throws SendOtpException {
+
+ BindingOtpRequestV2 otpRequest = new BindingOtpRequestV2();
+ otpRequest.setIndividualId("8267411571");
+ otpRequest.setOtpChannels(Arrays.asList("OTP"));
+ otpRequest.setCaptchaToken("qwerty");
+
+ Map headers = new HashMap<>();
+
+ try {
+ keyBindingService.sendBindingOtpV2(otpRequest, headers);
+ } catch (EsignetException e) {
+ Assert.assertTrue(e.getErrorCode().equals(ErrorConstants.INVALID_CAPTCHA));
+ }
+ }
+
+ @Test
+ public void sendBindingOtpV2_withEmptyCaptcha_thenFail() throws SendOtpException {
+
+ BindingOtpRequestV2 otpRequest = new BindingOtpRequestV2();
+ otpRequest.setIndividualId("8267411571");
+ otpRequest.setOtpChannels(Arrays.asList("OTP"));
+ otpRequest.setCaptchaToken("");
+
+ Map headers = new HashMap<>();
+
+ try {
+ keyBindingService.sendBindingOtpV2(otpRequest, headers);
+ } catch (EsignetException e) {
+ Assert.assertTrue(e.getErrorCode().equals(ErrorConstants.INVALID_CAPTCHA));
+ }
+ }
+
@Test
public void sendBindingOtp_withNullResponseFromWrapper_thenFail() throws SendOtpException {
BindingOtpRequest otpRequest = new BindingOtpRequest();
@@ -368,4 +418,5 @@ public static JWK generateJWK_RSA() {
}
return null;
}
+
}
\ No newline at end of file
diff --git a/docs/esignet-openapi.yaml b/docs/esignet-openapi.yaml
index c042380ca..2d40f0043 100644
--- a/docs/esignet-openapi.yaml
+++ b/docs/esignet-openapi.yaml
@@ -4428,6 +4428,112 @@ paths:
- url: 'https://esignet.collab.mosip.net/v1/esignet'
x-stoplight:
id: t315hcecaulyy
+ /binding/v2/binding-otp:
+ post:
+ tags:
+ - WALLET BACKEND
+ summary: Send Binding OTP Endpoint
+ description: Send wallet binding OTP endpoint is invoked by Mimoto server.
+ operationId: post-binding-otp
+ parameters:
+ - name: partner-api-key
+ in: header
+ description: 'API key of the binding partner, this will be passed to binder implementation to interact with authentication system.'
+ schema:
+ type: string
+ - name: partner-id
+ in: header
+ description: 'Binding partner Identifier, this will be passed to binder implementation to interact with authentication system.'
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ requestTime:
+ type: string
+ request:
+ type: object
+ properties:
+ individualId:
+ type: string
+ description: User Id (UIN/VID)
+ otpChannels:
+ type: array
+ description: Channels to which OTP should be delivered.
+ items:
+ type: string
+ captchaToken:
+ type: string
+ description: 'Captcha token, if enabled.'
+ required:
+ - individualId
+ - otpChannels
+ required:
+ - requestTime
+ - request
+ examples:
+ Example 1:
+ value:
+ requestTime: '2023-09-22T08:01:13.000Z'
+ request:
+ individualId: '24554655645'
+ otpChannels:
+ - sms
+ - email
+ captchaToken: ALSKDJFURIEOQPZMKFURHFVBH
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ responseTIme:
+ type: string
+ response:
+ type: object
+ properties:
+ maskedEmail:
+ type: string
+ description: Masked email id of the individualId user.
+ maskedMobile:
+ type: string
+ description: Masked mobile number of the individualId user.
+ errors:
+ type: array
+ items:
+ type: object
+ properties:
+ errorCode:
+ type: string
+ enum:
+ - invalid_otp_channel
+ - unknown_error
+ - invalid_individual_id
+ - send_otp_failed
+ - invalid_captcha
+ errorMessage:
+ type: string
+ required:
+ - responseTIme
+ examples:
+ Example 1:
+ value:
+ responseTIme: '2023-09-22T08:01:16.000Z'
+ response:
+ maskedEmail: XXdXXaXXhXXkX@gmail.com
+ maskedMobile: XXXXXXX357934
+ errors: [ ]
+ security:
+ - Authorization-send_binding_otp: [ ]
+ servers:
+ - url: 'https://esignet.collab.mosip.net/v1/esignet'
+ x-stoplight:
+ id: xnl3gyq4v4bh4
/binding/wallet-binding:
post:
tags:
@@ -5290,8 +5396,6 @@ components:
-
-
diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/config/SharedComponentConfig.java b/esignet-core/src/main/java/io/mosip/esignet/core/config/SharedComponentConfig.java
index 40643213f..a0e418941 100644
--- a/esignet-core/src/main/java/io/mosip/esignet/core/config/SharedComponentConfig.java
+++ b/esignet-core/src/main/java/io/mosip/esignet/core/config/SharedComponentConfig.java
@@ -7,6 +7,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
+import java.util.List;
+
@Configuration
public class SharedComponentConfig {
@@ -15,7 +17,8 @@ public class SharedComponentConfig {
@Bean
public CaptchaHelper captchaHelper(@Value("${mosip.esignet.captcha.validator-url}") String validatorUrl,
- @Value("${mosip.esignet.captcha.module-name}") String moduleName) {
- return new CaptchaHelper(restTemplate, validatorUrl, moduleName);
+ @Value("${mosip.esignet.captcha.module-name}") String moduleName,
+ @Value("#{'${mosip.esignet.captcha.required}'}")List captchaRequired) {
+ return new CaptchaHelper(restTemplate, validatorUrl, moduleName, captchaRequired);
}
}
diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequest.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequest.java
index a9b2dd872..9aa090885 100644
--- a/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequest.java
+++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequest.java
@@ -24,4 +24,5 @@ public class BindingOtpRequest {
@NotNull(message = ErrorConstants.INVALID_OTP_CHANNEL)
@Size(min = 1, message = ErrorConstants.INVALID_OTP_CHANNEL)
private List<@OtpChannel String> otpChannels;
+
}
diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequestV2.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequestV2.java
new file mode 100644
index 000000000..9086b1414
--- /dev/null
+++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/BindingOtpRequestV2.java
@@ -0,0 +1,15 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package io.mosip.esignet.core.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class BindingOtpRequestV2 extends BindingOtpRequest{
+ private String captchaToken;
+}
diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/KeyBindingService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/KeyBindingService.java
index f927e7e66..ab80bcb5f 100644
--- a/esignet-core/src/main/java/io/mosip/esignet/core/spi/KeyBindingService.java
+++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/KeyBindingService.java
@@ -5,11 +5,8 @@
*/
package io.mosip.esignet.core.spi;
-import io.mosip.esignet.core.dto.BindingOtpResponse;
+import io.mosip.esignet.core.dto.*;
import io.mosip.esignet.core.exception.EsignetException;
-import io.mosip.esignet.core.dto.BindingOtpRequest;
-import io.mosip.esignet.core.dto.WalletBindingRequest;
-import io.mosip.esignet.core.dto.WalletBindingResponse;
import java.util.Map;
@@ -17,5 +14,7 @@ public interface KeyBindingService {
BindingOtpResponse sendBindingOtp(BindingOtpRequest otpRequest, Map requestHeaders) throws EsignetException;
+ BindingOtpResponse sendBindingOtpV2(BindingOtpRequestV2 otpRequest, Map requestHeaders) throws EsignetException;
+
WalletBindingResponse bindWallet(WalletBindingRequest walletBindingRequest, Map requestHeaders) throws EsignetException;
}
diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/util/CaptchaHelper.java b/esignet-core/src/main/java/io/mosip/esignet/core/util/CaptchaHelper.java
index f6ce7808e..e53147559 100644
--- a/esignet-core/src/main/java/io/mosip/esignet/core/util/CaptchaHelper.java
+++ b/esignet-core/src/main/java/io/mosip/esignet/core/util/CaptchaHelper.java
@@ -11,16 +11,22 @@
import io.mosip.esignet.core.dto.ResponseWrapper;
import io.mosip.esignet.core.exception.EsignetException;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.*;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.util.List;
import static io.mosip.esignet.core.constants.Constants.UTC_DATETIME_PATTERN;
+import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CAPTCHA;
@Slf4j
public class CaptchaHelper {
@@ -29,12 +35,29 @@ public class CaptchaHelper {
private String moduleName;
private String validatorUrl;
- public CaptchaHelper(RestTemplate restTemplate, String validatorUrl, String moduleName) {
+ private List captchaRequired;
+
+ public CaptchaHelper(RestTemplate restTemplate, String validatorUrl, String moduleName, List captchaRequired) {
this.restTemplate = restTemplate;
this.validatorUrl = validatorUrl;
this.moduleName = moduleName;
+ this.captchaRequired = captchaRequired;
+ }
+
+ public void validateCaptchaToken(String captchaToken, String authFactor) {
+ if(!captchaRequired.contains(authFactor)) {
+ log.warn("captcha validation is disabled for {} request!", authFactor);
+ return;
+ }
+ if(!StringUtils.hasText(captchaToken)) {
+ log.error("Captcha token is Null or Empty");
+ throw new EsignetException(INVALID_CAPTCHA);
+ }
+ if (!validateCaptcha(captchaToken))
+ throw new EsignetException(INVALID_CAPTCHA);
}
+
public boolean validateCaptcha(String captchaToken) {
if (captchaToken == null || captchaToken.isBlank()) {
diff --git a/esignet-core/src/test/java/io/mosip/esignet/core/CaptchaHelperTest.java b/esignet-core/src/test/java/io/mosip/esignet/core/CaptchaHelperTest.java
index 95b8bc056..ec7dbc8ff 100644
--- a/esignet-core/src/test/java/io/mosip/esignet/core/CaptchaHelperTest.java
+++ b/esignet-core/src/test/java/io/mosip/esignet/core/CaptchaHelperTest.java
@@ -1,5 +1,6 @@
package io.mosip.esignet.core;
+import io.mosip.esignet.core.constants.ErrorConstants;
import io.mosip.esignet.core.dto.ResponseWrapper;
import io.mosip.esignet.core.exception.EsignetException;
import io.mosip.esignet.core.util.CaptchaHelper;
@@ -7,10 +8,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.*;
@@ -18,8 +17,8 @@
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import static org.mockito.ArgumentMatchers.any;
@@ -35,7 +34,28 @@ public class CaptchaHelperTest {
@Before
public void setUp() {
captchaHelper = new CaptchaHelper(restTemplate, "https://api-internal.camdgc-dev1.mosip.net/v1/captcha/validatecaptcha",
- "esignet");
+ "esignet", List.of("binding-otp"));
+ }
+
+ @Test
+ public void validateCaptchaToken_withEmptyToken_thenFail() {
+ ReflectionTestUtils.setField(captchaHelper, "captchaRequired", List.of("binding-otp"));
+ try {
+ captchaHelper.validateCaptchaToken("", "binding-otp");
+ } catch(EsignetException e) {
+ Assert.assertEquals(ErrorConstants.INVALID_CAPTCHA, e.getErrorCode());
+ }
+ }
+
+
+ @Test
+ public void validateCaptchaToken_withInValidToken_thenFail() {
+ ReflectionTestUtils.setField(captchaHelper, "captchaRequired", List.of("binding-otp"));
+ try {
+ captchaHelper.validateCaptchaToken("captcha-token", "binding-otp");
+ } catch(EsignetException e) {
+ Assert.assertEquals(ErrorConstants.INVALID_CAPTCHA, e.getErrorCode());
+ }
}
@Test
@@ -48,6 +68,18 @@ public void validateCaptcha_withEmptyCaptchaToken_thenFail() {
Assert.assertThrows(EsignetException.class,()->captchaHelper.validateCaptcha(""));
}
+ @Test
+ public void validateCaptchaToken_withValidData_thenPass() {
+ ResponseWrapper responseWrapper = new ResponseWrapper();
+ responseWrapper.setResponse("success");
+ ResponseEntity responseEntity = ResponseEntity.ok(responseWrapper);
+ Mockito.when(restTemplate.exchange(Mockito.any(RequestEntity.class), Mockito.eq(ResponseWrapper.class)))
+ .thenReturn(responseEntity);
+ boolean result = captchaHelper.validateCaptcha("captchaToken");
+ Assert.assertTrue(result);
+ }
+
+
@Test
public void validateCaptcha_withNullResponse_thenFail() {
Mockito.when(restTemplate.exchange((RequestEntity>) any(), (Class