diff --git a/authentication/esignet-integration-impl/pom.xml b/authentication/esignet-integration-impl/pom.xml new file mode 100644 index 00000000000..e49686df59e --- /dev/null +++ b/authentication/esignet-integration-impl/pom.xml @@ -0,0 +1,225 @@ + + 4.0.0 + + + io.mosip.authentication + authentication-parent + 1.1.5.5-SNAPSHOT + + + esignet-integration-impl + esignet-integration-impl + e-Signet Integration Implementation Library + + + 11 + 1.1.5 + 1.1.5.3 + + + + + junit + junit + 4.13.1 + test + + + + org.projectlombok + lombok + 1.18.22 + compile + + + + io.mosip.esignet + esignet-integration-api + 1.0.0-SNAPSHOT + provided + + + org.springframework.boot + spring-boot-starter + + + + + + io.mosip.kernel + kernel-keymanager-service + 1.2.1-SNAPSHOT + provided + lib + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.security + spring-security-test + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework + spring-context + + + org.springframework + spring-jdbc + + + org.springframework + spring-aop + + + org.springframework + spring-core + + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + test + + + org.springframework.boot + spring-boot-starter-webflux + 2.3.6.RELEASE + + + io.mosip.authentication + authentication-common + ${authentication-common.version} + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + io.mosip.kernel + kernel-biosdk-provider + + + io.mosip.kernel + kernel-websubclient-api + + + io.mosip.idrepository + id-repository-core + + + io.mosip.kernel + kernel-dataaccess-hibernate + + + io.mosip.kernel + kernel-pinvalidator + + + + + io.mosip.biometric.util + biometrics-util + ${kernel-biometrics-util} + + + org.springframework.boot + spring-boot-starter-web + + + + + io.mosip.kernel + kernel-core + ${kernel-core.version} + + + org.springframework.boot + + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot + + + org.springframework + spring-context + + + org.springframework + spring-jdbc + + + org.springframework + spring-aop + + + org.springframework + spring-core + + + + + io.mosip.kernel + kernel-cbeffutil-api + ${kernel-cbeffutil-api.version} + + + io.mosip.kernel + kernel-core + + + + + io.springfox + springfox-swagger2 + ${maven.swagger.version} + + + org.springframework + spring-context + + + org.springframework + spring-aop + + + org.springframework + spring-core + + + + + diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/config/IdaConfig.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/config/IdaConfig.java new file mode 100644 index 00000000000..d648ec9c12e --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/config/IdaConfig.java @@ -0,0 +1,140 @@ +package io.mosip.authentication.esignet.integration.config; + +import org.mockito.Mockito; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import io.mosip.authentication.common.service.cache.MasterDataCache; +import io.mosip.authentication.common.service.config.IDAMappingConfig; +import io.mosip.authentication.common.service.factory.RestRequestFactory; +import io.mosip.authentication.common.service.helper.IdInfoHelper; +import io.mosip.authentication.common.service.helper.RestHelper; +import io.mosip.authentication.common.service.impl.IdInfoFetcherImpl; +import io.mosip.authentication.common.service.integration.MasterDataManager; +import io.mosip.authentication.common.service.integration.OTPManager; +import io.mosip.authentication.common.service.integration.TokenIdManager; +import io.mosip.authentication.common.service.repository.OtpTxnRepository; +import io.mosip.authentication.common.service.repository.UinHashSaltRepo; +import io.mosip.authentication.common.service.transaction.manager.IdAuthSecurityManager; +import io.mosip.authentication.core.exception.IdAuthenticationBusinessException; +import io.mosip.authentication.core.spi.demoauth.DemoNormalizer; +import io.mosip.authentication.core.spi.indauth.match.IdInfoFetcher; +import io.mosip.authentication.core.spi.notification.service.NotificationService; +import io.mosip.kernel.cbeffutil.impl.CbeffImpl; +import io.mosip.kernel.core.cbeffutil.spi.CbeffUtil; +import io.mosip.kernel.cryptomanager.service.CryptomanagerService; +import io.mosip.kernel.cryptomanager.service.impl.CryptomanagerServiceImpl; +import io.mosip.kernel.tokenidgenerator.generator.TokenIDGenerator; +import io.mosip.kernel.tokenidgenerator.service.TokenIDGeneratorService; +import io.mosip.kernel.tokenidgenerator.service.impl.TokenIDGeneratorServiceImpl; +import io.mosip.kernel.zkcryptoservice.service.spi.ZKCryptoManagerService; + +@EnableCaching +@Configuration +@Import(value= {IDAMappingConfig.class}) +public class IdaConfig { + + @Bean + public TokenIDGeneratorService getTokenIdGeneratorService() { + return new TokenIDGeneratorServiceImpl(); + } + + @Bean + public TokenIdManager getTokenIdManager() { + return new TokenIdManager(); + } + + @Bean + public TokenIDGenerator getTokenIdGenerator() { + return new TokenIDGenerator(); + } + + @Bean + public IdInfoHelper getIdInfoHelper() { + return new IdInfoHelper(); + } + + @Bean + public IdInfoFetcher getIdInfoFetcher() { + return new IdInfoFetcherImpl(); + } + + @Bean + public OTPManager getOTPManager() { + return new OTPManager(); + } + + @Bean + public CryptomanagerService getCryptomanagerService() { + return new CryptomanagerServiceImpl(); + } + + @Bean("external") + public RestHelper getRestHelper() { + // Just using mock rest helper as it is not used here + return Mockito.mock(RestHelper.class); + } + + @Bean + public NotificationService getNotificationService() { + // Just using mock rest helper as it is not used here + return Mockito.mock(NotificationService.class); + } + + @Bean + public UinHashSaltRepo getUinHashSaltRepo() { + // Just using mock rest helper as it is not used here + return Mockito.mock(UinHashSaltRepo.class); + } + + @Bean + public DemoNormalizer getDemoNormalizer() { + // Just using mock rest helper as it is not used here + return Mockito.mock(DemoNormalizer.class); + } + + @Bean + public ZKCryptoManagerService getZKCryptoManagerService() { + // Just using mock rest helper as it is not used here + return Mockito.mock(ZKCryptoManagerService.class); + } + + @Bean + public OtpTxnRepository getOtpTxnRepository() { + // Just using mock rest helper as it is not used here + return Mockito.mock(OtpTxnRepository.class); + } + + @Bean + public CbeffUtil getCbeffUtil() { + return new CbeffImpl(); + } + + @Bean + public MasterDataManager getMasterDataManager() { + return new MasterDataManager(); + } + + @Bean + public MasterDataCache getMasterDataCache() { + return new MasterDataCache() { + @Override + public void loadMasterData() throws IdAuthenticationBusinessException { + //Do nothing + } + }; + } + + @Bean + public RestRequestFactory getRestRequestFactory() { + return new RestRequestFactory(); + } + + @Bean + public IdAuthSecurityManager getIdAuthSecurityManager() { + return new IdAuthSecurityManager(); + } + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditRequest.java new file mode 100644 index 00000000000..c4551c01068 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditRequest.java @@ -0,0 +1,36 @@ +package io.mosip.authentication.esignet.integration.dto; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * The Class AuditRequestDto. + * + * @author Manoj SP + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AuditRequest { + + private String eventId; + private String eventName; + private String eventType; + private LocalDateTime actionTimeStamp; + private String hostName; + private String hostIp; + private String applicationId; + private String applicationName; + private String sessionUserId; + private String sessionUserName; + private String id; + private String idType; + private String createdBy; + private String moduleName; + private String moduleId; + private String description; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditResponse.java new file mode 100644 index 00000000000..1dcc2a4e1ae --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/AuditResponse.java @@ -0,0 +1,10 @@ +package io.mosip.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class AuditResponse { + + private boolean status; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/ClientIdSecretKeyRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/ClientIdSecretKeyRequest.java new file mode 100644 index 00000000000..e93ec34b89d --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/ClientIdSecretKeyRequest.java @@ -0,0 +1,21 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ClientIdSecretKeyRequest { + + private String clientId; + private String secretKey; + private String appId; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/Error.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/Error.java new file mode 100644 index 00000000000..04c490a42f8 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/Error.java @@ -0,0 +1,20 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Error { + + private String errorCode; + private String errorMessage; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/GetAllCertificatesResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/GetAllCertificatesResponse.java new file mode 100644 index 00000000000..94a3dc03770 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/GetAllCertificatesResponse.java @@ -0,0 +1,18 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; + +import io.mosip.esignet.api.dto.KycSigningCertificateData; +import lombok.Data; + +@Data +public class GetAllCertificatesResponse { + + private List allCertificates; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaError.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaError.java new file mode 100644 index 00000000000..e967bb5e22e --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaError.java @@ -0,0 +1,16 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class IdaError { + + private String actionMessage; + private String errorCode; + private String errorMessage; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycAuthRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycAuthRequest.java new file mode 100644 index 00000000000..c6b32275a71 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycAuthRequest.java @@ -0,0 +1,53 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; +import java.util.Map; + +import lombok.Data; + +@Data +public class IdaKycAuthRequest { + + private String id; + private String version; + private String individualId; + private String individualIdType; + private String transactionID; + private String requestTime; + private String specVersion; + private String thumbprint; + private String domainUri; + private String env; + private boolean consentObtained; + private String request; + private String requestHMAC; + private String requestSessionKey; + private Map metadata; + private List allowedKycAttributes; + private Map requestedAuth; + + @Data + public static class AuthRequest { + private String otp; + private String staticPin; + private String timestamp; + private List biometrics; + private List keyBindedTokens; + } + + @Data + public static class Biometric { + private String data; + private String hash; + private String sessionKey; + private String specVersion; + private String thumbprint; + } + + +} \ No newline at end of file diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeRequest.java new file mode 100644 index 00000000000..78a6d123e29 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeRequest.java @@ -0,0 +1,24 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; + +import lombok.Data; + +@Data +public class IdaKycExchangeRequest { + + private String id; + private String version; + private String requestTime; + private String transactionID; + private String kycToken; + private List consentObtained; + private List locales; + private String respType; + private String individualId; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeResponse.java new file mode 100644 index 00000000000..01da00c1de1 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycExchangeResponse.java @@ -0,0 +1,14 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class IdaKycExchangeResponse { + + private String encryptedKyc; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycResponse.java new file mode 100644 index 00000000000..484439e1cfd --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaKycResponse.java @@ -0,0 +1,28 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class IdaKycResponse { + + /** The Variable to hold value of kyc Status */ + private boolean kycStatus; + + /** The Variable to hold value of kyc Status */ + private boolean authStatus; + + /** The Variable to hold value of auth Token */ + private String authToken; + + private String thumbprint; + + private String sessionKey; + + /** The Variable to hold value of identity */ + private String identity; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaOtpResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaOtpResponse.java new file mode 100644 index 00000000000..4d923a203de --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaOtpResponse.java @@ -0,0 +1,14 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class IdaOtpResponse { + private String maskedEmail; + private String maskedMobile; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaResponseWrapper.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaResponseWrapper.java new file mode 100644 index 00000000000..f9ee146f622 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaResponseWrapper.java @@ -0,0 +1,22 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; + +import lombok.Data; + +@Data +public class IdaResponseWrapper { + + private String id; + private String version; + private String transactionID; + private String responseTime; + private T response; + private List errors; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpRequest.java new file mode 100644 index 00000000000..358cf0d6ae3 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpRequest.java @@ -0,0 +1,23 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; + +import lombok.Data; + +@Data +public class IdaSendOtpRequest { + + private String id; + private String version; + private String individualId; + private String individualIdType; + private String transactionID; + private String requestTime; + private List otpChannel; + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpResponse.java new file mode 100644 index 00000000000..c1ccb48ac65 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/IdaSendOtpResponse.java @@ -0,0 +1,22 @@ +/* + * 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.authentication.esignet.integration.dto; + +import java.util.List; + +import lombok.Data; + +@Data +public class IdaSendOtpResponse { + + private String id; + private String version; + private String transactionID; + private String responseTime; + private List errors; + private IdaOtpResponse response; +} + diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindedToken.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindedToken.java new file mode 100644 index 00000000000..dbe00127abb --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindedToken.java @@ -0,0 +1,12 @@ +package io.mosip.authentication.esignet.integration.dto; + + +import lombok.Data; + +@Data +public class KeyBindedToken { + + private String token; + private String type; + private String format; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingRequest.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingRequest.java new file mode 100644 index 00000000000..214a0b6708c --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingRequest.java @@ -0,0 +1,22 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +import java.util.Map; + +@Data +public class KeyBindingRequest extends IdaKycAuthRequest { + + private IdentityKeyBinding identityKeyBinding; + + @Data + public static class IdentityKeyBinding { + private Map publicKeyJWK; + private String authFactorType; + } +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingResponse.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingResponse.java new file mode 100644 index 00000000000..e223bd3c019 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/dto/KeyBindingResponse.java @@ -0,0 +1,16 @@ +/* + * 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.authentication.esignet.integration.dto; + +import lombok.Data; + +@Data +public class KeyBindingResponse { + + private String identityCertificate; + private String authToken; + private boolean bindingAuthStatus; +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/AuthTransactionHelper.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/AuthTransactionHelper.java new file mode 100644 index 00000000000..6a1873abb92 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/AuthTransactionHelper.java @@ -0,0 +1,79 @@ +package io.mosip.authentication.esignet.integration.helper; + +import java.time.LocalDateTime; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.authentication.esignet.integration.dto.ClientIdSecretKeyRequest; +import io.mosip.kernel.core.http.RequestWrapper; +import io.mosip.kernel.core.http.ResponseWrapper; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class AuthTransactionHelper { + + private static final String AUTH_TOKEN_CACHE = "authtokens"; + + public static final String AUTH_TOKEN_CACHE_KEY = "auth_token"; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RestTemplate restTemplate; + + @Value("${mosip.esignet.authenticator.ida.auth-token-url}") + private String authTokenUrl; + + @Value("${mosip.esignet.authenticator.ida.client-id}") + private String clientId; + + @Value("${mosip.esignet.authenticator.ida.secret-key}") + private String secretKey; + + @Value("${mosip.esignet.authenticator.ida.app-id}") + private String appId; + + @Cacheable(value = AUTH_TOKEN_CACHE, key = "#root.target.AUTH_TOKEN_CACHE_KEY") + public String getAuthToken() throws Exception { + log.info("Started to get auth-token with appId : {} && clientId : {}", + appId, clientId); + + + + RequestWrapper authRequest = new RequestWrapper<>(); + authRequest.setRequesttime(LocalDateTime.now()); + ClientIdSecretKeyRequest clientIdSecretKeyRequest = new ClientIdSecretKeyRequest(clientId, secretKey, appId); + authRequest.setRequest(clientIdSecretKeyRequest); + + String requestBody = objectMapper.writeValueAsString(authRequest); + RequestEntity requestEntity = RequestEntity + .post(UriComponentsBuilder.fromUriString(authTokenUrl).build().toUri()) + .contentType(MediaType.APPLICATION_JSON) + .body(requestBody); + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference() {}); + + String authToken = responseEntity.getHeaders().getFirst("authorization"); + return authToken; + } + + @CacheEvict(value = AUTH_TOKEN_CACHE, allEntries = true) + public void purgeAuthTokenCache() { + log.info("Evicting entry from AUTH_TOKEN_CACHE"); + } + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataCache.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataCache.java new file mode 100644 index 00000000000..01a0370d3d9 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataCache.java @@ -0,0 +1,33 @@ +package io.mosip.authentication.esignet.integration.helper; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Component; + +/** + * + * @author Loganathan S + * + */ +@Component +public class IdentityDataCache { + + private static final String ENCRYPTED_IDENTITY_DATA = "encryptedIdentityData"; + + @Autowired + private CacheManager cacheManager; + + public void storeToCache(String key, T data) { + Cache dataCache = cacheManager.getCache(ENCRYPTED_IDENTITY_DATA); + dataCache.put(key, data); + } + + public T retrieveFromCache(String key, Class clazz) { + Cache dataCache = cacheManager.getCache(ENCRYPTED_IDENTITY_DATA); + return dataCache.get(key, clazz); + } + + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataStore.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataStore.java new file mode 100644 index 00000000000..919aa99f09e --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/helper/IdentityDataStore.java @@ -0,0 +1,28 @@ +package io.mosip.authentication.esignet.integration.helper; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +/** + * + * @author Loganathan S + * + */ +@Component +public class IdentityDataStore { + + @Autowired + private IdentityDataCache identityDataCache; + + public void putEncryptedIdentityData(String kycToken, String psut, String encryptedIdentityData) { + Objects.requireNonNull(encryptedIdentityData); + identityDataCache.storeToCache(kycToken + psut, encryptedIdentityData); + } + + public String getEncryptedIdentityData(String kycToken, String psut) { + return identityDataCache.retrieveFromCache(kycToken + psut, String.class); + } + + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/HelperService.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/HelperService.java new file mode 100644 index 00000000000..a948732c71f --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/HelperService.java @@ -0,0 +1,306 @@ +/* + * 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.authentication.esignet.integration.service; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.authentication.esignet.integration.dto.IdaKycAuthRequest; +import io.mosip.authentication.esignet.integration.dto.IdaKycAuthRequest.AuthRequest; +import io.mosip.authentication.esignet.integration.dto.IdaSendOtpRequest; +import io.mosip.authentication.esignet.integration.dto.IdaSendOtpResponse; +import io.mosip.authentication.esignet.integration.dto.KeyBindedToken; +import io.mosip.esignet.api.dto.AuthChallenge; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.KycAuthException; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.kernel.core.util.DateUtils; +import io.mosip.kernel.core.util.HMACUtils2; +import io.mosip.kernel.crypto.jce.core.CryptoCore; +import io.mosip.kernel.cryptomanager.dto.CryptomanagerRequestDto; +import io.mosip.kernel.cryptomanager.dto.CryptomanagerResponseDto; +import io.mosip.kernel.cryptomanager.service.CryptomanagerService; +import io.mosip.kernel.keygenerator.bouncycastle.util.KeyGeneratorUtils; +import io.mosip.kernel.keymanagerservice.util.KeymanagerUtil; +import io.mosip.kernel.partnercertservice.util.PartnerCertificateManagerUtil; +import io.mosip.kernel.signature.dto.JWTSignatureRequestDto; +import io.mosip.kernel.signature.dto.JWTSignatureResponseDto; +import io.mosip.kernel.signature.service.SignatureService; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class HelperService { + + public static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + public static final String SIGNATURE_HEADER_NAME = "signature"; + public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + public static final String UTC_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + public static final String INVALID_PARTNER_CERTIFICATE = "invalid_partner_cert"; + public static final String OIDC_PARTNER_APP_ID = "OIDC_PARTNER"; + public static final String BINDING_TRANSACTION = "bindingtransaction"; + private static Base64.Encoder urlSafeEncoder; + private static Base64.Decoder urlSafeDecoder; + + static { + urlSafeEncoder = Base64.getUrlEncoder().withoutPadding(); + urlSafeDecoder = Base64.getUrlDecoder(); + } + + @Value("${mosip.esignet.authenticator.ida-send-otp-id:mosip.identity.otp}") + private String sendOtpId; + + @Value("${mosip.esignet.authenticator.ida-send-otp-version:1.0}") + private String idaVersion; + + @Value("${mosip.esignet.authenticator.ida.cert-url}") + private String idaPartnerCertificateUrl; + + @Value("${mosip.esignet.authenticator.ida.send-otp-url}") + private String sendOtpUrl; + + @Value("${mosip.kernel.keygenerator.symmetric-algorithm-name}") + private String symmetricAlgorithm; + + @Value("${mosip.kernel.keygenerator.symmetric-key-length}") + private int symmetricKeyLength; + + @Autowired + private KeymanagerUtil keymanagerUtil; + + @Autowired + private SignatureService signatureService; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private CryptoCore cryptoCore; + + private Certificate idaPartnerCertificate; + + @Autowired + private CryptomanagerService cryptomanagerService; + + + @Cacheable(value = BINDING_TRANSACTION, key = "#idHash") + public String getTransactionId(String idHash) { + return HelperService.generateTransactionId(10); + } + + protected void setAuthRequest(List challengeList, IdaKycAuthRequest idaKycAuthRequest) throws Exception { + IdaKycAuthRequest.AuthRequest authRequest = new IdaKycAuthRequest.AuthRequest(); + authRequest.setTimestamp(HelperService.getUTCDateTime()); + challengeList.stream() + .filter( auth -> auth != null && auth.getAuthFactorType() != null) + .forEach( auth -> { buildAuthRequest(auth, authRequest, idaKycAuthRequest); }); + + KeyGenerator keyGenerator = KeyGeneratorUtils.getKeyGenerator(symmetricAlgorithm, symmetricKeyLength); + final SecretKey symmetricKey = keyGenerator.generateKey(); + String request = objectMapper.writeValueAsString(authRequest); + String hexEncodedHash = HMACUtils2.digestAsPlainText(request.getBytes(StandardCharsets.UTF_8)); + idaKycAuthRequest.setRequest(HelperService.b64Encode(cryptoCore.symmetricEncrypt(symmetricKey, + request.getBytes(StandardCharsets.UTF_8), null))); + idaKycAuthRequest.setRequestHMAC(HelperService.b64Encode(cryptoCore.symmetricEncrypt(symmetricKey, + hexEncodedHash.getBytes(StandardCharsets.UTF_8), null))); + Certificate certificate = getIdaPartnerCertificate(); + idaKycAuthRequest.setThumbprint(HelperService.b64Encode(getCertificateThumbprint(certificate))); + log.info("IDA certificate thumbprint {}", idaKycAuthRequest.getThumbprint()); + idaKycAuthRequest.setRequestSessionKey(HelperService.b64Encode( + cryptoCore.asymmetricEncrypt(certificate.getPublicKey(), symmetricKey.getEncoded()))); + } + + + protected SendOtpResult sendOTP(String partnerId, String clientId, IdaSendOtpRequest idaSendOtpRequest) + throws SendOtpException, JsonProcessingException { + idaSendOtpRequest.setId(sendOtpId); + idaSendOtpRequest.setVersion(idaVersion); + idaSendOtpRequest.setRequestTime(getUTCDateTime()); + + //set signature header, body and invoke kyc exchange endpoint + String requestBody = objectMapper.writeValueAsString(idaSendOtpRequest); + RequestEntity requestEntity = RequestEntity + .post(UriComponentsBuilder.fromUriString(sendOtpUrl).pathSegment(partnerId, clientId).build().toUri()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .header(SIGNATURE_HEADER_NAME, getRequestSignature(requestBody)) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_NAME) + .body(requestBody); + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, IdaSendOtpResponse.class); + if(responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + IdaSendOtpResponse idaSendOtpResponse = responseEntity.getBody(); + if(idaSendOtpRequest.getTransactionID().equals(idaSendOtpResponse.getTransactionID()) && idaSendOtpResponse.getResponse() != null){ + return new SendOtpResult(idaSendOtpResponse.getTransactionID(), + idaSendOtpResponse.getResponse().getMaskedEmail(), + idaSendOtpResponse.getResponse().getMaskedMobile()); + } + log.error("Errors in response received from IDA send-otp : {}", idaSendOtpResponse.getErrors()); + throw new SendOtpException(idaSendOtpResponse.getErrors().get(0).getErrorCode()); + } + log.error("Error response received from IDA (send-otp) with status : {}", responseEntity.getStatusCode()); + throw new SendOtpException(); + } + + protected String getRequestSignature(String request) { + JWTSignatureRequestDto jwtSignatureRequestDto = new JWTSignatureRequestDto(); + jwtSignatureRequestDto.setApplicationId(OIDC_PARTNER_APP_ID); + jwtSignatureRequestDto.setReferenceId(""); + jwtSignatureRequestDto.setIncludePayload(false); + jwtSignatureRequestDto.setIncludeCertificate(true); + jwtSignatureRequestDto.setDataToSign(HelperService.b64Encode(request)); + JWTSignatureResponseDto responseDto = signatureService.jwtSign(jwtSignatureRequestDto); + log.debug("Request signature ---> {}", responseDto.getJwtSignedData()); + return responseDto.getJwtSignedData(); + } + + public String decrptData(String identityStr) { + CryptomanagerRequestDto cryptomanagerRequestDto = new CryptomanagerRequestDto(); + cryptomanagerRequestDto.setApplicationId(OIDC_PARTNER_APP_ID); + cryptomanagerRequestDto.setData(identityStr); + cryptomanagerRequestDto.setTimeStamp(DateUtils.getUTCCurrentDateTime()); + CryptomanagerResponseDto cryptomanagerResponseDto = cryptomanagerService.decrypt(cryptomanagerRequestDto); + return cryptomanagerResponseDto.getData(); + } + + protected Certificate getIdaPartnerCertificate() throws KycAuthException { + if(StringUtils.isEmpty(idaPartnerCertificate)) { + log.info("Fetching IDA partner certificate from : {}", idaPartnerCertificateUrl); + idaPartnerCertificate = keymanagerUtil.convertToCertificate(restTemplate.getForObject(idaPartnerCertificateUrl, + String.class)); + } + if(PartnerCertificateManagerUtil.isCertificateDatesValid((X509Certificate)idaPartnerCertificate)) + return idaPartnerCertificate; + + log.info("PARTNER CERTIFICATE IS NOT VALID, Downloading the certificate again"); + idaPartnerCertificate = keymanagerUtil.convertToCertificate(restTemplate.getForObject(idaPartnerCertificateUrl, + String.class)); + if(PartnerCertificateManagerUtil.isCertificateDatesValid((X509Certificate)idaPartnerCertificate)) + return idaPartnerCertificate; + + throw new KycAuthException(INVALID_PARTNER_CERTIFICATE); + } + + protected byte[] getCertificateThumbprint(Certificate certificate) { + try { + return DigestUtils.sha256(certificate.getEncoded()); + } catch (CertificateEncodingException e) { + log.error("Failed to get cert thumbprint", e); + } + return new byte[]{}; + } + + /** + * Output format : 2022-12-01T03:22:46.720Z + * @return Formatted datetime + */ + protected static String getUTCDateTime() { + return ZonedDateTime + .now(ZoneOffset.UTC) + .format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN)); + } + + protected static String b64Encode(byte[] bytes) { + return urlSafeEncoder.encodeToString(bytes); + } + + protected static String b64Encode(String value) { + return urlSafeEncoder.encodeToString(value.getBytes(StandardCharsets.UTF_8)); + } + + protected static byte[] b64Decode(String value) { + return urlSafeDecoder.decode(value); + } + + private void buildAuthRequest(AuthChallenge authChallenge, AuthRequest authRequest, IdaKycAuthRequest kycAauthRequest) { + log.info("Build kyc-auth request with authFactor : {}", authChallenge.getAuthFactorType()); + switch (authChallenge.getAuthFactorType().toUpperCase()) { + case "OTP" : + authRequest.setOtp(authChallenge.getChallenge()); + kycAauthRequest.getRequestedAuth().put("otp", true); + break; + case "PIN" : + authRequest.setStaticPin(authChallenge.getChallenge()); + kycAauthRequest.getRequestedAuth().put("pin", true); + break; + case "BIO" : + kycAauthRequest.getRequestedAuth().put("bio", true); + byte[] decodedBio = HelperService.b64Decode(authChallenge.getChallenge()); + try { + List biometrics = objectMapper.readValue(decodedBio, + new TypeReference>(){}); + authRequest.setBiometrics(biometrics); + } catch (Exception e) { + log.error("Failed to parse biometric capture response", e); + } + break; + case "WLA" : + List list = new ArrayList<>(); + KeyBindedToken keyBindedToken = new KeyBindedToken(); + keyBindedToken.setType(authChallenge.getAuthFactorType()); + keyBindedToken.setToken(authChallenge.getChallenge()); + keyBindedToken.setFormat(authChallenge.getFormat()); + list.add(keyBindedToken); + authRequest.setKeyBindedTokens(list); + break; + default: + throw new NotImplementedException("KYC auth not implemented"); + } + } + + protected static String generateTransactionId(int length) { + StringBuilder builder = new StringBuilder(); + for(int i=0; i request = new RequestWrapper<>(); + + AuditRequest auditRequest = new AuditRequest(); + auditRequest.setEventId(action.name()); + auditRequest.setEventName(action.name()); + auditRequest.setEventType(status.name()); + auditRequest.setActionTimeStamp(DateUtils.getUTCCurrentDateTime()); + auditRequest.setHostName("localhost"); + auditRequest.setHostIp("localhost"); + auditRequest.setApplicationId(ESIGNET); + auditRequest.setApplicationName(ESIGNET); + auditRequest.setSessionUserId(StringUtils.isEmpty(username)?"no-user":username); + auditRequest.setSessionUserName(StringUtils.isEmpty(username)?"no-user":username); + auditRequest.setIdType(TRANSACTION); + auditRequest.setCreatedBy(this.getClass().getSimpleName()); + auditRequest.setModuleName(getModuleByAction(action)); + auditRequest.setModuleId(getModuleByAction(action)); + auditRequest.setDescription(getAuditDescription(audit)); + auditRequest.setId(audit.getTransactionId()); + + request.setRequest(auditRequest); + request.setId("ida"); + request.setRequesttime(DateUtils.getUTCCurrentDateTime()); + + String requestBody = objectMapper.writeValueAsString(request); + RequestEntity requestEntity = RequestEntity + .post(UriComponentsBuilder.fromUriString(auditManagerUrl).build().toUri()) + .contentType(MediaType.APPLICATION_JSON).header(HttpHeaders.COOKIE, "Authorization=" + authToken) + .body(requestBody); + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference() { + }); + + if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + ResponseWrapper responseWrapper = responseEntity.getBody(); + if (responseWrapper.getErrors() != null && !responseWrapper.getErrors().isEmpty()) { + log.error("Error response received from audit service with errors: {}", + responseWrapper.getErrors()); + } + } + + if(responseEntity.getStatusCode() == HttpStatus.FORBIDDEN || + responseEntity.getStatusCode() == HttpStatus.UNAUTHORIZED) { + log.error("Audit call failed with error: {}, issue with auth-token hence purging the auth-token-cache", + responseEntity.getStatusCode()); + authTransactionHelper.purgeAuthTokenCache(); + } + } catch (Exception e) { + log.error("LogAudit failed with error : {}", e); + } + } + + private String getAuditDescription(AuditDTO audit) throws JSONException { + JSONObject json = new JSONObject(); + json.put("clientId", audit.getClientId()); + json.put("relyingPartyId", audit.getRelyingPartyId()); + json.put("state", audit.getState()); + json.put("codeHash", audit.getCodeHash()); + json.put("accessTokenHash", audit.getAccessTokenHash()); + return json.toString(); + } + + private String getModuleByAction(Action action) { + switch (action) { + case OIDC_CLIENT_CREATE: + case OIDC_CLIENT_UPDATE: + return "ClientManagementController"; + case GET_OAUTH_DETAILS: + case TRANSACTION_STARTED: + case SEND_OTP: + case AUTHENTICATE: + case GET_AUTH_CODE: + case DO_KYC_AUTH: + case DO_KYC_EXCHANGE: + return "AuthorizationController"; + case GENERATE_TOKEN: + return "OAuthController"; + case GET_USERINFO: + return "OpenIdConnectController"; + case LINK_AUTH_CODE: + case LINK_AUTHENTICATE: + case LINK_CODE: + case LINK_SEND_OTP: + case LINK_STATUS: + case LINK_TRANSACTION: + case SAVE_CONSENT: + return "LinkedAuthorizationController"; + case GET_CERTIFICATE: + case UPLOAD_CERTIFICATE: + return "SystemInfoController"; + default: + return "EsignetService"; + } + } + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImpl.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImpl.java new file mode 100644 index 00000000000..8cfa549e7f6 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImpl.java @@ -0,0 +1,667 @@ +/* + * 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.authentication.esignet.integration.service; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.authentication.common.service.helper.IdInfoHelper; +import io.mosip.authentication.common.service.impl.match.BioMatchType; +import io.mosip.authentication.common.service.integration.TokenIdManager; +import io.mosip.authentication.core.constant.IdAuthConfigKeyConstants; +import io.mosip.authentication.core.constant.IdAuthenticationErrorConstants; +import io.mosip.authentication.core.exception.IdAuthenticationBusinessException; +import io.mosip.authentication.core.indauth.dto.IdentityInfoDTO; +import io.mosip.authentication.core.spi.bioauth.CbeffDocType; +import io.mosip.authentication.core.spi.indauth.match.IdInfoFetcher; +import io.mosip.authentication.esignet.integration.dto.GetAllCertificatesResponse; +import io.mosip.authentication.esignet.integration.dto.IdaKycAuthRequest; +import io.mosip.authentication.esignet.integration.dto.IdaKycExchangeResponse; +import io.mosip.authentication.esignet.integration.dto.IdaKycResponse; +import io.mosip.authentication.esignet.integration.dto.IdaResponseWrapper; +import io.mosip.authentication.esignet.integration.dto.IdaSendOtpRequest; +import io.mosip.authentication.esignet.integration.helper.AuthTransactionHelper; +import io.mosip.authentication.esignet.integration.helper.IdentityDataStore; +import io.mosip.biometrics.util.ConvertRequestDto; +import io.mosip.biometrics.util.face.FaceDecoder; +import io.mosip.esignet.api.dto.KycAuthDto; +import io.mosip.esignet.api.dto.KycAuthResult; +import io.mosip.esignet.api.dto.KycExchangeDto; +import io.mosip.esignet.api.dto.KycExchangeResult; +import io.mosip.esignet.api.dto.KycSigningCertificateData; +import io.mosip.esignet.api.dto.SendOtpDto; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.KycAuthException; +import io.mosip.esignet.api.exception.KycExchangeException; +import io.mosip.esignet.api.exception.KycSigningCertificateException; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.esignet.api.spi.Authenticator; +import io.mosip.esignet.api.util.ErrorConstants; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.core.util.CryptoUtil; +import io.mosip.kernel.signature.dto.JWTSignatureRequestDto; +import io.mosip.kernel.signature.service.SignatureService; +import lombok.extern.slf4j.Slf4j; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + + +@ConditionalOnProperty(value = "mosip.esignet.integration.authenticator", havingValue = "IdaAuthenticatorImpl") +@Component +@Slf4j +public class IdaAuthenticatorImpl implements Authenticator { + + public static final String SIGNATURE_HEADER_NAME = "signature"; + public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + public static final String KYC_EXCHANGE_TYPE = "oidc"; + private static final String HASH_ALGORITHM_NAME = "SHA-256"; + public static final String SUBJECT = "sub"; + public static final String CLAIMS_LANG_SEPERATOR = "#"; + public static final String ADDRESS_FORMATTED = "formatted"; + public static final String FACE_ISO_NUMBER = "ISO19794_5_2011"; + public static final String EMPTY = ""; + + @Value("${mosip.esignet.authenticator.ida-kyc-id:mosip.identity.kyc}") + private String kycId; + + @Value("${mosip.esignet.authenticator.ida-authonly-id:mosip.identity.auth}") + private String authOnlyId; + + @Value("${mosip.esignet.authenticator.ida-version:1.0}") + private String idaVersion; + + @Value("${mosip.esignet.authenticator.ida-domainUri}") + private String idaDomainUri; + + @Value("${mosip.esignet.authenticator.ida-env:Staging}") + private String idaEnv; + + @Value("${mosip.esignet.authenticator.ida.kyc-url}") + private String kycUrl; + + @Value("${mosip.esignet.authenticator.ida.auth-url}") + private String authUrl; + + @Value("${mosip.esignet.authenticator.ida.otp-channels}") + private List otpChannels; + + @Value("${mosip.esignet.authenticator.ida.get-certificates-url}") + private String getCertsUrl; + + @Value("${mosip.esignet.authenticator.ida.application-id:IDA}") + private String applicationId; + + @Value("${mosip.esignet.authenticator.ida.reference-id:SIGN}") + private String referenceId; + + @Value("${mosip.esignet.authenticator.ida.client-id}") + private String clientId; + + @Value("${mosip.esignet.authenticator.ida.wrapper.auth.partner.id}") + private String esignetAuthPartnerId; + + + @Value("${mosip.esignet.authenticator.ida.wrapper.auth.partner.apikey}") + private String esignetAuthPartnerApiKey; + + @Value("${mosip.esignet.authenticator.ida.wrapper.auth.reference.id}") + private String esignetRefId; + + @Value("${ida.idp.consented.picture.attribute.name:picture}") + private String consentedFaceAttributeName; + + @Value("${ida.idp.consented.address.attribute.name:address}") + private String consentedAddressAttributeName; + + @Value("${ida.idp.consented.individual_id.attribute.name:individual_id}") + private String consentedIndividualAttributeName; + + @Value("${ida.idp.consented.picture.attribute.prefix:data:image/jpeg;base64,}") + private String consentedPictureAttributePrefix; + + @Value("${mosip.ida.idp.consented.address.subset.attributes:}") + private String[] addressSubsetAttributes; + + @Value("${ida.idp.consented.address.value.separator: }") + private String addressValueSeparator; + + @Value("${ida.kyc.send-face-as-cbeff-xml:false}") + private boolean sendFaceAsCbeffXml; + + @Value("${mosip.ida.kyc.exchange.sign.include.certificate:false}") + private boolean includeCertificate; + + /** The sign applicationid. */ + @Value("${mosip.ida.kyc.exchange.sign.applicationid:IDA_KYC_EXCHANGE}") + private String kycExchSignApplicationId; + + /** The key splitter. */ + @Value("${" + IdAuthConfigKeyConstants.KEY_SPLITTER + "}") + private String keySplitter; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + HelperService helperService; + + @Autowired + private AuthTransactionHelper authTransactionHelper; + + @Autowired + private IdentityDataStore identityDataStore; + + @Autowired + private TokenIdManager tokenIdManager; + + @Autowired + private IdInfoHelper idInfoHelper; + + /** The token ID length. */ + @Value("${mosip.ida.kyc.token.secret}") + private String kycTokenSecret; + + @Autowired + private SignatureService signatureService; + + @Value("${mosip.ida.kyc.exchange.skip:false}") + private boolean skipKycExchange; + + @Override + public KycAuthResult doKycAuth(String relyingPartyId, String clientId, KycAuthDto kycAuthDto) + throws KycAuthException { + log.info("Started to build kyc request with transactionId : {} && clientId : {}", + kycAuthDto.getTransactionId(), clientId); + try { + IdaKycAuthRequest idaKycAuthRequest = new IdaKycAuthRequest(); + idaKycAuthRequest.setId(skipKycExchange ? authOnlyId: kycId); + idaKycAuthRequest.setVersion(idaVersion); + idaKycAuthRequest.setRequestTime(HelperService.getUTCDateTime()); + idaKycAuthRequest.setDomainUri(idaDomainUri); + idaKycAuthRequest.setEnv(idaEnv); + idaKycAuthRequest.setConsentObtained(true); + idaKycAuthRequest.setIndividualId(kycAuthDto.getIndividualId()); + idaKycAuthRequest.setTransactionID(kycAuthDto.getTransactionId()); + //Needed in pre-LTS version (such as 1.1.5.X) + Map requestedAuth = new HashMap<>(); + idaKycAuthRequest.setRequestedAuth(requestedAuth); + + helperService.setAuthRequest(kycAuthDto.getChallengeList(), idaKycAuthRequest); + + //set signature header, body and invoke kyc auth endpoint + String requestBody = objectMapper.writeValueAsString(idaKycAuthRequest); + RequestEntity requestEntity = RequestEntity + .post(UriComponentsBuilder.fromUriString(skipKycExchange? authUrl : kycUrl).pathSegment(esignetAuthPartnerId, esignetAuthPartnerApiKey).build().toUri()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .header(SIGNATURE_HEADER_NAME, helperService.getRequestSignature(requestBody)) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_NAME) + .body(requestBody); + ResponseEntity> responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference>() {}); + + if(responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + IdaResponseWrapper responseWrapper = responseEntity.getBody(); + String psut = generatePsut(relyingPartyId, kycAuthDto.getIndividualId()); + Tuple2 result = processKycResponse(responseWrapper, psut); + if(result != null) { + String kycToken = result.getT1(); + String encryptedIdentityData = result.getT2(); + if(encryptedIdentityData != null) { + identityDataStore.putEncryptedIdentityData(kycToken, psut, encryptedIdentityData); + } + if(kycToken != null) { + return new KycAuthResult(kycToken, psut); + } + } + + + log.error("Error response received from IDA status : {} && Errors: {}", + (responseWrapper.getResponse().isKycStatus() || responseWrapper.getResponse().isAuthStatus()), responseWrapper.getErrors()); + throw new KycAuthException(CollectionUtils.isEmpty(responseWrapper.getErrors()) ? + ErrorConstants.AUTH_FAILED : responseWrapper.getErrors().get(0).getErrorCode()); + } + + log.error("Error response received from IDA (Kyc-auth) with status : {}", responseEntity.getStatusCode()); + } catch (KycAuthException e) { throw e; } catch (Exception e) { + log.error("KYC-auth failed with transactionId : {} && clientId : {}", kycAuthDto.getTransactionId(), + clientId, e); + } + throw new KycAuthException(ErrorConstants.AUTH_FAILED); + } + + + private String generatePsut(String relyingPartyId, String individualId) throws Exception { + return tokenIdManager.generateTokenId(individualId, relyingPartyId); + } + + private Tuple2 processKycResponse(IdaResponseWrapper responseWrapper, String psut) throws DecoderException, NoSuchAlgorithmException { + String kycToken = generateKycToken(responseWrapper.getTransactionID(), psut); + if(responseWrapper.getResponse() != null && responseWrapper.getResponse().isKycStatus()) { + IdaKycResponse response = responseWrapper.getResponse(); + String encryptedIdentityData = response.getIdentity(); + String combinedData = encryptedIdentityData; + String sessionKey = response.getSessionKey(); + if(sessionKey != null) { + combinedData = CryptoUtil.encodeToURLSafeBase64(combineDataForDecryption(CryptoUtil.decodeURLSafeBase64(sessionKey), CryptoUtil.decodeURLSafeBase64(encryptedIdentityData))); + } + String thumbprint = response.getThumbprint(); + if(thumbprint != null) { + combinedData = CryptoUtil.encodeToURLSafeBase64(CryptoUtil.combineByteArray(CryptoUtil.decodeURLSafeBase64(combinedData), Hex.decodeHex(thumbprint), EMPTY)); + } + return Tuples.of(kycToken, combinedData); + } + return Tuples.of(kycToken, null); + } + + /** + * Combine data for decryption. + * + * @param encryptedSessionKey the encrypted session key + * @param encryptedData the encrypted data + * @return the string + */ + private byte[] combineDataForDecryption(byte[] encryptedSessionKey, byte[] encryptedData) { + return CryptoUtil.combineByteArray(encryptedData, encryptedSessionKey, keySplitter); + } + + private String generateKycToken(String transactionID, String authToken) throws DecoderException, NoSuchAlgorithmException { + String uuid = UUID.nameUUIDFromBytes(transactionID.getBytes()).toString(); + return doGenerateKycToken(uuid, authToken); + } + + private String doGenerateKycToken(String uuid, String idHash) throws DecoderException, NoSuchAlgorithmException { + try { + byte[] uuidBytes = uuid.getBytes(); + byte[] idHashBytes = Hex.decodeHex(idHash); + ByteBuffer bBuffer = ByteBuffer.allocate(uuidBytes.length + idHashBytes.length); + bBuffer.put(uuidBytes); + bBuffer.put(idHashBytes); + + byte[] kycTokenInputBytes = bBuffer.array(); + return generateKeyedHash(kycTokenInputBytes); + } catch (DecoderException e) { + log.error("Error Generating KYC Token", e); + throw e; + } + } + + public String generateKeyedHash(byte[] bytesToHash) throws java.security.NoSuchAlgorithmException { + try { + // Need to get secret from HSM + byte[] tokenSecret = CryptoUtil.decodeURLSafeBase64(kycTokenSecret); + MessageDigest messageDigest = MessageDigest.getInstance(HASH_ALGORITHM_NAME); + messageDigest.update(bytesToHash); + messageDigest.update(tokenSecret); + byte[] tokenHash = messageDigest.digest(); + + return TokenEncoderUtil.encodeBase58(tokenHash); + } catch (NoSuchAlgorithmException e) { + log.error("Error generating Keyed Hash", e); + throw e; + } + } + + @Override + public KycExchangeResult doKycExchange(String relyingPartyId, String clientId, KycExchangeDto kycExchangeDto) + throws KycExchangeException { + log.info("Started to build kyc-exchange request with transactionId : {} && clientId : {}", + kycExchangeDto.getTransactionId(), clientId); + try { + String psut = generatePsut(relyingPartyId, kycExchangeDto.getIndividualId()); + String encryptedIdentityData = identityDataStore.getEncryptedIdentityData(kycExchangeDto.getKycToken(), psut); + Map idResDTO; + if(encryptedIdentityData != null) { + String decrptIdentityData = helperService.decrptData(encryptedIdentityData); + idResDTO = objectMapper.readValue(CryptoUtil.decodeURLSafeBase64(decrptIdentityData), Map.class); + } else { + idResDTO = Map.of(); + } + Map> idInfo = IdInfoFetcher.getIdInfo(idResDTO); + + String respJson =idInfo.isEmpty() ? null: buildKycExchangeResponse(psut, idInfo, kycExchangeDto.getAcceptedClaims(), List.of(kycExchangeDto.getClaimsLocales()), kycExchangeDto.getIndividualId()); + IdaResponseWrapper responseWrapper = new IdaResponseWrapper<>(); + IdaKycExchangeResponse respose = new IdaKycExchangeResponse(); + responseWrapper.setResponse(respose); + respose.setEncryptedKyc(respJson); + return new KycExchangeResult(responseWrapper.getResponse().getEncryptedKyc()); + } catch (KycExchangeException e) { + throw e; + } catch (Exception e) {e.printStackTrace(); + log.error("IDA Kyc-exchange failed with clientId : {}", clientId, e); + } + throw new KycExchangeException(); + } + + public String buildKycExchangeResponse(String subject, Map> idInfo, + List consentedAttributes, List consentedLocales, String idVid) throws IdAuthenticationBusinessException { + + log.info("Building claims response for PSU token: " + subject); + + Map respMap = new HashMap<>(); + Set uniqueConsentedLocales = new HashSet(consentedLocales); + Map mappedConsentedLocales = localesMapping(uniqueConsentedLocales); + + respMap.put(SUBJECT, subject); + + for (String attrib : consentedAttributes) { + if (attrib.equals(SUBJECT)) + continue; + if (attrib.equals(consentedIndividualAttributeName)) { + respMap.put(attrib, idVid); + continue; + } + List idSchemaAttribute = idInfoHelper.getIdentityAttributesForIdName(attrib); + if (mappedConsentedLocales.size() > 0) { + addEntityForLangCodes(mappedConsentedLocales, idInfo, respMap, attrib, idSchemaAttribute); + } + } + + try { + return signWithPayload(objectMapper.writeValueAsString(respMap)); + } catch (JsonProcessingException e) { + throw new IdAuthenticationBusinessException(IdAuthenticationErrorConstants.UNABLE_TO_PROCESS, e); + } + } + + public String signWithPayload(String data) { + JWTSignatureRequestDto request = new JWTSignatureRequestDto(); + request.setApplicationId(kycExchSignApplicationId); + request.setDataToSign(CryptoUtil.encodeToURLSafeBase64(data.getBytes())); + request.setIncludeCertHash(false); + request.setIncludeCertificate(includeCertificate); + request.setIncludePayload(true); + request.setReferenceId(EMPTY); + return signatureService.jwtSign(request).getJwtSignedData(); + } + + private void addEntityForLangCodes(Map mappedConsentedLocales, Map> idInfo, + Map respMap, String consentedAttribute, List idSchemaAttributes) + throws IdAuthenticationBusinessException { + + if (consentedAttribute.equals(consentedFaceAttributeName)) { + if (!idInfo.keySet().contains(BioMatchType.FACE.getIdMapping().getIdname())) { + log.info("Face Bio not found in DB. So not adding to response claims."); + return; + } + Map faceEntityInfoMap = idInfoHelper.getIdEntityInfoMap(BioMatchType.FACE, idInfo, null); + if (Objects.nonNull(faceEntityInfoMap)) { + String face = convertJP2ToJpeg(faceEntityInfoMap.get(CbeffDocType.FACE.getType().value())); + if (Objects.nonNull(face)) + respMap.put(consentedAttribute, consentedPictureAttributePrefix + face); + } + return; + } + + if (idSchemaAttributes.size() == 1) { + List idInfoList = idInfo.get(idSchemaAttributes.get(0)); + if (Objects.isNull(idInfoList)) { + log.info("Data not available in Identity Info for the claim. So not adding to response claims. Claim Name: " + idSchemaAttributes.get(0)); + return; + } + Map mappedLangCodes = langCodeMapping(idInfoList); + List availableLangCodes = getAvailableLangCodes(mappedConsentedLocales, mappedLangCodes); + if (availableLangCodes.size() == 1){ + for (IdentityInfoDTO identityInfo : idInfoList) { + String langCode = mappedLangCodes.get(availableLangCodes.get(0)); + if (identityInfo.getLanguage().equalsIgnoreCase(langCode)) { + respMap.put(consentedAttribute, identityInfo.getValue()); + } + } + } else { + if (availableLangCodes.size() > 0) { + for (IdentityInfoDTO identityInfo : idInfoList) { + for (String availableLangCode : availableLangCodes) { + String langCode = mappedLangCodes.get(availableLangCode); + if (identityInfo.getLanguage().equalsIgnoreCase(langCode)) { + respMap.put(consentedAttribute + CLAIMS_LANG_SEPERATOR + availableLangCode, + identityInfo.getValue()); + } + } + } + } else { + respMap.put(consentedAttribute, idInfoList.get(0).getValue()); + } + } + } else { + if (consentedAttribute.equals(consentedAddressAttributeName)) { + if (mappedConsentedLocales.size() > 1) { + for (String consentedLocale: mappedConsentedLocales.keySet()) { + String consentedLocaleValue = mappedConsentedLocales.get(consentedLocale); + if (addressSubsetAttributes.length == 0) { + log.info("No address subset attributes configured. Will return the address with formatted attribute."); + addFormattedAddress(idSchemaAttributes, idInfo, consentedLocaleValue, respMap, true, + CLAIMS_LANG_SEPERATOR + consentedLocaleValue); + continue; + } + addAddressClaim(addressSubsetAttributes, idInfo, consentedLocaleValue, respMap, true, + CLAIMS_LANG_SEPERATOR + consentedLocaleValue); + } + } else { + String consentedLocale = mappedConsentedLocales.keySet().iterator().next(); + String consentedLocaleValue = mappedConsentedLocales.get(consentedLocale); + if (addressSubsetAttributes.length == 0) { + log.info("No address subset attributes configured. Will return the address with formatted attribute."); + addFormattedAddress(idSchemaAttributes, idInfo, consentedLocaleValue, respMap, false, ""); + return; + } + + addAddressClaim(addressSubsetAttributes, idInfo, consentedLocaleValue, respMap, false, ""); + } + } + } + } + + private void addFormattedAddress(List idSchemaAttributes, Map> idInfo, String localeValue, + Map respMap, boolean addLocale, String localeAppendValue) throws IdAuthenticationBusinessException { + boolean langCodeFound = false; + Map addressMap = new HashMap<>(); + StringBuilder identityInfoValue = new StringBuilder(); + for (String schemaAttrib: idSchemaAttributes) { + List idSchemaSubsetAttributes = idInfoHelper.getIdentityAttributesForIdName(schemaAttrib); + for (String idSchemaAttribute : idSchemaSubsetAttributes) { + List idInfoList = idInfo.get(idSchemaAttribute); + Map mappedLangCodes = langCodeMapping(idInfoList); + if (identityInfoValue.length() > 0) { + identityInfoValue.append(addressValueSeparator); + } + if (mappedLangCodes.keySet().contains(localeValue)) { + String langCode = mappedLangCodes.get(localeValue); + for (IdentityInfoDTO identityInfo : idInfoList) { + if (identityInfoValue.length() > 0) { + identityInfoValue.append(addressValueSeparator); + } + if (identityInfo.getLanguage().equals(langCode)) { + langCodeFound = true; + identityInfoValue.append(identityInfo.getValue()); + } + } + } else { + if (Objects.nonNull(idInfoList) && idInfoList.size() == 1) { + identityInfoValue.append(idInfoList.get(0).getValue()); + } + } + } + } + //String identityInfoValueStr = identityInfoValue.toString(); + //String trimmedValue = identityInfoValueStr.substring(0, identityInfoValueStr.lastIndexOf(addressValueSeparator)); + addressMap.put(ADDRESS_FORMATTED + localeAppendValue, identityInfoValue.toString()); + if (langCodeFound && addLocale) + respMap.put(consentedAddressAttributeName + localeAppendValue, addressMap); + else + respMap.put(consentedAddressAttributeName, addressMap); + } + + private void addAddressClaim(String[] addressAttributes, Map> idInfo, String consentedLocaleValue, + Map respMap, boolean addLocale, String localeAppendValue) throws IdAuthenticationBusinessException { + boolean langCodeFound = false; //added for language data not available in identity info (Eg: fr) + Map addressMap = new HashMap<>(); + for (String addressAttribute : addressAttributes) { + List idSchemaSubsetAttributes = idInfoHelper.getIdentityAttributesForIdName(addressAttribute); + StringBuilder identityInfoValue = new StringBuilder(); + for (String idSchemaAttribute : idSchemaSubsetAttributes) { + List idInfoList = idInfo.get(idSchemaAttribute); + Map mappedLangCodes = langCodeMapping(idInfoList); + if (identityInfoValue.length() > 0) { + identityInfoValue.append(addressValueSeparator); + } + if (mappedLangCodes.keySet().contains(consentedLocaleValue)) { + String langCode = mappedLangCodes.get(consentedLocaleValue); + for (IdentityInfoDTO identityInfo : idInfoList) { + if (identityInfoValue.length() > 0) { + identityInfoValue.append(addressValueSeparator); + } + if (identityInfo.getLanguage().equals(langCode)) { + langCodeFound = true; + identityInfoValue.append(identityInfo.getValue()); + } + } + } else { + if (Objects.nonNull(idInfoList) && idInfoList.size() == 1) { + identityInfoValue.append(idInfoList.get(0).getValue()); + } + } + } + // Added below condition to skip if the data is not available in DB. MOSIP-26472 + if (identityInfoValue.toString().trim().length() > 0) + addressMap.put(addressAttribute + localeAppendValue, identityInfoValue.toString()); + } + if (langCodeFound && addLocale) + respMap.put(consentedAddressAttributeName + localeAppendValue, addressMap); + else + respMap.put(consentedAddressAttributeName, addressMap); + } + + private String convertJP2ToJpeg(String jp2Image) { + try { + ConvertRequestDto convertRequestDto = new ConvertRequestDto(); + convertRequestDto.setVersion(FACE_ISO_NUMBER); + convertRequestDto.setInputBytes(CryptoUtil.decodeBase64(jp2Image));// TODO check url safe / plain + byte[] image = FaceDecoder.convertFaceISOToImageBytes(convertRequestDto); + return CryptoUtil.encodeBase64(image);// TODO check url safe / plain + } catch(Exception exp) { + log.error("Error Converting JP2 To JPEG. " + exp.getMessage(), exp); + } + return null; + } + + private Map localesMapping(Set locales) { + + Map mappedLocales = new HashMap<>(); + for (String locale : locales) { + if (locale.trim().length() == 0) + continue; + mappedLocales.put(locale, locale.substring(0, 2)); + } + return mappedLocales; + } + + private Map langCodeMapping(List idInfoList) { + + Map mappedLangCodes = new HashMap<>(); + if (Objects.nonNull(idInfoList)) { + for (IdentityInfoDTO idInfo : idInfoList) { + if (Objects.nonNull(idInfo.getLanguage())) { + mappedLangCodes.put(idInfo.getLanguage().substring(0,2), idInfo.getLanguage()); + } + } + } + return mappedLangCodes; + } + + private List getAvailableLangCodes(Map mappedLocales, Map mappedLangCodes) { + List availableLangCodes = new ArrayList<>(); + for (String entry: mappedLocales.keySet()) { + String locale = mappedLocales.get(entry); + if (mappedLangCodes.keySet().contains(locale)) { + availableLangCodes.add(locale); + } + } + return availableLangCodes; + } + + @Override + public SendOtpResult sendOtp(String relyingPartyId, String clientId, SendOtpDto sendOtpDto) throws SendOtpException { + log.info("Started to build send-otp request with transactionId : {} && clientId : {}", + sendOtpDto.getTransactionId(), clientId); + try { + IdaSendOtpRequest idaSendOtpRequest = new IdaSendOtpRequest(); + idaSendOtpRequest.setOtpChannel(sendOtpDto.getOtpChannels()); + idaSendOtpRequest.setIndividualId(sendOtpDto.getIndividualId()); + idaSendOtpRequest.setTransactionID(sendOtpDto.getTransactionId()); + return helperService.sendOTP(relyingPartyId, clientId, idaSendOtpRequest); + } catch (SendOtpException e) { + throw e; + } catch (Exception e) { + log.error("send-otp failed with clientId : {}", clientId, e); + } + throw new SendOtpException(); + } + + @Override + public boolean isSupportedOtpChannel(String channel) { + return channel != null && otpChannels.contains(channel.toLowerCase()); + } + + @Override + public List getAllKycSigningCertificates() throws KycSigningCertificateException { + try { + String authToken = authTransactionHelper.getAuthToken(); + + RequestEntity requestEntity = RequestEntity + .get(UriComponentsBuilder.fromUriString(getCertsUrl).queryParam("applicationId", applicationId).queryParam("referenceId", referenceId).build().toUri()) + .header(HttpHeaders.COOKIE, "Authorization=" + authToken) + .build(); + + ResponseEntity> responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference>() {}); + + if(responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + ResponseWrapper responseWrapper = responseEntity.getBody(); + if(responseWrapper.getResponse() != null && responseWrapper.getResponse().getAllCertificates() != null) { + return responseWrapper.getResponse().getAllCertificates(); + } + log.error("Error response received from getAllSigningCertificates with errors: {}", + responseWrapper.getErrors()); + throw new KycSigningCertificateException(CollectionUtils.isEmpty(responseWrapper.getErrors()) ? + ErrorConstants.KYC_SIGNING_CERTIFICATE_FAILED : responseWrapper.getErrors().get(0).getErrorCode()); + } + log.error("Error response received from getAllSigningCertificates with status : {}", responseEntity.getStatusCode()); + } catch (KycSigningCertificateException e) { throw e; } catch (Exception e) { + log.error("getAllKycSigningCertificates failed with clientId : {}", clientId, e); + } + throw new KycSigningCertificateException(); + } +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImpl.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImpl.java new file mode 100644 index 00000000000..245a7821ebf --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImpl.java @@ -0,0 +1,174 @@ +/* + * 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.authentication.esignet.integration.service; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.authentication.esignet.integration.dto.*; +import io.mosip.esignet.api.dto.AuthChallenge; +import io.mosip.esignet.api.dto.KeyBindingResult; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.KeyBindingException; +import io.mosip.esignet.api.exception.KycAuthException; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.esignet.api.spi.KeyBinder; +import io.mosip.esignet.api.util.ErrorConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ConditionalOnProperty(value = "mosip.esignet.integration.key-binder", havingValue = "IdaKeyBinderImpl") +@Component +@Slf4j +public class IdaKeyBinderImpl implements KeyBinder { + + private static final Map> supportedFormats = new HashMap<>(); + static { + supportedFormats.put("OTP", Arrays.asList("alpha-numeric")); + supportedFormats.put("PIN", Arrays.asList("number")); + supportedFormats.put("BIO", Arrays.asList("encoded-json")); + supportedFormats.put("WLA", Arrays.asList("jwt")); + } + + private static final String PARTNER_ID_HEADER = "partner-id"; + private static final String PARTNER_API_KEY_HEADER = "partner-api-key"; + public static final String SIGNATURE_HEADER_NAME = "signature"; + public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + public static final String REQUIRED_HEADERS_MISSING = "required_header_missing"; + + @Value("${mosip.esignet.binder.ida.key-binding-url}") + private String keyBinderUrl; + + @Value("${mosip.esignet.binder.ida-binding-id:mosip.identity.keybinding}") + private String keyBindingId; + + @Value("${mosip.esignet.authenticator.ida-version:1.0}") + private String idaVersion; + + @Value("${mosip.esignet.authenticator.ida-domainUri}") + private String idaDomainUri; + + @Value("${mosip.esignet.authenticator.ida-env:Staging}") + private String idaEnv; + + @Autowired + private HelperService helperService; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RestTemplate restTemplate; + + @Override + public SendOtpResult sendBindingOtp(String individualId, List otpChannels, Map requestHeaders) + throws SendOtpException { + log.info("Started to send-binding-otp request"); + try { + if(StringUtils.isEmpty(requestHeaders.get(PARTNER_ID_HEADER)) || StringUtils.isEmpty(requestHeaders.get(PARTNER_API_KEY_HEADER))) + throw new SendOtpException(REQUIRED_HEADERS_MISSING); + + IdaSendOtpRequest idaSendOtpRequest = new IdaSendOtpRequest(); + idaSendOtpRequest.setOtpChannel(otpChannels); + idaSendOtpRequest.setIndividualId(individualId); + idaSendOtpRequest.setTransactionID(helperService.getTransactionId(HelperService.generateHash(individualId.trim()))); + return helperService.sendOTP(requestHeaders.get(PARTNER_ID_HEADER), + requestHeaders.get(PARTNER_API_KEY_HEADER), idaSendOtpRequest); + } catch (SendOtpException e) { + throw e; + } catch (Exception e) { + log.error("send-binding-otp failed with requestHeaders : {}", requestHeaders, e); + } + throw new SendOtpException(); + } + + @Override + public KeyBindingResult doKeyBinding(String individualId, List challengeList, Map publicKeyJWK, + String bindAuthFactorType, Map requestHeaders) throws KeyBindingException { + log.info("Started to key-binding request for auth-factor-type {}", bindAuthFactorType); + if(StringUtils.isEmpty(requestHeaders.get(PARTNER_ID_HEADER)) || StringUtils.isEmpty(requestHeaders.get(PARTNER_API_KEY_HEADER))) + throw new KeyBindingException(REQUIRED_HEADERS_MISSING); + + try { + KeyBindingRequest keyBindingRequest = new KeyBindingRequest(); + keyBindingRequest.setId(keyBindingId); + keyBindingRequest.setVersion(idaVersion); + keyBindingRequest.setRequestTime(HelperService.getUTCDateTime()); + keyBindingRequest.setDomainUri(idaDomainUri); + keyBindingRequest.setEnv(idaEnv); + keyBindingRequest.setConsentObtained(true); + keyBindingRequest.setIndividualId(individualId); + keyBindingRequest.setTransactionID(helperService.getTransactionId(HelperService.generateHash(individualId.trim()))); + helperService.setAuthRequest(challengeList, keyBindingRequest); + + KeyBindingRequest.IdentityKeyBinding identityKeyBinding = new KeyBindingRequest.IdentityKeyBinding(); + identityKeyBinding.setPublicKeyJWK(publicKeyJWK); + identityKeyBinding.setAuthFactorType(bindAuthFactorType); + keyBindingRequest.setIdentityKeyBinding(identityKeyBinding); + + //set signature header, body and invoke kyc auth endpoint + String requestBody = objectMapper.writeValueAsString(keyBindingRequest); + RequestEntity requestEntity = RequestEntity + .post(UriComponentsBuilder.fromUriString(keyBinderUrl).pathSegment(requestHeaders.getOrDefault(PARTNER_ID_HEADER, PARTNER_ID_HEADER), + requestHeaders.getOrDefault(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER)).build().toUri()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .header(SIGNATURE_HEADER_NAME, helperService.getRequestSignature(requestBody)) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_NAME) + .body(requestBody); + ResponseEntity> responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference>() {}); + + if(responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + IdaResponseWrapper responseWrapper = responseEntity.getBody(); + if(responseWrapper.getResponse() == null) { + log.error("Error response received from IDA (Key-binding) Errors: {}", responseWrapper.getErrors()); + throw new KeyBindingException(CollectionUtils.isEmpty(responseWrapper.getErrors()) ? + ErrorConstants.KEY_BINDING_FAILED : responseWrapper.getErrors().get(0).getErrorCode()); + } + + if(!responseWrapper.getResponse().isBindingAuthStatus()) { + log.error("Binding-Auth-status : {}", responseWrapper.getResponse().isBindingAuthStatus()); + throw new KeyBindingException(ErrorConstants.BINDING_AUTH_FAILED); + } + + KeyBindingResult keyBindingResult = new KeyBindingResult(); + keyBindingResult.setCertificate(responseWrapper.getResponse().getIdentityCertificate()); + keyBindingResult.setPartnerSpecificUserToken(responseWrapper.getResponse().getAuthToken()); + return keyBindingResult; + } + + log.error("Error response received from IDA (Key-binding) with status : {}", responseEntity.getStatusCode()); + } catch (KeyBindingException e) { + throw e; + } catch (Exception e) { + log.error("Key-binding failed with headers: {}", requestHeaders, e); + } + throw new KeyBindingException(ErrorConstants.KEY_BINDING_FAILED); + } + + @Override + public List getSupportedChallengeFormats(String authFactorType) { + return supportedFormats.getOrDefault(authFactorType, Arrays.asList()); + } + +} diff --git a/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/TokenEncoderUtil.java b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/TokenEncoderUtil.java new file mode 100644 index 00000000000..11722819ff8 --- /dev/null +++ b/authentication/esignet-integration-impl/src/main/java/io/mosip/authentication/esignet/integration/service/TokenEncoderUtil.java @@ -0,0 +1,61 @@ +package io.mosip.authentication.esignet.integration.service; + +import java.util.Arrays; +import java.util.Objects; + +/** + * The Class TokenEncoderUtil is the utility class to encode base58 for the generated token. + * + * @author Mahammed Taheer + */ +public final class TokenEncoderUtil { + + private static final char[] BASE58_ALPHANUMERIC = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + + private static final char ENCODED_0 = BASE58_ALPHANUMERIC[0]; + + public static String encodeBase58(final byte[] dataToEncode) { + if (Objects.isNull(dataToEncode) || dataToEncode.length == 0) { + return ""; + } + + final char[] encoded = new char[dataToEncode.length * 2]; + final byte[] copy = Arrays.copyOf(dataToEncode, dataToEncode.length); + + int zeros = 0; + while (zeros < dataToEncode.length && dataToEncode[zeros] == 0) { + ++zeros; + } + + int inputIndex = zeros; + int outputIndex = encoded.length; + while (inputIndex < copy.length) { + encoded[--outputIndex] = BASE58_ALPHANUMERIC[divmod(copy, inputIndex, 256, 58)]; + if (copy[inputIndex] == 0) { + ++inputIndex; + } + } + + while (outputIndex < encoded.length && encoded[outputIndex] == ENCODED_0) { + ++outputIndex; + } + while (--zeros >= 0) { + encoded[--outputIndex] = ENCODED_0; + } + + return new String(encoded, outputIndex, encoded.length - outputIndex); + } + + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xff; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; + } + return (byte) remainder; + } + + +} diff --git a/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/HelperServiceTest.java b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/HelperServiceTest.java new file mode 100644 index 00000000000..8528d4b3d91 --- /dev/null +++ b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/HelperServiceTest.java @@ -0,0 +1,244 @@ +package io.mosip.authentication.esignet.integration.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.authentication.esignet.integration.dto.*; +import io.mosip.authentication.esignet.integration.dto.Error; +import io.mosip.esignet.api.dto.AuthChallenge; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.kernel.crypto.jce.core.CryptoCore; +import io.mosip.kernel.keymanagerservice.util.KeymanagerUtil; +import io.mosip.kernel.signature.dto.JWTSignatureResponseDto; +import io.mosip.kernel.signature.service.SignatureService; +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Assert; +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.http.HttpStatus; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +@RunWith(MockitoJUnitRunner.class) +public class HelperServiceTest { + + @InjectMocks + private HelperService helperService; + + @Mock + private KeymanagerUtil keymanagerUtil; + + @Mock + private SignatureService signatureService; + + @Mock + private RestTemplate restTemplate; + + @Mock + private CryptoCore cryptoCore; + + String partnerId = "test"; + String partnerAPIKey = "test-api-key"; + + ObjectMapper objectMapper = new ObjectMapper(); + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ReflectionTestUtils.setField(helperService, "sendOtpUrl", "https://test/test"); + ReflectionTestUtils.setField(helperService, "idaPartnerCertificateUrl", "https://test/test"); + ReflectionTestUtils.setField(helperService, "symmetricAlgorithm", "AES"); + ReflectionTestUtils.setField(helperService, "symmetricKeyLength", 256); + ReflectionTestUtils.setField(helperService, "objectMapper", objectMapper); + } + + @Test + public void sendOtp_requestSignatureFailed_thenFail() { + JWTSignatureResponseDto jwtSignatureResponseDto = new JWTSignatureResponseDto(); + jwtSignatureResponseDto.setJwtSignedData("test-jwt"); + Mockito.when(signatureService.jwtSign(Mockito.any())).thenThrow(RuntimeException.class); + IdaSendOtpRequest sendOtpRequest = new IdaSendOtpRequest(); + Assert.assertThrows(Exception.class, () -> helperService.sendOTP(partnerId, partnerAPIKey, sendOtpRequest)); + } + + @Test + public void sendOtp_withNullResponse_thenFail() { + JWTSignatureResponseDto jwtSignatureResponseDto = new JWTSignatureResponseDto(); + jwtSignatureResponseDto.setJwtSignedData("test-jwt"); + Mockito.when(signatureService.jwtSign(Mockito.any())).thenReturn(jwtSignatureResponseDto); + + ResponseEntity responseEntity = new ResponseEntity(HttpStatus.OK); + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.any())).thenReturn(responseEntity); + IdaSendOtpRequest sendOtpRequest = new IdaSendOtpRequest(); + Assert.assertThrows(SendOtpException.class, () -> helperService.sendOTP(partnerId, partnerAPIKey, sendOtpRequest)); + } + + @Test + public void sendOtp_withValidResponse_thenPass() throws Exception { + JWTSignatureResponseDto jwtSignatureResponseDto = new JWTSignatureResponseDto(); + jwtSignatureResponseDto.setJwtSignedData("test-jwt"); + Mockito.when(signatureService.jwtSign(Mockito.any())).thenReturn(jwtSignatureResponseDto); + + IdaSendOtpResponse idaSendOtpResponse = new IdaSendOtpResponse(); + idaSendOtpResponse.setTransactionID("123456788"); + IdaOtpResponse idaOtpResponse = new IdaOtpResponse(); + idaOtpResponse.setMaskedEmail("masked-mail"); + new IdaOtpResponse().setMaskedMobile("masked-mobile"); + idaSendOtpResponse.setResponse(idaOtpResponse); + ResponseEntity responseEntity = new ResponseEntity( + idaSendOtpResponse, HttpStatus.OK); + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.any())).thenReturn(responseEntity); + IdaSendOtpRequest sendOtpRequest = new IdaSendOtpRequest(); + sendOtpRequest.setTransactionID("123456788"); + SendOtpResult sendOtpResult = helperService.sendOTP(partnerId, partnerAPIKey, sendOtpRequest); + Assert.assertEquals(idaSendOtpResponse.getTransactionID(), sendOtpResult.getTransactionId()); + Assert.assertEquals(idaOtpResponse.getMaskedEmail(), sendOtpResult.getMaskedEmail()); + Assert.assertEquals(idaOtpResponse.getMaskedMobile(), sendOtpResult.getMaskedMobile()); + } + + @Test + public void sendOtp_withErrorResponse_thenFail() { + JWTSignatureResponseDto jwtSignatureResponseDto = new JWTSignatureResponseDto(); + jwtSignatureResponseDto.setJwtSignedData("test-jwt"); + Mockito.when(signatureService.jwtSign(Mockito.any())).thenReturn(jwtSignatureResponseDto); + + IdaSendOtpResponse idaSendOtpResponse = new IdaSendOtpResponse(); + idaSendOtpResponse.setTransactionID("123456788"); + idaSendOtpResponse.setErrors(Arrays.asList(new Error("otp-error", "otp-error"))); + ResponseEntity responseEntity = new ResponseEntity( + idaSendOtpResponse, HttpStatus.OK); + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.any())).thenReturn(responseEntity); + + IdaSendOtpRequest sendOtpRequest = new IdaSendOtpRequest(); + sendOtpRequest.setTransactionID("123456788"); + try { + helperService.sendOTP(partnerId, partnerAPIKey, sendOtpRequest); + } catch (SendOtpException e) { + Assert.assertEquals("otp-error", e.getErrorCode()); + } catch (JsonProcessingException e) { + Assert.fail(); + } + } + + @Test + public void setAuthRequest_withInvalidChallengeType_thenFail() { + List challengeList = new ArrayList<>(); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("test"); + authChallenge.setAuthFactorType("Test"); + challengeList.add(authChallenge); + Assert.assertThrows(NotImplementedException.class, + () -> helperService.setAuthRequest(challengeList, new IdaKycAuthRequest())); + } + + @Test + public void setAuthRequest_withOTPChallengeType_thenPass() throws Exception { + List challengeList = new ArrayList<>(); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("111333"); + authChallenge.setAuthFactorType("otp"); + authChallenge.setFormat("numeric"); + challengeList.add(authChallenge); + + Mockito.when(restTemplate.getForObject("https://test/test", String.class)).thenReturn("test-certificate"); + Mockito.when(keymanagerUtil.convertToCertificate(Mockito.any(String.class))).thenReturn(TestUtil.getCertificate()); + Mockito.when(cryptoCore.asymmetricEncrypt(Mockito.any(), Mockito.any())).thenReturn("test".getBytes()); + + IdaKycAuthRequest idaKycAuthRequest = new IdaKycAuthRequest(); + helperService.setAuthRequest(challengeList, idaKycAuthRequest); + Assert.assertNotNull(idaKycAuthRequest.getRequest()); + Assert.assertNotNull(idaKycAuthRequest.getRequestSessionKey()); + Assert.assertNotNull(idaKycAuthRequest.getRequestHMAC()); + Assert.assertNotNull(idaKycAuthRequest.getThumbprint()); + } + + @Test + public void setAuthRequest_withPINChallengeType_thenPass() throws Exception { + List challengeList = new ArrayList<>(); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("111333"); + authChallenge.setAuthFactorType("pin"); + authChallenge.setFormat("numeric"); + challengeList.add(authChallenge); + + Mockito.when(restTemplate.getForObject("https://test/test", String.class)).thenReturn("test-certificate"); + Mockito.when(keymanagerUtil.convertToCertificate(Mockito.any(String.class))).thenReturn(TestUtil.getCertificate()); + Mockito.when(cryptoCore.asymmetricEncrypt(Mockito.any(), Mockito.any())).thenReturn("test".getBytes()); + + IdaKycAuthRequest idaKycAuthRequest = new IdaKycAuthRequest(); + helperService.setAuthRequest(challengeList, idaKycAuthRequest); + Assert.assertNotNull(idaKycAuthRequest.getRequest()); + Assert.assertNotNull(idaKycAuthRequest.getRequestSessionKey()); + Assert.assertNotNull(idaKycAuthRequest.getRequestHMAC()); + Assert.assertNotNull(idaKycAuthRequest.getThumbprint()); + } + + @Test + public void setAuthRequest_withBIOChallengeType_thenPass() throws Exception { + IdaKycAuthRequest.Biometric biometric = new IdaKycAuthRequest.Biometric(); + biometric.setData("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0=.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"); + List list = new ArrayList<>(); + list.add(biometric); + String value = objectMapper.writeValueAsString(list); + + List challengeList = new ArrayList<>(); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge(HelperService.b64Encode(value)); + authChallenge.setAuthFactorType("bio"); + authChallenge.setFormat("numeric"); + challengeList.add(authChallenge); + + Mockito.when(restTemplate.getForObject("https://test/test", String.class)).thenReturn("test-certificate"); + Mockito.when(keymanagerUtil.convertToCertificate(Mockito.any(String.class))).thenReturn(TestUtil.getCertificate()); + Mockito.when(cryptoCore.asymmetricEncrypt(Mockito.any(), Mockito.any())).thenReturn("test".getBytes()); + + IdaKycAuthRequest idaKycAuthRequest = new IdaKycAuthRequest(); + helperService.setAuthRequest(challengeList, idaKycAuthRequest); + Assert.assertNotNull(idaKycAuthRequest.getRequest()); + Assert.assertNotNull(idaKycAuthRequest.getRequestSessionKey()); + Assert.assertNotNull(idaKycAuthRequest.getRequestHMAC()); + Assert.assertNotNull(idaKycAuthRequest.getThumbprint()); + } + + @Test + public void getIdaPartnerCertificate_withUnsetPartnerCertificate_thenPass() throws Exception { + Mockito.when(restTemplate.getForObject("https://test/test", String.class)).thenReturn("test-certificate"); + Certificate certificate = TestUtil.getCertificate(); + Mockito.when(keymanagerUtil.convertToCertificate(Mockito.any(String.class))).thenReturn(certificate); + Assert.assertEquals(certificate, helperService.getIdaPartnerCertificate()); + } + + @Test + public void getIdaPartnerCertificate_withExpiredPartnerCertificate_thenPass() throws Exception { + Mockito.when(restTemplate.getForObject("https://test/test", String.class)).thenReturn("test-certificate", "test-certificate"); + Certificate certificate = TestUtil.getCertificate(); + Mockito.when(keymanagerUtil.convertToCertificate(Mockito.any(String.class))).thenReturn(TestUtil.getExpiredCertificate(), certificate); + Assert.assertEquals(certificate, helperService.getIdaPartnerCertificate()); + } + + @Test + public void getRequestSignature_validation() { + JWTSignatureResponseDto jwtSignatureResponseDto = new JWTSignatureResponseDto(); + jwtSignatureResponseDto.setJwtSignedData("test-jwt"); + Mockito.when(signatureService.jwtSign(Mockito.any())).thenReturn(jwtSignatureResponseDto); + Assert.assertEquals("test-jwt", helperService.getRequestSignature("test-request-value")); + } +} diff --git a/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImplTest.java b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImplTest.java new file mode 100644 index 00000000000..21af9462e40 --- /dev/null +++ b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaAuthenticatorImplTest.java @@ -0,0 +1,425 @@ +/* + * 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.authentication.esignet.integration.service; + +import static org.mockito.ArgumentMatchers.any; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Assert; +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.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.authentication.esignet.integration.dto.GetAllCertificatesResponse; +import io.mosip.authentication.esignet.integration.dto.IdaKycExchangeResponse; +import io.mosip.authentication.esignet.integration.dto.IdaResponseWrapper; +import io.mosip.authentication.esignet.integration.helper.AuthTransactionHelper; +import io.mosip.esignet.api.dto.KycExchangeDto; +import io.mosip.esignet.api.dto.KycExchangeResult; +import io.mosip.esignet.api.dto.KycSigningCertificateData; +import io.mosip.esignet.api.dto.SendOtpDto; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.KycExchangeException; +import io.mosip.esignet.api.exception.KycSigningCertificateException; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; + +@SpringBootTest +@RunWith(MockitoJUnitRunner.class) +public class IdaAuthenticatorImplTest { + + @InjectMocks + IdaAuthenticatorImpl idaAuthenticatorImpl; + + @Mock + ObjectMapper mapper; + + @Mock + RestTemplate restTemplate; + + @Mock + HelperService helperService; + + @Mock + AuthTransactionHelper authTransactionHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + ReflectionTestUtils.setField(helperService, "sendOtpUrl", "https:/"); + ReflectionTestUtils.setField(helperService, "idaPartnerCertificateUrl", "https://test"); + ReflectionTestUtils.setField(helperService, "symmetricAlgorithm", "AES"); + ReflectionTestUtils.setField(helperService, "symmetricKeyLength", 256); + + ReflectionTestUtils.setField(idaAuthenticatorImpl, "kycExchangeUrl", "https://dev.mosip.net"); + ReflectionTestUtils.setField(idaAuthenticatorImpl, "idaVersion", "VersionIDA"); + ReflectionTestUtils.setField(idaAuthenticatorImpl, "kycAuthUrl", "https://testkycAuthUrl"); + ReflectionTestUtils.setField(idaAuthenticatorImpl, "getCertsUrl", "https://testGetCertsUrl"); + ReflectionTestUtils.setField(idaAuthenticatorImpl, "otpChannels", Arrays.asList("otp", "pin", "bio")); + } +//TODO fix the test cases +// @Test +// public void doKycAuth_withInvalidDetails_throwsException() throws Exception { +// KycAuthDto kycAuthDto = new KycAuthDto(); +// kycAuthDto.setIndividualId("IND1234"); +// kycAuthDto.setTransactionId("TRAN1234"); +// AuthChallenge authChallenge = new AuthChallenge(); +// authChallenge.setAuthFactorType("PIN"); +// authChallenge.setChallenge("111111"); +// List authChallengeList = new ArrayList<>(); +// authChallengeList.add(authChallenge); +// kycAuthDto.setChallengeList(authChallengeList); +// +// Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); +// Mockito.when(restTemplate.exchange(Mockito.>any(), +// Mockito.>>any())).thenReturn(null); +// +// Assert.assertThrows(KycAuthException.class, +// () -> idaAuthenticatorImpl.doKycAuth("relyingId", "clientId", kycAuthDto)); +// } +// +// @Test +// public void doKycAuth_withValidDetails_thenPass() throws Exception { +// KycAuthDto kycAuthDto = new KycAuthDto(); +// kycAuthDto.setIndividualId("IND1234"); +// kycAuthDto.setTransactionId("TRAN1234"); +// AuthChallenge authChallenge = new AuthChallenge(); +// authChallenge.setAuthFactorType("OTP"); +// authChallenge.setChallenge("111111"); +// List authChallengeList = new ArrayList<>(); +// authChallengeList.add(authChallenge); +// kycAuthDto.setChallengeList(authChallengeList); +// +// Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); +// +// IdaKycAuthResponse idaKycAuthResponse = new IdaKycAuthResponse(); +// idaKycAuthResponse.setAuthToken("authToken1234"); +// idaKycAuthResponse.setKycToken("kycToken1234"); +// idaKycAuthResponse.setKycStatus(true); +// +// IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); +// idaResponseWrapper.setResponse(idaKycAuthResponse); +// idaResponseWrapper.setTransactionID("TRAN123"); +// idaResponseWrapper.setVersion("VER1"); +// +// ResponseEntity> responseEntity = new ResponseEntity>( +// idaResponseWrapper, HttpStatus.OK); +// +// Mockito.when(restTemplate.exchange(Mockito.>any(), +// Mockito.>>any())) +// .thenReturn(responseEntity); +// +// KycAuthResult kycAuthResult = idaAuthenticatorImpl.doKycAuth("relyingId", "clientId", kycAuthDto); +// +// Assert.assertEquals(kycAuthResult.getKycToken(), kycAuthResult.getKycToken()); +// } +// +// @Test +// public void doKycAuth_withAuthChallengeNull_thenFail() throws Exception { +// KycAuthDto kycAuthDto = new KycAuthDto(); +// kycAuthDto.setIndividualId("IND1234"); +// kycAuthDto.setTransactionId("TRAN1234"); +// kycAuthDto.setChallengeList(null); +// +// Assert.assertThrows(KycAuthException.class, +// () -> idaAuthenticatorImpl.doKycAuth("relyingId", "clientId", kycAuthDto)); +// } +// +// @Test +// public void doKycAuth_withInvalidAuthChallenge_thenFail() throws Exception { +// KycAuthDto kycAuthDto = new KycAuthDto(); +// kycAuthDto.setIndividualId("IND1234"); +// kycAuthDto.setTransactionId("TRAN1234"); +// AuthChallenge authChallenge = new AuthChallenge(); +// authChallenge.setAuthFactorType("Test"); +// authChallenge.setChallenge("111111"); +// List authChallengeList = new ArrayList<>(); +// authChallengeList.add(authChallenge); +// kycAuthDto.setChallengeList(authChallengeList); +// +// Assert.assertThrows(KycAuthException.class, +// () -> idaAuthenticatorImpl.doKycAuth("relyingId", "clientId", kycAuthDto)); +// } +// +// @Test +// public void doKycAuth_withBIOAuthChallenge_thenPass() throws Exception { +// KycAuthDto kycAuthDto = new KycAuthDto(); +// kycAuthDto.setIndividualId("IND1234"); +// kycAuthDto.setTransactionId("TRAN1234"); +// AuthChallenge authChallenge = new AuthChallenge(); +// authChallenge.setAuthFactorType("BIO"); +// authChallenge.setChallenge("111111"); +// List authChallengeList = new ArrayList<>(); +// authChallengeList.add(authChallenge); +// kycAuthDto.setChallengeList(authChallengeList); +// +// Biometric b = new Biometric(); +// b.setData( +// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"); +// b.setHash("Hash"); +// b.setSessionKey("SessionKey"); +// b.setSpecVersion("SepecV"); +// b.setThumbprint("Thumbprint"); +// List bioList = new ArrayList<>(); +// bioList.add(b); +// Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); +// IdaKycAuthResponse idaKycAuthResponse = new IdaKycAuthResponse(); +// idaKycAuthResponse.setAuthToken("authToken1234"); +// idaKycAuthResponse.setKycToken("kycToken1234"); +// idaKycAuthResponse.setKycStatus(true); +// +// IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); +// idaResponseWrapper.setResponse(idaKycAuthResponse); +// idaResponseWrapper.setTransactionID("TRAN123"); +// idaResponseWrapper.setVersion("VER1"); +// +// ResponseEntity> responseEntity = new ResponseEntity>( +// idaResponseWrapper, HttpStatus.OK); +// +// Mockito.when(restTemplate.exchange(Mockito.>any(), +// Mockito.>>any())) +// .thenReturn(responseEntity); +// +// KycAuthResult kycAuthResult = idaAuthenticatorImpl.doKycAuth("relyingId", "clientId", kycAuthDto); +// +// Assert.assertEquals(kycAuthResult.getKycToken(), kycAuthResult.getKycToken()); +// } + + @Test + public void doKycExchange_withValidDetails_thenPass() throws Exception { + KycExchangeDto kycExchangeDto = new KycExchangeDto(); + kycExchangeDto.setIndividualId("IND1234"); + kycExchangeDto.setKycToken("KYCT123"); + kycExchangeDto.setTransactionId("TRAN123"); + List acceptedClaims = new ArrayList<>(); + acceptedClaims.add("claims"); + kycExchangeDto.setAcceptedClaims(acceptedClaims); + String[] claimsLacales = new String[] { "claims", "locales" }; + kycExchangeDto.setClaimsLocales(claimsLacales); + + Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); + + IdaKycExchangeResponse idaKycExchangeResponse = new IdaKycExchangeResponse(); + idaKycExchangeResponse.setEncryptedKyc("ENCRKYC123"); + + IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); + idaResponseWrapper.setResponse(idaKycExchangeResponse); + idaResponseWrapper.setTransactionID("TRAN123"); + idaResponseWrapper.setVersion("VER1"); + + ResponseEntity> responseEntity = new ResponseEntity>( + idaResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(responseEntity); + + KycExchangeResult kycExchangeResult = idaAuthenticatorImpl.doKycExchange("relyingPartyId", "clientId", + kycExchangeDto); + + Assert.assertEquals(idaKycExchangeResponse.getEncryptedKyc(), kycExchangeResult.getEncryptedKyc()); + } + + @Test + public void doKycExchange_withInvalidDetails_thenFail() throws Exception { + KycExchangeDto kycExchangeDto = new KycExchangeDto(); + kycExchangeDto.setIndividualId(null); + kycExchangeDto.setKycToken("KYCT123"); + kycExchangeDto.setTransactionId("TRAN123"); + List acceptedClaims = new ArrayList<>(); + acceptedClaims.add("claims"); + kycExchangeDto.setAcceptedClaims(acceptedClaims); + String[] claimsLacales = new String[] { "claims", "locales" }; + kycExchangeDto.setClaimsLocales(claimsLacales); + + Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); + + IdaKycExchangeResponse idaKycExchangeResponse = new IdaKycExchangeResponse(); + idaKycExchangeResponse.setEncryptedKyc("ENCRKYC123"); + + IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); + idaResponseWrapper.setResponse(null); + idaResponseWrapper.setTransactionID("TRAN123"); + idaResponseWrapper.setVersion("VER1"); + + ResponseEntity> responseEntity = new ResponseEntity>( + idaResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(responseEntity); + + Assert.assertThrows(KycExchangeException.class, + () -> idaAuthenticatorImpl.doKycExchange("test-relyingPartyId", "test-clientId", kycExchangeDto)); + } + + @Test + public void doKycExchange_withInvalidIndividualId_throwsException() throws KycExchangeException, Exception { + KycExchangeDto kycExchangeDto = new KycExchangeDto(); + kycExchangeDto.setIndividualId("IND1234"); + kycExchangeDto.setKycToken("KYCT123"); + kycExchangeDto.setTransactionId("TRAN123"); + List acceptedClaims = new ArrayList<>(); + acceptedClaims.add("claims"); + kycExchangeDto.setAcceptedClaims(acceptedClaims); + String[] claimsLacales = new String[] { "claims", "locales" }; + kycExchangeDto.setClaimsLocales(claimsLacales); + + Mockito.when(mapper.writeValueAsString(Mockito.any())).thenReturn("value"); + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(null); + + Assert.assertThrows(KycExchangeException.class, + () -> idaAuthenticatorImpl.doKycExchange("relyingId", "clientId", kycExchangeDto)); + } + + @Test + public void sendOtp_withValidDetails_thenPass() throws Exception { + SendOtpDto sendOtpDto = new SendOtpDto(); + sendOtpDto.setIndividualId("1234"); + sendOtpDto.setTransactionId("4567"); + List otpChannelsList = new ArrayList<>(); + otpChannelsList.add("channel"); + sendOtpDto.setOtpChannels(otpChannelsList); + + Mockito.when(helperService.sendOTP(any(),any(),any())).thenReturn(new SendOtpResult(sendOtpDto.getTransactionId(), "", "")); + + SendOtpResult sendOtpResult = idaAuthenticatorImpl.sendOtp("rly123", "cli123", sendOtpDto); + + Assert.assertEquals(sendOtpDto.getTransactionId(), sendOtpResult.getTransactionId()); + } + + @Test + public void sendOtp_withErrorResponse_throwsException() throws Exception { + SendOtpDto sendOtpDto = new SendOtpDto(); + sendOtpDto.setIndividualId(null); + sendOtpDto.setTransactionId("4567"); + List otpChannelsList = new ArrayList<>(); + otpChannelsList.add("channel"); + sendOtpDto.setOtpChannels(otpChannelsList); + + Mockito.when(helperService.sendOTP(any(),any(),any())).thenThrow(new SendOtpException("error-100")); + + try { + idaAuthenticatorImpl.sendOtp("rly123", "cli123", sendOtpDto); + Assert.fail(); + } catch (SendOtpException e) { + Assert.assertEquals("error-100", e.getErrorCode()); + } + } + + @Test + public void isSupportedOtpChannel_withInvalidChannel_thenFail() { + Assert.assertFalse(idaAuthenticatorImpl.isSupportedOtpChannel("test")); + } + + @Test + public void isSupportedOtpChannel_withValidChannel_thenPass() { + Assert.assertTrue(idaAuthenticatorImpl.isSupportedOtpChannel("OTP")); + } + + @Test + public void getAllKycSigningCertificates_withValidDetails_thenPass() throws Exception { + Mockito.when(authTransactionHelper.getAuthToken()).thenReturn("test-token"); + + GetAllCertificatesResponse getAllCertificatesResponse = new GetAllCertificatesResponse(); + getAllCertificatesResponse.setAllCertificates(new ArrayList()); + + ResponseWrapper certsResponseWrapper = new ResponseWrapper(); + certsResponseWrapper.setId("test-id"); + certsResponseWrapper.setResponse(getAllCertificatesResponse); + + ResponseEntity> certsResponseEntity = new ResponseEntity>( + certsResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(certsResponseEntity); + + List signingCertificates = new ArrayList<>(); + + signingCertificates = idaAuthenticatorImpl.getAllKycSigningCertificates(); + + Assert.assertSame(signingCertificates, getAllCertificatesResponse.getAllCertificates()); + } + + @Test + public void getAllKycSigningCertificates_withInvalidResponse_throwsException() throws Exception { + Mockito.when(authTransactionHelper.getAuthToken()).thenReturn("test-token"); + + ResponseWrapper certsResponseWrapper = new ResponseWrapper(); + certsResponseWrapper.setId("test-id"); + List errors = new ArrayList<>(); + ServiceError error = new ServiceError("ERR-001", "Certificates not found"); + errors.add(error); + certsResponseWrapper.setErrors(errors); + + ResponseEntity> certsResponseEntity = new ResponseEntity>( + certsResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(certsResponseEntity); + + Assert.assertThrows(KycSigningCertificateException.class, + () -> idaAuthenticatorImpl.getAllKycSigningCertificates()); + } + + @Test + public void getAllKycSigningCertificates_withErrorResponse_throwsException() throws Exception { + Mockito.when(authTransactionHelper.getAuthToken()).thenReturn("test-token"); + + ResponseWrapper certsResponseWrapper = new ResponseWrapper(); + certsResponseWrapper.setId("test-id"); + List errors = new ArrayList<>(); + ServiceError error = new ServiceError("ERR-001", "Certificates not found"); + errors.add(error); + certsResponseWrapper.setErrors(errors); + + ResponseEntity> certsResponseEntity = new ResponseEntity>( + certsResponseWrapper, HttpStatus.FORBIDDEN); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(certsResponseEntity); + + Assert.assertThrows(KycSigningCertificateException.class, + () -> idaAuthenticatorImpl.getAllKycSigningCertificates()); + } + + @SuppressWarnings("rawtypes") + @Test + public void getAllKycSigningCertificates_withInvalidToken_thenFail() throws Exception { + Mockito.when(authTransactionHelper.getAuthToken()).thenReturn("test-token"); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>any())).thenThrow(RuntimeException.class); + + Assert.assertThrows(KycSigningCertificateException.class, + () -> idaAuthenticatorImpl.getAllKycSigningCertificates()); + } + +} diff --git a/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImplTest.java b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImplTest.java new file mode 100644 index 00000000000..47d3d69d9dd --- /dev/null +++ b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/IdaKeyBinderImplTest.java @@ -0,0 +1,188 @@ +package io.mosip.authentication.esignet.integration.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.authentication.esignet.integration.dto.*; +import io.mosip.esignet.api.dto.KeyBindingResult; +import io.mosip.esignet.api.dto.SendOtpDto; +import io.mosip.esignet.api.dto.SendOtpResult; +import io.mosip.esignet.api.exception.KeyBindingException; +import io.mosip.esignet.api.exception.SendOtpException; +import io.mosip.esignet.api.util.ErrorConstants; +import org.junit.Assert; +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.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.parameters.P; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; + +@RunWith(MockitoJUnitRunner.class) +public class IdaKeyBinderImplTest { + + @InjectMocks + private IdaKeyBinderImpl idaKeyBinderImpl; + + @Mock + private HelperService helperService; + + @Mock + private RestTemplate restTemplate; + + private ObjectMapper objectMapper = new ObjectMapper(); + private static final String PARTNER_ID_HEADER = "partner-id"; + private static final String PARTNER_API_KEY_HEADER = "partner-api-key"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ReflectionTestUtils.setField(idaKeyBinderImpl, "keyBinderUrl", "https://localhost/identity-key-binding/mispLK/"); + ReflectionTestUtils.setField(idaKeyBinderImpl, "objectMapper", objectMapper); + } + + @Test + public void sendBindingOtp_withValidDetails_thenPass() throws Exception { + SendOtpDto sendOtpDto = new SendOtpDto(); + sendOtpDto.setIndividualId("1234"); + sendOtpDto.setTransactionId("4567"); + List otpChannelsList = new ArrayList<>(); + otpChannelsList.add("channel"); + sendOtpDto.setOtpChannels(otpChannelsList); + Map headers = new HashMap<>(); + headers.put(PARTNER_ID_HEADER, PARTNER_ID_HEADER); + headers.put(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER); + Mockito.when(helperService.sendOTP(any(),any(),any())).thenReturn(new SendOtpResult(sendOtpDto.getTransactionId(), "", "")); + SendOtpResult sendOtpResult = idaKeyBinderImpl.sendBindingOtp("individualId", Arrays.asList("email"), headers); + Assert.assertEquals(sendOtpDto.getTransactionId(), sendOtpResult.getTransactionId()); + } + + @Test + public void sendBindingOtp_withErrorResponse_throwsException() throws Exception { + SendOtpDto sendOtpDto = new SendOtpDto(); + sendOtpDto.setIndividualId(null); + sendOtpDto.setTransactionId("4567"); + List otpChannelsList = new ArrayList<>(); + otpChannelsList.add("channel"); + sendOtpDto.setOtpChannels(otpChannelsList); + Mockito.when(helperService.sendOTP(any(),any(),any())).thenThrow(new SendOtpException("error-100")); + Map headers = new HashMap<>(); + headers.put(PARTNER_ID_HEADER, PARTNER_ID_HEADER); + headers.put(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER); + try { + idaKeyBinderImpl.sendBindingOtp("individualId", Arrays.asList("email"), headers); + Assert.fail(); + } catch (SendOtpException e) { + Assert.assertEquals("error-100", e.getErrorCode()); + } + } + + @Test + public void sendBindingOtp_withEmptyHeaders_throwsException() throws Exception { + try { + idaKeyBinderImpl.sendBindingOtp("individualId", Arrays.asList("email"), new HashMap<>()); + Assert.fail(); + } catch (SendOtpException e) { + Assert.assertEquals(IdaKeyBinderImpl.REQUIRED_HEADERS_MISSING, e.getErrorCode()); + } + } + + @Test + public void doKeyBinding_withValidDetails_thenPass() throws KeyBindingException { + IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); + KeyBindingResponse keyBindingResponse = new KeyBindingResponse(); + keyBindingResponse.setAuthToken("auth-token"); + keyBindingResponse.setBindingAuthStatus(true); + keyBindingResponse.setIdentityCertificate("certificate"); + idaResponseWrapper.setResponse(keyBindingResponse); + ResponseEntity> responseEntity = new ResponseEntity>( + idaResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(responseEntity); + + Map headers = new HashMap<>(); + headers.put(PARTNER_ID_HEADER, PARTNER_ID_HEADER); + headers.put(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER); + KeyBindingResult keyBindingResult = idaKeyBinderImpl.doKeyBinding("individualId", new ArrayList<>(), new HashMap<>(), + "WLA", headers); + Assert.assertNotNull(keyBindingResult); + Assert.assertEquals(keyBindingResponse.getAuthToken(), keyBindingResult.getPartnerSpecificUserToken()); + Assert.assertEquals(keyBindingResponse.getIdentityCertificate(), keyBindingResult.getCertificate()); + } + + @Test + public void doKeyBinding_withAuthFailure_thenPass() { + IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); + KeyBindingResponse keyBindingResponse = new KeyBindingResponse(); + keyBindingResponse.setAuthToken("auth-token"); + keyBindingResponse.setBindingAuthStatus(false); + keyBindingResponse.setIdentityCertificate("certificate"); + idaResponseWrapper.setResponse(keyBindingResponse); + ResponseEntity> responseEntity = new ResponseEntity>( + idaResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(responseEntity); + + Map headers = new HashMap<>(); + headers.put(PARTNER_ID_HEADER, PARTNER_ID_HEADER); + headers.put(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER); + try { + idaKeyBinderImpl.doKeyBinding("individualId", new ArrayList<>(), new HashMap<>(), + "WLA", headers); + Assert.fail(); + } catch (KeyBindingException e) { + Assert.assertEquals(ErrorConstants.BINDING_AUTH_FAILED, e.getErrorCode()); + } + } + + @Test + public void doKeyBinding_withErrorResponse_thenFail() { + IdaResponseWrapper idaResponseWrapper = new IdaResponseWrapper<>(); + IdaError idaError = new IdaError(); + idaError.setErrorCode("test-err-code"); + idaResponseWrapper.setErrors(Arrays.asList(idaError)); + ResponseEntity> responseEntity = new ResponseEntity>( + idaResponseWrapper, HttpStatus.OK); + + Mockito.when(restTemplate.exchange(Mockito.>any(), + Mockito.>>any())) + .thenReturn(responseEntity); + + Map headers = new HashMap<>(); + headers.put(PARTNER_ID_HEADER, PARTNER_ID_HEADER); + headers.put(PARTNER_API_KEY_HEADER, PARTNER_API_KEY_HEADER); + try { + idaKeyBinderImpl.doKeyBinding("individualId", new ArrayList<>(), new HashMap<>(), + "WLA", headers); + Assert.fail(); + } catch (KeyBindingException e) { + Assert.assertEquals("test-err-code", e.getErrorCode()); + } + } + + @Test + public void doKeyBinding_withEmptyHeaders_thenFail() { + try { + idaKeyBinderImpl.doKeyBinding("individualId", new ArrayList<>(), new HashMap<>(), + "WLA", new HashMap<>()); + Assert.fail(); + } catch (KeyBindingException e) { + Assert.assertEquals(IdaKeyBinderImpl.REQUIRED_HEADERS_MISSING, e.getErrorCode()); + } + } +} diff --git a/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/TestUtil.java b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/TestUtil.java new file mode 100644 index 00000000000..1ebb8ad2600 --- /dev/null +++ b/authentication/esignet-integration-impl/src/test/java/io/mosip/authentication/esignet/integration/service/TestUtil.java @@ -0,0 +1,80 @@ +/* + * 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.authentication.esignet.integration.service; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Date; +import java.util.UUID; + + +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; + +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.x509.X509V3CertificateGenerator; + +import javax.security.auth.x500.X500Principal; + +@Slf4j +public class TestUtil { + + public static JWK generateJWK_RSA() { + // Generate the RSA key pair + try { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(2048); + KeyPair keyPair = gen.generateKeyPair(); + // Convert public key to JWK format + return new RSAKey.Builder((RSAPublicKey)keyPair.getPublic()) + .privateKey((RSAPrivateKey)keyPair.getPrivate()) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .build(); + } catch (NoSuchAlgorithmException e) { + log.error("generateJWK_RSA failed", e); + } + return null; + } + + public static X509Certificate getCertificate() throws Exception { + JWK clientJWK = TestUtil.generateJWK_RSA(); + X509V3CertificateGenerator generator = new X509V3CertificateGenerator(); + X500Principal dnName = new X500Principal("CN=Test"); + generator.setSubjectDN(dnName); + generator.setIssuerDN(dnName); // use the same + generator.setNotBefore(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)); + generator.setNotAfter(new Date(System.currentTimeMillis() + 24 * 365 * 24 * 60 * 60 * 1000)); + generator.setPublicKey(clientJWK.toRSAKey().toPublicKey()); + generator.setSignatureAlgorithm("SHA256WITHRSA"); + generator.setSerialNumber(new BigInteger(String.valueOf(System.currentTimeMillis()))); + return generator.generate(clientJWK.toRSAKey().toPrivateKey()); + } + + public static X509Certificate getExpiredCertificate() throws Exception { + JWK clientJWK = TestUtil.generateJWK_RSA(); + X509V3CertificateGenerator generator = new X509V3CertificateGenerator(); + X500Principal dnName = new X500Principal("CN=Test"); + generator.setSubjectDN(dnName); + generator.setIssuerDN(dnName); // use the same + generator.setNotBefore(new Date(System.currentTimeMillis())); + generator.setNotAfter(new Date(System.currentTimeMillis())); + generator.setPublicKey(clientJWK.toRSAKey().toPublicKey()); + generator.setSignatureAlgorithm("SHA256WITHRSA"); + generator.setSerialNumber(new BigInteger(String.valueOf(System.currentTimeMillis()))); + return generator.generate(clientJWK.toRSAKey().toPrivateKey()); + } +} diff --git a/authentication/pom.xml b/authentication/pom.xml index 51705ab9395..0200e7fbd4e 100644 --- a/authentication/pom.xml +++ b/authentication/pom.xml @@ -64,6 +64,7 @@ authentication-internal-service authentication-kyc-service authentication-otp-service + esignet-integration-impl @@ -113,7 +114,7 @@ 1.18.8 - 2.0.2.RELEASE + 2.3.6.RELEASE 4.0.1.RELEASE 5.0.5.RELEASE 2.0.0.RELEASE