diff --git a/.github/workflows/push_trigger.yml b/.github/workflows/push_trigger.yml index f5038f38f..a8ad836ed 100644 --- a/.github/workflows/push_trigger.yml +++ b/.github/workflows/push_trigger.yml @@ -10,6 +10,7 @@ on: - 1.* - develop - main + - bugfix-ES-218 jobs: call-workflow-codeql-analysis: diff --git a/.github/workflows/release_chart.yml b/.github/workflows/release_chart.yml index 4c9e2158c..98c63f92b 100644 --- a/.github/workflows/release_chart.yml +++ b/.github/workflows/release_chart.yml @@ -19,6 +19,6 @@ jobs: with: token: ${{ secrets.ACTION_PAT }} charts_dir: ./helm - charts_url: https://github.com/mosip + charts_url: https://mosip.github.io/mosip-helm repository: mosip-helm branch: gh-pages diff --git a/README.md b/README.md index 5524b9525..7a06b323f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,62 @@ The project requires JDK 11. $ docker build -f Dockerfile ``` +## Installing in k8s cluster using helm +### Pre-requisites +1. Set the kube config file of the Mosip cluster having dependent services is set correctly in PC. +1. Make sure [DB setup](db_scripts/README.md#install-in-existing-mosip-k8-cluster) is done. +1. Add / merge below mentioned properties files into existing config branch: + * [esignet-default.properties](https://github.com/mosip/mosip-config/blob/v1.2.0.1-B3/esignet-default.properties) + * [application-default.properties](https://github.com/mosip/mosip-config/blob/v1.2.0.1-B3/application-default.properties) +1. Below are the dependent services required for esignet service: + | Chart | Chart version | + |---|---| + |[Keycloak](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/iam) | 7.1.18 | + |[Keycloak-init](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/iam) | 12.0.1-B3 | + |[Postgres](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/postgres) | 10.16.2 | + |[Postgres Init](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/postgres) | 12.0.1-B3 | + |[Minio](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/object-store) | 10.1.6 | + |[Kafka](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/external/kafka) | 0.4.2 | + |[Config-server](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/mosip/config-server) | 12.0.1-B3 | + |[Websub](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/mosip/websub) | 12.0.1-B2 | + |[Artifactory server](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/mosip/artifactory) | 12.0.1-B3 | + |[Keymanager service](https://github.com/mosip/mosip-infra/blob/v1.2.0.1-B3/deployment/v3/mosip/keymanager) | 12.0.1-B2 | + |[Kernel services](https://github.com/mosip/mosip-infra/blob/v1.2.0.1-B3/deployment/v3/mosip/kernel) | 12.0.1-B2 | + |[Biosdk service](https://github.com/mosip/mosip-infra/tree/v1.2.0.1-B3/deployment/v3/mosip/biosdk) | 12.0.1-B3 | + |[Idrepo services](https://github.com/mosip/mosip-infra/blob/v1.2.0.1-B3/deployment/v3/mosip/idrepo) | 12.0.1-B2 | + |[Pms services](https://github.com/mosip/mosip-infra/blob/v1.2.0.1-B3/deployment/v3/mosip/pms) | 12.0.1-B3 | + |[IDA services](https://github.com/mosip/mosip-infra/blob/v1.2.0.1-B3/deployment/v3/mosip/ida) | 12.0.1-B3 | + +### Install +* Install `kubectl` and `helm` utilities. +* Run `install-all.sh` to deploy esignet services. + ``` + cd helm + ./install-all.sh + ``` +* During the execution of the `install-all.sh` script, a prompt appears requesting information regarding the presence of a public domain and a valid SSL certificate on the server. +* If the server lacks a public domain and a valid SSL certificate, it is advisable to select the `n` option. Opting it will enable the `init-container` with an `emptyDir` volume and include it in the deployment process. +* The init-container will proceed to download the server's self-signed SSL certificate and mount it to the specified location within the container's Java keystore (i.e., `cacerts`) file. +* This particular functionality caters to scenarios where the script needs to be employed on a server utilizing self-signed SSL certificates. + +### Delete +* Run `delete-all.sh` to remove esignet services. + ``` + cd helm + ./delete-all.sh + ``` + +### Restart +* Run `restart-all.sh` to restart esignet services. + ``` + cd helm + ./restart-all.sh + ``` + +## Onboard esignet +* Run onboarder's [install.sh](partner-onboarder) script to exchange jwk certificates. + + ## APIs API documentation is available [here](https://mosip.stoplight.io/docs/identity-provider/branches/main/6f1syzijynu40-identity-provider). diff --git a/binding-service-impl/pom.xml b/binding-service-impl/pom.xml index 557018e0b..6791fb508 100644 --- a/binding-service-impl/pom.xml +++ b/binding-service-impl/pom.xml @@ -8,7 +8,7 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet diff --git a/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java b/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java index cc4f746b5..6f8fd1ec1 100644 --- a/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java +++ b/binding-service-impl/src/main/java/io/mosip/esignet/services/KeyBindingServiceImpl.java @@ -12,6 +12,7 @@ import java.time.format.DateTimeFormatter; import java.util.*; +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; @@ -80,13 +81,23 @@ public BindingOtpResponse sendBindingOtp(BindingOtpRequest bindingOtpRequest, Ma return otpResponse; } + private void validateChallengeListAuthFormat(List challengeList){ + if(!challengeList.stream().allMatch(challenge->keyBindingWrapper.getSupportedChallengeFormats(challenge.getAuthFactorType()). + contains(challenge.getFormat()))) { + log.error("Invalid auth factor type or challenge format in the challenge list"); + throw new EsignetException(INVALID_AUTH_FACTOR_TYPE_OR_CHALLENGE_FORMAT); + } + } + @Override public WalletBindingResponse bindWallet(WalletBindingRequest walletBindingRequest, Map requestHeaders) throws EsignetException { log.debug("bindWallet :: Request headers >> {}", requestHeaders); + validateChallengeListAuthFormat(walletBindingRequest.getChallengeList()); + //Do not store format, only check if the format is supported by the wrapper. if(!keyBindingWrapper.getSupportedChallengeFormats(walletBindingRequest.getAuthFactorType()). contains(walletBindingRequest.getFormat())) - throw new EsignetException(INVALID_CHALLENGE_FORMAT); + throw new EsignetException(INVALID_AUTH_FACTOR_TYPE_OR_CHALLENGE_FORMAT); String publicKey = IdentityProviderUtil.getJWKString(walletBindingRequest.getPublicKey()); KeyBindingResult keyBindingResult; diff --git a/binding-service-impl/src/main/java/io/mosip/esignet/services/PublicKeyRegistryServiceImpl.java b/binding-service-impl/src/main/java/io/mosip/esignet/services/PublicKeyRegistryServiceImpl.java new file mode 100644 index 000000000..ea733c08c --- /dev/null +++ b/binding-service-impl/src/main/java/io/mosip/esignet/services/PublicKeyRegistryServiceImpl.java @@ -0,0 +1,36 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.services; + +import io.mosip.esignet.core.dto.PublicKeyRegistry; +import io.mosip.esignet.core.spi.PublicKeyRegistryService; +import io.mosip.esignet.repository.PublicKeyRegistryRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Slf4j +@Service +public class PublicKeyRegistryServiceImpl implements PublicKeyRegistryService { + + @Autowired + private PublicKeyRegistryRepository publicKeyRegistryRepository; + + @Override + public Optional findLatestPublicKeyByPsuTokenAndAuthFactor(String psuToken, String authFactor) { + Optional optionalPublicKeyRegistry = publicKeyRegistryRepository.findLatestByPsuTokenAndAuthFactor(psuToken,authFactor); + if(optionalPublicKeyRegistry.isPresent()) { + PublicKeyRegistry publicKeyRegistry = new PublicKeyRegistry(); + publicKeyRegistry.setPublicKey(optionalPublicKeyRegistry.get().getPublicKey()); + publicKeyRegistry.setPsuToken(optionalPublicKeyRegistry.get().getPsuToken()); + publicKeyRegistry.setAuthFactor(optionalPublicKeyRegistry.get().getAuthFactor()); + return Optional.of(publicKeyRegistry); + } + return Optional.empty(); + } +} diff --git a/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java b/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java index cd56a241d..21490d519 100644 --- a/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java +++ b/binding-service-impl/src/test/java/io/mosip/esignet/KeyBindingServiceTest.java @@ -201,6 +201,7 @@ public void bindWallet_withUnsupportedFormat_thenFail() throws EsignetException, AuthChallenge authChallenge = new AuthChallenge(); authChallenge.setAuthFactorType("OTP"); authChallenge.setChallenge("111111"); + authChallenge.setFormat("alpha-numeric"); List authChallengeList = new ArrayList(); authChallengeList.add(authChallenge); walletBindingRequest.setChallengeList(authChallengeList); @@ -210,7 +211,7 @@ public void bindWallet_withUnsupportedFormat_thenFail() throws EsignetException, Assert.assertNotNull(keyBindingService.bindWallet(walletBindingRequest, new HashMap<>())); Assert.fail(); } catch (EsignetException e) { - Assert.assertTrue(e.getErrorCode().equals(ErrorConstants.INVALID_CHALLENGE_FORMAT)); + Assert.assertTrue(e.getErrorCode().equals(ErrorConstants.INVALID_AUTH_FACTOR_TYPE_OR_CHALLENGE_FORMAT)); } } @@ -230,8 +231,6 @@ public void bindWallet_withInvalidKeyBindingResult_thenFail() throws IOException walletBindingRequest.setPublicKey( (Map) objectMapper.readValue(clientJWK.toJSONString(), HashMap.class)); - when(mockKeyBindingWrapperService.doKeyBinding(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())) - .thenReturn(null); try { Assert.assertNotNull(keyBindingService.bindWallet(walletBindingRequest, new HashMap<>())); Assert.fail(); diff --git a/client-management-service-impl/pom.xml b/client-management-service-impl/pom.xml index 99a0449a8..3d4810278 100644 --- a/client-management-service-impl/pom.xml +++ b/client-management-service-impl/pom.xml @@ -6,7 +6,7 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet diff --git a/consent-service-impl/pom.xml b/consent-service-impl/pom.xml new file mode 100644 index 000000000..60ba82662 --- /dev/null +++ b/consent-service-impl/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + io.mosip.esignet + esignet-parent + 1.1.0-SNAPSHOT + + + consent-service-impl + + + 11 + 11 + UTF-8 + ${project.version} + + + + io.mosip.esignet + esignet-core + ${esignet.core.version} + + + + org.modelmapper + modelmapper + 3.1.1 + + + \ No newline at end of file diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/config/ModelMapperConfig.java b/consent-service-impl/src/main/java/io/mosip/esignet/config/ModelMapperConfig.java new file mode 100644 index 000000000..32c99cd43 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/config/ModelMapperConfig.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.esignet.config; + + +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ModelMapperConfig { + + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } + +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentDetail.java b/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentDetail.java new file mode 100644 index 000000000..4f0073f51 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentDetail.java @@ -0,0 +1,82 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.entity; + +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.UUID; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLAIM; +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLIENT_ID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class ConsentDetail { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + @NotNull(message = INVALID_CLIENT_ID) + @Column(name = "client_id") + private String clientId; + + @NotNull + @Column(name = "psu_token") + private String psuToken; + + @NotNull(message = INVALID_CLAIM) + @Column(name = "claims") + private String claims; + + /* + It stores the requested authorization scopes from the relying party in a json string + { + "scope" : "boolean" (essential or optional) + } + */ + @NotNull + @Column(name = "authorization_scopes") + private String authorizationScopes; + + @NotNull + @Column(name = "cr_dtimes") + private LocalDateTime createdtimes; + + @Column(name = "expire_dtimes") + private LocalDateTime expiredtimes; + + @Column(name = "signature") + private String signature; + + @Column(name = "hash") + private String hash; + + @Column(name = "accepted_claims") + private String acceptedClaims; + + @Column(name = "permitted_scopes") + private String permittedScopes; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + ConsentDetail consentDetail = (ConsentDetail) o; + return getId() != null && Objects.equals(getId(), consentDetail.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentHistory.java b/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentHistory.java new file mode 100644 index 000000000..52823d34d --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/entity/ConsentHistory.java @@ -0,0 +1,78 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.UUID; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLAIM; +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLIENT_ID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class ConsentHistory { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + @NotNull(message = INVALID_CLIENT_ID) + @Column(name = "client_id") + private String clientId; + + @NotNull + @Column(name = "psu_token") + private String psuToken; + + @NotNull(message = INVALID_CLAIM) + @Column(name = "claims") + private String claims; + + @NotNull + @Column(name = "authorization_scopes") + private String authorizationScopes; + + @NotNull + @Column(name = "cr_dtimes") + private LocalDateTime createdtimes; + + @Column(name = "expire_dtimes") + private LocalDateTime expiredtimes; + + @Column(name = "signature") + private String signature; + + @Column(name = "hash") + private String hash; + + @Column(name = "accepted_claims") + private String acceptedClaims; + + @Column(name = "permitted_scopes") + private String permittedScopes; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + ConsentHistory consentDetail = (ConsentHistory) o; + return getId() != null && Objects.equals(getId(), consentDetail.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/ConsentMapper.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/ConsentMapper.java new file mode 100644 index 000000000..5c7ed9448 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/ConsentMapper.java @@ -0,0 +1,50 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.core.dto.ConsentDetail; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.entity.ConsentHistory; +import io.mosip.esignet.mapper.converter.*; +import org.modelmapper.ModelMapper; + + + +public class ConsentMapper { + + private ConsentMapper(){} + + private static final ModelMapper modelMapper = new ModelMapper(); + + static { + ObjectMapper objectMapper = new ObjectMapper(); + modelMapper.addConverter(new ClaimsToStringConverter(objectMapper)); + modelMapper.addConverter(new StringToClaimsConverter(objectMapper)); + modelMapper.addConverter(new MapToStringConverter(objectMapper)); + modelMapper.addConverter(new StringToMapConverter(objectMapper)); + modelMapper.addConverter(new ListToStringConverter()); + modelMapper.addConverter(new StringToListConverter()); + modelMapper.addMappings(new CustomConsentRequestMapping()); + modelMapper.addMappings(new CustomConsentHistoryMapping()); + } + + public static io.mosip.esignet.entity.ConsentDetail toEntity(ConsentDetail consentDetailDTo) { + return modelMapper.map(consentDetailDTo, io.mosip.esignet.entity.ConsentDetail.class); + } + + public static io.mosip.esignet.entity.ConsentDetail toEntity(UserConsent userConsent) { + return modelMapper.map(userConsent, io.mosip.esignet.entity.ConsentDetail.class); + } + + public static ConsentDetail toDto(io.mosip.esignet.entity.ConsentDetail consentDetail) { + return modelMapper.map(consentDetail, ConsentDetail.class); + } + + public static ConsentHistory toConsentHistoryEntity(UserConsent userConsent){ + return modelMapper.map(userConsent, ConsentHistory.class); + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentHistoryMapping.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentHistoryMapping.java new file mode 100644 index 000000000..6e1b5d1ad --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentHistoryMapping.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.esignet.mapper; + +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.entity.ConsentHistory; +import org.modelmapper.PropertyMap; + +public class CustomConsentHistoryMapping extends PropertyMap { + @Override + protected void configure() { + // Skip the 'id' field when mapping + skip().setId(null); + } +} + + diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentRequestMapping.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentRequestMapping.java new file mode 100644 index 000000000..3de2f4f34 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/CustomConsentRequestMapping.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.esignet.mapper; + +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.entity.ConsentDetail; +import org.modelmapper.PropertyMap; + +public class CustomConsentRequestMapping extends PropertyMap { + @Override + protected void configure() { + // Skip the 'id' field when mapping + skip().setId(null); + } +} + + diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ClaimsToStringConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ClaimsToStringConverter.java new file mode 100644 index 000000000..961768890 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ClaimsToStringConverter.java @@ -0,0 +1,35 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.core.exception.EsignetException; +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLAIM; + +@Slf4j +public class ClaimsToStringConverter implements Converter { + private final ObjectMapper objectMapper; + + public ClaimsToStringConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public String convert(MappingContext context) { + Claims claims = context.getSource(); + try { + return claims != null ? objectMapper.writeValueAsString(claims) : ""; + } catch (JsonProcessingException e) { + throw new EsignetException(INVALID_CLAIM); + } + } +} \ No newline at end of file diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ListToStringConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ListToStringConverter.java new file mode 100644 index 000000000..ec9664ed5 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/ListToStringConverter.java @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper.converter; + +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import java.util.List; + +public class ListToStringConverter implements Converter, String> { + @Override + public String convert(MappingContext, String> context) { + List source = context.getSource(); + return source == null ? "" : String.join(",", context.getSource()); + } +} \ No newline at end of file diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/MapToStringConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/MapToStringConverter.java new file mode 100644 index 000000000..5e832d45d --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/MapToStringConverter.java @@ -0,0 +1,35 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.core.exception.EsignetException; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import java.util.Map; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_PERMITTED_SCOPE; + +public class MapToStringConverter implements Converter,String> { + + private final ObjectMapper objectMapper; + + public MapToStringConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public String convert(MappingContext, String> mappingContext) { + Map map = mappingContext.getSource(); + try{ + return map!=null?objectMapper.writeValueAsString(map):""; + }catch (JsonProcessingException e) { + throw new EsignetException(INVALID_PERMITTED_SCOPE); + } + } +} \ No newline at end of file diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToClaimsConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToClaimsConverter.java new file mode 100644 index 000000000..921ac5d18 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToClaimsConverter.java @@ -0,0 +1,37 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.core.exception.EsignetException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_CLAIM; + +@Slf4j +public class StringToClaimsConverter implements Converter +{ + private final ObjectMapper objectMapper; + + public StringToClaimsConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Claims convert(MappingContext context) { + String claims = context.getSource(); + try { + return StringUtils.isNotBlank(claims) ? objectMapper.readValue(claims, Claims.class) : null; + } catch (JsonProcessingException e) { + throw new EsignetException(INVALID_CLAIM); + } + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToListConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToListConverter.java new file mode 100644 index 000000000..2edf94a8c --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToListConverter.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.esignet.mapper.converter; + +import org.apache.commons.lang3.StringUtils; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import java.util.Arrays; +import java.util.List; + +public class StringToListConverter implements Converter> { + @Override + public List convert(MappingContext> context) { + String source = context.getSource(); + return StringUtils.isEmpty(source) ? List.of(): Arrays.asList(context.getSource().split(",")); + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToMapConverter.java b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToMapConverter.java new file mode 100644 index 000000000..b7951e29f --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/mapper/converter/StringToMapConverter.java @@ -0,0 +1,39 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.mapper.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.core.exception.EsignetException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; + +import java.util.Collections; +import java.util.Map; + +import static io.mosip.esignet.core.constants.ErrorConstants.INVALID_PERMITTED_SCOPE; + +@Slf4j +public class StringToMapConverter implements Converter> { + + private final ObjectMapper objectMapper; + + public StringToMapConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Map convert(MappingContext> mappingContext) { + String authorizeScopes= mappingContext.getSource(); + try{ + return StringUtils.isNotBlank(authorizeScopes) ? objectMapper.readValue(authorizeScopes,Map.class): Collections.emptyMap(); + } catch (JsonProcessingException e) { + throw new EsignetException(INVALID_PERMITTED_SCOPE); + } + } +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentHistoryRepository.java b/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentHistoryRepository.java new file mode 100644 index 000000000..11daeaae1 --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentHistoryRepository.java @@ -0,0 +1,15 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.repository; + + + +import io.mosip.esignet.entity.ConsentHistory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ConsentHistoryRepository extends JpaRepository{ + +} \ No newline at end of file diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentRepository.java b/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentRepository.java new file mode 100644 index 000000000..80b49a94f --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/repository/ConsentRepository.java @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.repository; + +import io.mosip.esignet.entity.ConsentDetail; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.UUID; +@Repository +public interface ConsentRepository extends JpaRepository { + boolean existsByClientIdAndPsuToken(String clientId, String psuToken); + Optional findByClientIdAndPsuToken(String clientId, String psuToken); + void deleteByClientIdAndPsuToken(String clientId, String psuToken); + +} diff --git a/consent-service-impl/src/main/java/io/mosip/esignet/services/ConsentServiceImpl.java b/consent-service-impl/src/main/java/io/mosip/esignet/services/ConsentServiceImpl.java new file mode 100644 index 000000000..ec0c48a9d --- /dev/null +++ b/consent-service-impl/src/main/java/io/mosip/esignet/services/ConsentServiceImpl.java @@ -0,0 +1,95 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.services; + + +import io.mosip.esignet.api.spi.AuditPlugin; +import io.mosip.esignet.api.util.Action; +import io.mosip.esignet.api.util.ActionStatus; +import io.mosip.esignet.core.dto.ConsentDetail; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.core.dto.UserConsentRequest; +import io.mosip.esignet.core.spi.ConsentService; +import io.mosip.esignet.core.util.AuditHelper; +import io.mosip.esignet.entity.ConsentHistory; +import io.mosip.esignet.mapper.ConsentMapper; +import io.mosip.esignet.repository.ConsentHistoryRepository; +import io.mosip.esignet.repository.ConsentRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Optional; + +@Service +@Slf4j +public class ConsentServiceImpl implements ConsentService { + + @Autowired + private ConsentRepository consentRepository; + + @Autowired + private ConsentHistoryRepository consentHistoryRepository; + + @Autowired + private AuditPlugin auditWrapper; + + @Value("${mosip.esignet.audit.claim-name:preferred_username}") + private String claimName; + + @Override + public Optional getUserConsent(UserConsentRequest userConsentRequest) { + + Optional consentOptional = consentRepository. + findByClientIdAndPsuToken(userConsentRequest.getClientId(), + userConsentRequest.getPsuToken()); + if (consentOptional.isPresent()) { + ConsentDetail consentDetailDto = ConsentMapper.toDto( consentOptional.get()); + + return Optional.of(consentDetailDto); + } + auditWrapper.logAudit(AuditHelper.getClaimValue(SecurityContextHolder.getContext(), claimName), + Action.GET_USER_CONSENT, ActionStatus.SUCCESS, + AuditHelper.buildAuditDto(userConsentRequest.getClientId()), null); + return Optional.empty(); + } + + @Override + @Transactional + public ConsentDetail saveUserConsent(UserConsent userConsent) { + Optional clientDetailOptional = + consentRepository.findByClientIdAndPsuToken(userConsent.getClientId(), userConsent.getPsuToken()); + if(clientDetailOptional.isPresent()) { + consentRepository.deleteByClientIdAndPsuToken(userConsent.getClientId(), userConsent.getPsuToken()); + consentRepository.flush(); + } + LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC); + //convert ConsentRequest to Entity + ConsentHistory consentHistory = ConsentMapper.toConsentHistoryEntity(userConsent); + consentHistory.setCreatedtimes(now); + consentHistoryRepository.save(consentHistory); + + io.mosip.esignet.entity.ConsentDetail consentDetail =ConsentMapper.toEntity(userConsent); + consentDetail.setCreatedtimes(now); + + ConsentDetail consentDetailDto =ConsentMapper.toDto(consentRepository.save(consentDetail)); + auditWrapper.logAudit(AuditHelper.getClaimValue(SecurityContextHolder.getContext(), claimName), + Action.SAVE_USER_CONSENT, ActionStatus.SUCCESS, + AuditHelper.buildAuditDto(userConsent.getClientId()), null); + return consentDetailDto; + } + + @Override + @Transactional + public void deleteUserConsent(String clientId, String psuToken) { + consentRepository.deleteByClientIdAndPsuToken(clientId, psuToken); + } +} diff --git a/consent-service-impl/src/test/java/io/mosip/esignet/ConsentDetailRepositoryTest.java b/consent-service-impl/src/test/java/io/mosip/esignet/ConsentDetailRepositoryTest.java new file mode 100644 index 000000000..52b810330 --- /dev/null +++ b/consent-service-impl/src/test/java/io/mosip/esignet/ConsentDetailRepositoryTest.java @@ -0,0 +1,208 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet; + +import io.mosip.esignet.entity.ConsentDetail; +import io.mosip.esignet.repository.ConsentRepository; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.validation.ConstraintViolationException; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; + +@RunWith(SpringRunner.class) +@DataJpaTest +public class ConsentDetailRepositoryTest { + + @Autowired + private ConsentRepository consentRepository; + + @Test + public void createConsent_withValidDetail_thenPass() { + + ConsentDetail consentDetail =new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setClientId("123"); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + consentDetail.setHash("hash"); + consentDetail.setAcceptedClaims("claim"); + consentDetail.setPermittedScopes("scope"); + consentDetail =consentRepository.save(consentDetail); + Assert.assertNotNull(consentDetail); + + Optional result; + + result = consentRepository.findByClientIdAndPsuToken("123", "abc"); + Assert.assertTrue(result.isPresent()); + + result = consentRepository.findByClientIdAndPsuToken("123", "abcd"); + Assert.assertFalse(result.isPresent()); + } + + @Test + public void createConsent_withNullClientId_thenFail() { + + ConsentDetail consentDetail = new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setId(uuid); + consentDetail.setClientId(null); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + try { + consentRepository.saveAndFlush(consentDetail); + } catch (ConstraintViolationException e) { + Assert.assertTrue(e.getConstraintViolations().stream() + .anyMatch( v -> v.getPropertyPath().toString().equals("clientId"))); + return; + } + Assert.fail(); + } + + @Test + public void createConsent_withNullPsuValue_thenFail() { + + ConsentDetail consentDetail = new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setId(uuid); + consentDetail.setClientId("123"); + consentDetail.setPsuToken(null); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + try { + consentRepository.saveAndFlush(consentDetail); + } catch (ConstraintViolationException e) { + Assert.assertTrue(e.getConstraintViolations().stream() + .anyMatch( v -> v.getPropertyPath().toString().equals("psuToken"))); + return; + } + Assert.fail(); + } + + @Test + public void createConsent_withNullClaims_thenFail() { + + ConsentDetail consentDetail = new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setId(uuid); + consentDetail.setClientId("123"); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims(null); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + try { + consentRepository.saveAndFlush(consentDetail); + } catch (ConstraintViolationException e) { + Assert.assertTrue(e.getConstraintViolations().stream() + .anyMatch( v -> v.getPropertyPath().toString().equals("claims"))); + return; + } + Assert.fail(); + } + + @Test + public void createConsent_withNullAuthorizationScopes_thenFail() { + + ConsentDetail consentDetail = new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setId(uuid); + consentDetail.setClientId("123"); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes(null); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + try { + consentRepository.saveAndFlush(consentDetail); + } catch (ConstraintViolationException e) { + Assert.assertTrue(e.getConstraintViolations().stream() + .anyMatch( v -> v.getPropertyPath().toString().equals("authorizationScopes"))); + return; + } + Assert.fail(); + } + + @Test + public void createConsent_withNullCreatedtimes_thenFail() { + + ConsentDetail consentDetail = new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + consentDetail.setId(uuid); + consentDetail.setClientId("123"); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(null); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + try { + consentRepository.saveAndFlush(consentDetail); + } catch (ConstraintViolationException e) { + Assert.assertTrue(e.getConstraintViolations().stream() + .anyMatch( v -> v.getPropertyPath().toString().equals("createdtimes"))); + return; + } + Assert.fail(); + } + + @Test + public void createAndDeleteConsent_withValidDetail_thenPass() { + + ConsentDetail consentDetail =new ConsentDetail(); + UUID uuid=UUID.randomUUID(); + LocalDateTime date = LocalDateTime.of(2019, 12, 12, 12, 12, 12); + consentDetail.setClientId("123"); + consentDetail.setPsuToken("abc"); + consentDetail.setClaims("claims"); + consentDetail.setAuthorizationScopes("authorizationScopes"); + consentDetail.setCreatedtimes(date); + consentDetail.setExpiredtimes(LocalDateTime.now()); + consentDetail.setSignature("signature"); + consentDetail.setHash("hash"); + consentDetail.setAcceptedClaims("claim"); + consentDetail.setPermittedScopes("scope"); + consentDetail =consentRepository.save(consentDetail); + Assert.assertNotNull(consentDetail); + + Optional result; + + result = consentRepository.findByClientIdAndPsuToken("123", "abc"); + Assert.assertTrue(result.isPresent()); + + result = consentRepository.findByClientIdAndPsuToken("123", "abcd"); + Assert.assertFalse(result.isPresent()); + consentRepository.deleteByClientIdAndPsuToken(consentDetail.getClientId(),consentDetail.getPsuToken()); + consentRepository.flush(); + Optional consentDetailOptional = consentRepository.findByClientIdAndPsuToken("123", "abc"); + Assert.assertFalse(consentDetailOptional.isPresent()); + } + +} diff --git a/consent-service-impl/src/test/java/io/mosip/esignet/ConsentServiceImplTest.java b/consent-service-impl/src/test/java/io/mosip/esignet/ConsentServiceImplTest.java new file mode 100644 index 000000000..9fe489724 --- /dev/null +++ b/consent-service-impl/src/test/java/io/mosip/esignet/ConsentServiceImplTest.java @@ -0,0 +1,151 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet; + +import io.mosip.esignet.api.dto.ClaimDetail; +import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.api.spi.AuditPlugin; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.core.dto.UserConsentRequest; +import io.mosip.esignet.entity.ConsentDetail; +import io.mosip.esignet.entity.ConsentHistory; +import io.mosip.esignet.repository.ConsentHistoryRepository; +import io.mosip.esignet.repository.ConsentRepository; +import io.mosip.esignet.services.ConsentServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.modelmapper.MappingException; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class ConsentServiceImplTest { + + + @Mock + ConsentRepository consentRepository; + + @Mock + ConsentHistoryRepository consentHistoryRepository; + + @Mock + AuditPlugin auditWrapper; + + @InjectMocks + ConsentServiceImpl consentService; + + + + @Test + public void getUserConsent_withValidDetails_thenPass() throws Exception{ + ConsentDetail consentDetail = new ConsentDetail(); + consentDetail.setId(UUID.randomUUID()); + consentDetail.setClientId("1234"); + consentDetail.setClaims("{\"userinfo\":{\"given_name\":{\"essential\":true},\"phone_number\":null,\"email\":{\"essential\":true},\"picture\":{\"essential\":false},\"gender\":{\"essential\":false}},\"id_token\":{}}"); + consentDetail.setCreatedtimes(LocalDateTime.now()); + consentDetail.setPsuToken("psuValue"); + consentDetail.setExpiredtimes(LocalDateTime.now()); + + Optional consentOptional = Optional.of(consentDetail); + Mockito.when(consentRepository.findByClientIdAndPsuToken(Mockito.anyString(),Mockito.anyString())).thenReturn(consentOptional); + + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId("1234"); + userConsentRequest.setPsuToken("psuValue"); + + Optional userConsentDto = consentService.getUserConsent(userConsentRequest); + Assert.assertNotNull(userConsentDto); + Assert.assertEquals("1234", userConsentDto.get().getClientId()); + Assert.assertEquals("psuValue", userConsentDto.get().getPsuToken()); + + } + + @Test + public void getUserConsent_withInValidClaimsDetails_thenFail() { + ConsentDetail consentDetail = new ConsentDetail(); + consentDetail.setId(UUID.randomUUID()); + consentDetail.setClientId("1234"); + consentDetail.setCreatedtimes(LocalDateTime.now()); + consentDetail.setClaims("claims"); + consentDetail.setPsuToken("psuValue"); + consentDetail.setExpiredtimes(LocalDateTime.now()); + + Optional consentOptional = Optional.of(consentDetail); + Mockito.when(consentRepository.findByClientIdAndPsuToken(Mockito.anyString(),Mockito.anyString())).thenReturn(consentOptional); + + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId("1234"); + userConsentRequest.setPsuToken("psuValue"); + try{ + Optional userConsentDto = consentService.getUserConsent(userConsentRequest); + Assert.fail(); + }catch (MappingException e){ + Assert.assertTrue(true); + } + } + + @Test + public void saveUserConsent_withValidDetails_thenPass() throws Exception{ + Claims claims = new Claims(); + + Map userinfo = new HashMap<>(); + Map id_token = new HashMap<>(); + ClaimDetail userinfoClaimDetail = new ClaimDetail("value1", new String[]{"value1a", "value1b"}, true); + ClaimDetail idTokenClaimDetail = new ClaimDetail("value2", new String[]{"value2a", "value2b"}, false); + userinfo.put("userinfoKey", userinfoClaimDetail); + id_token.put("idTokenKey", idTokenClaimDetail); + claims.setUserinfo(userinfo); + claims.setId_token(id_token); + + + UserConsent userConsent = new UserConsent(); + userConsent.setClientId("1234"); + userConsent.setPsuToken("psuValue"); + userConsent.setClaims(claims); + + Map authorizeScopes = Map.of("given_name",true,"email",true); + userConsent.setAuthorizationScopes(authorizeScopes); + userConsent.setExpirydtimes(LocalDateTime.now()); + userConsent.setSignature("signature"); + + ConsentDetail consentDetail = new ConsentDetail(); + consentDetail.setId(UUID.randomUUID()); + consentDetail.setClientId("1234"); + consentDetail.setClaims("{\"userinfo\":{\"given_name\":{\"essential\":true},\"phone_number\":null,\"email\":{\"essential\":true},\"picture\":{\"essential\":false},\"gender\":{\"essential\":false}},\"id_token\":{}}"); + consentDetail.setCreatedtimes(LocalDateTime.now()); + consentDetail.setPsuToken("psuValue"); + consentDetail.setExpiredtimes(LocalDateTime.now()); + + Mockito.when(consentRepository.save(Mockito.any())).thenReturn(consentDetail); + Mockito.when(consentHistoryRepository.save(Mockito.any())).thenReturn(new ConsentHistory()); + Mockito.when(consentRepository.findByClientIdAndPsuToken(Mockito.any(),Mockito.any())).thenReturn(Optional.empty()); + io.mosip.esignet.core.dto.ConsentDetail userConsentDtoDetail = consentService.saveUserConsent(userConsent); + Assert.assertNotNull(userConsentDtoDetail); + Assert.assertEquals("1234", userConsentDtoDetail.getClientId()); + + } + + @Test + public void deleteConsentByClientIdAndPsuToken_thenPass(){ + String clientId = "test-client-id"; + String psuToken = "test-psu-token"; + consentService.deleteUserConsent(clientId,psuToken); + Mockito.verify(consentRepository).deleteByClientIdAndPsuToken(clientId, psuToken); + } + + +} diff --git a/consent-service-impl/src/test/java/io/mosip/esignet/TestApplication.java b/consent-service-impl/src/test/java/io/mosip/esignet/TestApplication.java new file mode 100644 index 000000000..1a2727be6 --- /dev/null +++ b/consent-service-impl/src/test/java/io/mosip/esignet/TestApplication.java @@ -0,0 +1,41 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet; + +import io.mosip.esignet.api.dto.AuditDTO; +import io.mosip.esignet.api.spi.AuditPlugin; +import io.mosip.esignet.api.util.Action; +import io.mosip.esignet.api.util.ActionStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ActiveProfiles; + +@Slf4j +@SpringBootApplication +@ActiveProfiles(value = {"test"}) +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @Bean + public AuditPlugin loggerAuditWrapper() { + return new AuditPlugin() { + @Override + public void logAudit(Action action, ActionStatus status, AuditDTO audit, Throwable t) { + //do nothing + } + + @Override + public void logAudit(String username, Action action, ActionStatus status, AuditDTO audit, Throwable t) { + //do nothing + } + }; + } + +} diff --git a/consent-service-impl/src/test/resources/bootstrap.properties b/consent-service-impl/src/test/resources/bootstrap.properties new file mode 100644 index 000000000..a4d9bc37e --- /dev/null +++ b/consent-service-impl/src/test/resources/bootstrap.properties @@ -0,0 +1,17 @@ +spring.profiles.active=test +server.port=8088 +##----------------------------------------- Database properties ------------------------------------------- + +spring.jpa.defer-datasource-initialization=false +spring.jpa.hibernate.ddl-auto=none +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true +#Enabling H2 console +spring.h2.console.enabled=false +spring.datasource.url=jdbc:h2:mem:mosip_idp +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=test +spring.datasource.password=test + +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/consent-service-impl/src/test/resources/schema.sql b/consent-service-impl/src/test/resources/schema.sql new file mode 100644 index 000000000..4e1388419 --- /dev/null +++ b/consent-service-impl/src/test/resources/schema.sql @@ -0,0 +1,30 @@ +create table consent_history ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id) +); +create table consent_detail ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id), + CONSTRAINT unique_client_token UNIQUE (client_id, psu_token) +); +CREATE INDEX IF NOT EXISTS idx_consent_psu_client ON consent_detail(psu_token, client_id, cr_dtimes); \ No newline at end of file diff --git a/db_scripts/README.md b/db_scripts/README.md index db4348ca9..63dc97928 100644 --- a/db_scripts/README.md +++ b/db_scripts/README.md @@ -1,13 +1,32 @@ -# e-Signet +# e-Signet Database Open ID based Identity provider for large scale authentication. +## Overview +This folder containers various SQL scripts to create database and tables in postgres. +The tables are described under `/ddl/`. +Default data that's populated in the tables is present under `/dml` folder. + ## Prerequisites * Make sure DB changes for IDA and PMS are up to date. * If not upgraded, IDA DB using the [release script](https://github.com/mosip/id-authentication/tree/develop/db_release_scripts). * If not upgraded, PMS DB using the [release script](https://github.com/mosip/partner-management-services/tree/develop/db_release_scripts). - -## Initialize esignet DB -* To initialize esignet DB, run below script. +* Command line utilities: + - kubectl + - helm +* Helm repos: ```sh - ./init_db.sh + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add mosip https://mosip.github.io/mosip-helm + ``` + +## Install in existing MOSIP K8 Cluster +These scripts are automatically run with below mentioned script in existing k8 cluster with Postgres installed. +### Install +* Set your kube_config file or kube_config variable on PC. +* Update `init_values.yaml` with db-common-password from the postgres namespace in the required field `dbUserPasswords.dbuserPassword` and ensure `databases.mosip_esignet` is enabled. + ``` + ./init_db.sh` ``` + +## Install for developers +Developers may run the SQLs using `/deploy.sh` script. diff --git a/db_scripts/mosip_esignet/ddl.sql b/db_scripts/mosip_esignet/ddl.sql index e8a413073..0ea32c676 100644 --- a/db_scripts/mosip_esignet/ddl.sql +++ b/db_scripts/mosip_esignet/ddl.sql @@ -5,3 +5,5 @@ \ir ddl/esignet-key_policy_def.sql \ir ddl/esignet-key_store.sql \ir ddl/esignet-public_key_registry.sql +\ir ddl/esignet-consent.sql +\ir ddl/esignet-consent_history.sql diff --git a/db_scripts/mosip_esignet/ddl/esignet-consent.sql b/db_scripts/mosip_esignet/ddl/esignet-consent.sql new file mode 100644 index 000000000..9bd1f8b50 --- /dev/null +++ b/db_scripts/mosip_esignet/ddl/esignet-consent.sql @@ -0,0 +1,47 @@ +-- 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/. +-- ------------------------------------------------------------------------------------------------- +-- Database Name: mosip_esignet +-- Table Name : consent_detail +-- Purpose : To store user consent details +-- +-- Create By : Hitesh C +-- Created Date : May-2023 +-- +-- Modified Date Modified By Comments / Remarks +-- ------------------------------------------------------------------------------------------ +-- ------------------------------------------------------------------------------------------ + +create table consent_detail ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id), + CONSTRAINT unique_client_token UNIQUE (client_id, psu_token) +); + +CREATE INDEX IF NOT EXISTS idx_consent_psu_client ON consent_detail(psu_token, client_id); + +COMMENT ON TABLE consent_detail IS 'Contains user consent details'; + +COMMENT ON COLUMN consent_detail.id IS 'UUID : Unique id associated with each consent'; +COMMENT ON COLUMN consent_detail.client_id IS 'Client_id: associated with relying party'; +COMMENT ON COLUMN consent_detail.psu_token IS 'PSU token associated with user consent'; +COMMENT ON COLUMN consent_detail.claims IS 'Json of requested and user accepted claims'; +COMMENT ON COLUMN consent_detail.authorization_scopes IS 'Json string of requested authorization scope'; +COMMENT ON COLUMN consent_detail.cr_dtimes IS 'Consent creation date'; +COMMENT ON COLUMN consent_detail.expire_dtimes IS 'Expiration date'; +COMMENT ON COLUMN consent_detail.signature IS 'Signature of consent object '; +COMMENT ON COLUMN consent_detail.hash IS 'hash of consent object'; +COMMENT ON COLUMN consent_detail.accepted_claims IS 'Accepted Claims by the user'; +COMMENT ON COLUMN consent_detail.permitted_scopes IS 'Accepted Scopes by the user'; + diff --git a/db_scripts/mosip_esignet/ddl/esignet-consent_history.sql b/db_scripts/mosip_esignet/ddl/esignet-consent_history.sql new file mode 100644 index 000000000..0d90fa34f --- /dev/null +++ b/db_scripts/mosip_esignet/ddl/esignet-consent_history.sql @@ -0,0 +1,45 @@ +-- 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/. +-- ------------------------------------------------------------------------------------------------- +-- Database Name: mosip_esignet +-- Table Name : consent_history +-- Purpose : To store user consent details +-- +-- Create By : Hitesh C +-- Created Date : May-2023 +-- +-- Modified Date Modified By Comments / Remarks +-- ------------------------------------------------------------------------------------------ +-- ------------------------------------------------------------------------------------------ + +create table consent_history ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id) +); +CREATE INDEX IF NOT EXISTS idx_consent_history_psu_client ON consent_history(psu_token, client_id); + +COMMENT ON TABLE consent_history IS 'Contains user consent details'; + +COMMENT ON COLUMN consent_history.id IS 'UUID : Unique id associated with each consent'; +COMMENT ON COLUMN consent_history.client_id IS 'Client_id: associated with relying party'; +COMMENT ON COLUMN consent_history.psu_token IS 'PSU token associated with user consent'; +COMMENT ON COLUMN consent_history.claims IS 'Json of requested and user accepted claims'; +COMMENT ON COLUMN consent_history.authorization_scopes IS 'Json string of requested authorization scope'; +COMMENT ON COLUMN consent_history.cr_dtimes IS 'Consent creation date'; +COMMENT ON COLUMN consent_history.expire_dtimes IS 'Expiration date'; +COMMENT ON COLUMN consent_history.signature IS 'Signature of consent object '; +COMMENT ON COLUMN consent_history.hash IS 'hash of consent object'; +COMMENT ON COLUMN consent_history.accepted_claims IS 'Accepted Claims by the user'; +COMMENT ON COLUMN consent_history.permitted_scopes IS 'Accepted Scopes by the user'; + diff --git a/db_upgrade_script/README.md b/db_upgrade_script/README.md new file mode 100644 index 000000000..b09ed8119 --- /dev/null +++ b/db_upgrade_script/README.md @@ -0,0 +1 @@ +Directory contains sql scripts to be executed for DB migrations. \ No newline at end of file diff --git a/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_rollback.sql b/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_rollback.sql new file mode 100644 index 000000000..7a97b57ed --- /dev/null +++ b/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_rollback.sql @@ -0,0 +1,19 @@ +-- 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/. +-- ------------------------------------------------------------------------------------------------- +-- Database Name: mosip_esignet +-- Table Name : consent_detail +-- Purpose : To store user consent details +-- +-- Create By : Hitesh C +-- Created Date : August-2023 +-- +-- Modified Date Modified By Comments / Remarks +-- ------------------------------------------------------------------------------------------ +-- ------------------------------------------------------------------------------------------ + +\c mosip_esignet + +drop table consent_detail; +drop table consent_history; \ No newline at end of file diff --git a/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_upgrade.sql b/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_upgrade.sql new file mode 100644 index 000000000..c40f958f1 --- /dev/null +++ b/db_upgrade_script/mosip_esignet/sql/1.0.0_to_1.1.0_upgrade.sql @@ -0,0 +1,79 @@ +-- 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/. +-- ------------------------------------------------------------------------------------------------- +-- Database Name: mosip_esignet +-- Table Name : consent_detail +-- Purpose : To store user consent details +-- +-- Create By : Hitesh C +-- Created Date : August-2023 +-- +-- Modified Date Modified By Comments / Remarks +-- ------------------------------------------------------------------------------------------ +-- ------------------------------------------------------------------------------------------ +\c mosip_esignet + +create table consent_detail ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id), + CONSTRAINT unique_client_token UNIQUE (client_id, psu_token) +); + +CREATE INDEX IF NOT EXISTS idx_consent_psu_client ON consent_detail(psu_token, client_id); + +COMMENT ON TABLE consent_detail IS 'Contains user consent details'; + +COMMENT ON COLUMN consent_detail.id IS 'UUID : Unique id associated with each consent'; +COMMENT ON COLUMN consent_detail.client_id IS 'Client_id: associated with relying party'; +COMMENT ON COLUMN consent_detail.psu_token IS 'PSU token associated with user consent'; +COMMENT ON COLUMN consent_detail.claims IS 'Json of requested and user accepted claims'; +COMMENT ON COLUMN consent_detail.authorization_scopes IS 'Json string of requested authorization scope'; +COMMENT ON COLUMN consent_detail.cr_dtimes IS 'Consent creation date'; +COMMENT ON COLUMN consent_detail.expire_dtimes IS 'Expiration date'; +COMMENT ON COLUMN consent_detail.signature IS 'Signature of consent object '; +COMMENT ON COLUMN consent_detail.hash IS 'hash of consent object'; +COMMENT ON COLUMN consent_detail.accepted_claims IS 'Accepted Claims by the user'; +COMMENT ON COLUMN consent_detail.permitted_scopes IS 'Accepted Scopes by the user'; + + +create table consent_history ( + id UUID NOT NULL, + client_id VARCHAR NOT NULL, + psu_token VARCHAR NOT NULL, + claims VARCHAR NOT NULL, + authorization_scopes VARCHAR NOT NULL, + cr_dtimes TIMESTAMP DEFAULT NOW() NOT NULL, + expire_dtimes TIMESTAMP, + signature VARCHAR, + hash VARCHAR, + accepted_claims VARCHAR, + permitted_scopes VARCHAR, + PRIMARY KEY (id) +); +CREATE INDEX IF NOT EXISTS idx_consent_history_psu_client ON consent_history(psu_token, client_id); + +COMMENT ON TABLE consent_history IS 'Contains user consent details'; + +COMMENT ON COLUMN consent_history.id IS 'UUID : Unique id associated with each consent'; +COMMENT ON COLUMN consent_history.client_id IS 'Client_id: associated with relying party'; +COMMENT ON COLUMN consent_history.psu_token IS 'PSU token associated with user consent'; +COMMENT ON COLUMN consent_history.claims IS 'Json of requested and user accepted claims'; +COMMENT ON COLUMN consent_history.authorization_scopes IS 'Json string of requested authorization scope'; +COMMENT ON COLUMN consent_history.cr_dtimes IS 'Consent creation date'; +COMMENT ON COLUMN consent_history.expire_dtimes IS 'Expiration date'; +COMMENT ON COLUMN consent_history.signature IS 'Signature of consent object '; +COMMENT ON COLUMN consent_history.hash IS 'hash of consent object'; +COMMENT ON COLUMN consent_history.accepted_claims IS 'Accepted Claims by the user'; +COMMENT ON COLUMN consent_history.permitted_scopes IS 'Accepted Scopes by the user'; + diff --git a/db_upgrade_script/mosip_esignet/upgrade.properties b/db_upgrade_script/mosip_esignet/upgrade.properties new file mode 100644 index 000000000..6d4bafcd3 --- /dev/null +++ b/db_upgrade_script/mosip_esignet/upgrade.properties @@ -0,0 +1,12 @@ +ACTION=upgrade +MOSIP_DB_NAME= +DB_SERVERIP= +DB_PORT= +SU_USER=postgres +SU_USER_PWD= +SYS_ADMIN_USER= +SYS_ADMIN_PWD= +DEFAULT_DB_NAME=postgres +DBUSER_PWD= +CURRENT_VERSION= +UPGRADE_VERSION= diff --git a/db_upgrade_script/mosip_esignet/upgrade.sh b/db_upgrade_script/mosip_esignet/upgrade.sh new file mode 100644 index 000000000..6e9ccdc8a --- /dev/null +++ b/db_upgrade_script/mosip_esignet/upgrade.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +set -e +properties_file="$1" +echo `date "+%m/%d/%Y %H:%M:%S"` ": $properties_file" +if [ -f "$properties_file" ] +then + echo `date "+%m/%d/%Y %H:%M:%S"` ": Property file \"$properties_file\" found." + while IFS='=' read -r key value + do + key=$(echo $key | tr '.' '_') + eval ${key}=\${value} + done < "$properties_file" +else + echo `date "+%m/%d/%Y %H:%M:%S"` ": Property file not found, Pass property file name as argument." +fi + +echo "Current version: $CURRENT_VERSION" +echo "UPGRADE version: $UPGRADE_VERSION" +echo "Action: $ACTION" + +# Terminate existing connections +echo " active connections" +CONN=$(PGPASSWORD=$SU_USER_PWD psql -v ON_ERROR_STOP=1 --username=$SU_USER --host=$DB_SERVERIP --port=$DB_PORT --dbname=$DEFAULT_DB_NAME -t -c "SELECT count(pg_terminate_backend(pg_stat_activity.pid)) FROM pg_stat_activity WHERE datname = '$MOSIP_DB_NAME' AND pid <> pg_backend_pid()";exit;) +echo "Terminated connections" + +# Execute upgrade or rollback +if [ "$ACTION" == "upgrade" ]; then + echo "Upgrading database from $CURRENT_VERSION to $UPGRADE_VERSION" + UPGRADE_SCRIPT_FILE="sql/${CURRENT_VERSION}_to_${UPGRADE_VERSION}_upgrade.sql" + if [ -f "$UPGRADE_SCRIPT_FILE" ]; then + echo "Executing upgrade script $UPGRADE_SCRIPT_FILE" + PGPASSWORD=$SU_USER_PWD psql -v ON_ERROR_STOP=1 --username=$SU_USER --host=$DB_SERVERIP --port=$DB_PORT --dbname=$DEFAULT_DB_NAME -v primary_language_code=$PRIMARY_LANGUAGE_CODE -a -b -f $UPGRADE_SCRIPT_FILE + else + echo "Upgrade script not found, exiting." + exit 1 + fi +elif [ "$ACTION" == "rollback" ]; then + echo "Rolling back database for $CURRENT_VERSION to $UPGRADE_VERSION" + REVOKE_SCRIPT_FILE="sql/${CURRENT_VERSION}_to_${UPGRADE_VERSION}_rollback.sql" + if [ -f "$REVOKE_SCRIPT_FILE" ]; then + echo "Executing rollback script $REVOKE_SCRIPT_FILE" + PGPASSWORD=$SU_USER_PWD psql -v ON_ERROR_STOP=1 --username=$SU_USER --host=$DB_SERVERIP --port=$DB_PORT --dbname=$DEFAULT_DB_NAME -v primary_language_code=$PRIMARY_LANGUAGE_CODE -a -b -f $REVOKE_SCRIPT_FILE + else + echo "rollback script not found, exiting." + exit 1 + fi +else + echo "Unknown action: $ACTION, must be 'upgrade' or 'rollback'." + exit 1 +fi \ No newline at end of file diff --git a/docs/Esignet Collection.postman_collection.json b/docs/Esignet Collection.postman_collection.json index e471463f3..87c50a1c0 100644 --- a/docs/Esignet Collection.postman_collection.json +++ b/docs/Esignet Collection.postman_collection.json @@ -1,9 +1,9 @@ { "info": { - "_postman_id": "2bbed2fa-e3f2-4820-9abd-64ef5e1accee", + "_postman_id": "496855d4-693e-43f6-b0be-dd22c2cd3fa1", "name": "MOSIP e-Signet Collection", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "20579541" + "_exporter_id": "6039892" }, "item": [ { @@ -464,6 +464,71 @@ }, "response": [] }, + { + "name": "Authenticate User V2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var token = pm.cookies.get(\"XSRF-TOKEN\")", + "pm.environment.set(\"csrf_token\", token);" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-XSRF-TOKEN", + "value": "{{csrf_token}}", + "type": "text" + }, + { + "key": "oauth-details-key", + "value": "{{oauth_details_key}}", + "type": "text" + }, + { + "key": "oauth-details-hash", + "value": "{{oauth_details_hash}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"requestTime\": \"{{$isoTimestamp}}\",\n \"request\": {\n \"transactionId\": \"{{transaction_id}}\",\n \"individualId\": \"{{individual_id}}\",\n \"challengeList\" : [\n {\n \"authFactorType\" : \"OTP\",\n \"challenge\" : \"111111\",\n \"format\" : \"alpha-numeric\"\n }\n ]\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/authorization/v2/authenticate", + "host": [ + "{{url}}" + ], + "path": [ + "authorization", + "v2", + "authenticate" + ] + } + }, + "response": [] + }, { "name": "Authorization Code", "event": [ @@ -854,6 +919,86 @@ }, "response": [] }, + { + "name": "Link Authenticate User (wallet) -V2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "eval(pm.globals.get('pmlib_code'))", + "", + "//sign token", + "const private_key_jwk = {", + " \"p\": \"xZDfG1GbHpd7C81k2au8ntvFsTCXj96iyimsHXBzgvKL9WdZfwVVjw5sWCZwSTqt6gL7wCTGxxUeQsN4IKfCalCAmHzFkvDANLi-7LmT2ZJM992j-E7oEOQOv7H4VRqBuir7bECpeeSRoI0qVs07olA5RCKUZpF4iBlecjVVD3M\",", + " \"kty\": \"RSA\",", + " \"q\": \"qGRtmjha2uqCwmGRZS19qWD3ICeLE8NjNgmqx6pI0SLEzD5uQS98T8JOWZxTUjyOm5LtisWBfhloZdT9UNnRX6yMFIswLovTKcYo38yD5fQ9K2gpqzD0K9_ngST08aTDOPY_IlCGe2RZeeHhulyA71MpFhSZLaU2rG2u5exwxBc\",", + " \"d\": \"Of0lDpdIXY3jyhcKuatsw1N6zQpn20UQHxzMdZH2XHSHMzC1vYvbOQD2JmoSahggu2or0n5JeAMbs2k1BlabSqHM65TaJMgKjJVWIiSEAPBhPbqJqGF7nZKrHNk6jVgVYDI_rFGdo7DwNw8-RS0QSiuLBCbmCgCM9wiFWarn2pE9MpK9n_xEmRVNrSFhlvWyPmCCsr5TVVOrXF_7ERYevPDN22uZC0cYgdEtPI80AUZy5ofJnSnIZ0n0fZFO5I7rUFU_MDMuM7W2plKS__fT9UWCBk4mGCgqgqfIzBGFGT2sRDs1kMgVhMMoNFDlc8Hy3KptHmOOOGeb3rwYUixb0Q\",", + " \"e\": \"AQAB\",", + " \"use\": \"sig\",", + " \"qi\": \"bLWOocu5x-RCoxh1MXtJ4Dkahk7vjdQ2iUGiRjmJEL61tKIV1NQk7D5yUl5BFlWanMBwdajRlwcg81ZYV81v2aE2qczOm8X23S8K1QQOSlb2-3p_cGCxaOb9LqV6kDcNetVp2sAxJ-naQLPstE0LQiLGnmij-LwarZFbrpQVVb8\",", + " \"dp\": \"I5De-TYJrMosVoWVQDJQdHCv-CP9ROMZfzddSrdxtAMh-v2t-NXm_yfjjULuN-CeamA7He4A8tn0ZqR1vbs1npn7bHgZwUgh8dvIUyldWm-h94Uo_YyG7nN_zDans_pfnDocpPJHEDB7nuGK27F6qM-0X3WyLXzGrAmVHQ5Iz80\",", + " \"alg\": \"RS256\",", + " \"dq\": \"QJCBFaiV7WoNSMpwHQMcM11iY4nF4YYA_C8os_WuhcF3A3jMfWHfoBU4RVOS_u0JIM1tFtAznk3PoXvQxLc_eBeIfjf_-VjTVikMcwLPb9P9wC7oCyNx-4CWGwY4hI5P6_8-pcGKTrVjOUBKdFITfa-9CSJMuRunouVLMg0-D3E\",", + " \"n\": \"gfSTlsfcU4hCChv8FZ-ffFfNlPesknqVsZubLOlPKB5JHEHEhyy4yRjRmUQK9hzgsSKNyNVrqJIePZnCBKdjs1wxR3uM895hCLfJTQXU1p1hON0VfX0lbG2-y-ZLD6zTkUbn3JNLFc1mxWn4IAiXeKmpXlBqUM3QPCKQWp95lmhfq45UkbdEPkCssdeqYoShkCJWd7GFhC5qOoMLfbNT71q5s3L7VoqKnEpbCsKhvN6BkT4jE1m4xy4jCpc80uZR6bnKG1gryN_LSjeubrodjWpfUrDKCXT23R-RKhMRjCK3sJQbnERJkzpIQ8b_GcOhRzL07BNAoXafRqhrsMhvVQ\"", + "}", + "", + "// Set headers for JWT", + "var certificate = eval(pm.environment.get('bind_certificate'));", + "var public_key_jwk = pmlib.rs.KEYUTIL.getJWK(certificate);", + "// Set headers for JWT", + "var header = {", + " \"x5t#S256\" : public_key_jwk['x5t#S256'],", + "\t\"alg\": \"RS256\"", + "};", + "", + "//const signed_jwt = pmlib.clientAssertPrivateKey(private_key_jwk, pm.environment.get('individual_id'), pm.environment.get('wla_aud'), exp = 60, \"RS256\");", + "", + "const signed_jwt = pmlib.jwtSign(private_key_jwk, {", + " \"aud\" : pm.environment.get('wla_aud'),", + "\t\"sub\": pm.environment.get('individual_id'),", + " \"iss\" : \"postman-inji\"", + "}, header, exp=600, alg = \"RS256\")", + "", + "pm.collectionVariables.set(\"wla_challenge\",signed_jwt);", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-XSRF-TOKEN", + "value": "{{csrf_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"requestTime\": \"{{$isoTimestamp}}\",\n \"request\": {\n \"linkedTransactionId\": \"{{linkTransactionId}}\",\n \"individualId\": \"{{individual_id}}\",\n \"challengeList\" : [\n {\n \"authFactorType\" : \"WLA\",\n \"challenge\" : \"{{wla_challenge}}\",\n \"format\" : \"jwt\"\n }\n ]\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/linked-authorization/v2/authenticate", + "host": [ + "{{url}}" + ], + "path": [ + "linked-authorization", + "v2", + "authenticate" + ] + } + }, + "response": [] + }, { "name": "Link Consent Request (wallet)", "event": [ @@ -905,6 +1050,103 @@ }, "response": [] }, + { + "name": "Link Consent Request (wallet) - V2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Validate code\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.response.code).not.equals(null);", + " pm.collectionVariables.set(\"code\", jsonData.response.code);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "eval(pm.globals.get('pmlib_code'));", + "", + "const private_key_jwk = {", + " \"p\": \"xZDfG1GbHpd7C81k2au8ntvFsTCXj96iyimsHXBzgvKL9WdZfwVVjw5sWCZwSTqt6gL7wCTGxxUeQsN4IKfCalCAmHzFkvDANLi-7LmT2ZJM992j-E7oEOQOv7H4VRqBuir7bECpeeSRoI0qVs07olA5RCKUZpF4iBlecjVVD3M\",", + " \"kty\": \"RSA\",", + " \"q\": \"qGRtmjha2uqCwmGRZS19qWD3ICeLE8NjNgmqx6pI0SLEzD5uQS98T8JOWZxTUjyOm5LtisWBfhloZdT9UNnRX6yMFIswLovTKcYo38yD5fQ9K2gpqzD0K9_ngST08aTDOPY_IlCGe2RZeeHhulyA71MpFhSZLaU2rG2u5exwxBc\",", + " \"d\": \"Of0lDpdIXY3jyhcKuatsw1N6zQpn20UQHxzMdZH2XHSHMzC1vYvbOQD2JmoSahggu2or0n5JeAMbs2k1BlabSqHM65TaJMgKjJVWIiSEAPBhPbqJqGF7nZKrHNk6jVgVYDI_rFGdo7DwNw8-RS0QSiuLBCbmCgCM9wiFWarn2pE9MpK9n_xEmRVNrSFhlvWyPmCCsr5TVVOrXF_7ERYevPDN22uZC0cYgdEtPI80AUZy5ofJnSnIZ0n0fZFO5I7rUFU_MDMuM7W2plKS__fT9UWCBk4mGCgqgqfIzBGFGT2sRDs1kMgVhMMoNFDlc8Hy3KptHmOOOGeb3rwYUixb0Q\",", + " \"e\": \"AQAB\",", + " \"use\": \"sig\",", + " \"qi\": \"bLWOocu5x-RCoxh1MXtJ4Dkahk7vjdQ2iUGiRjmJEL61tKIV1NQk7D5yUl5BFlWanMBwdajRlwcg81ZYV81v2aE2qczOm8X23S8K1QQOSlb2-3p_cGCxaOb9LqV6kDcNetVp2sAxJ-naQLPstE0LQiLGnmij-LwarZFbrpQVVb8\",", + " \"dp\": \"I5De-TYJrMosVoWVQDJQdHCv-CP9ROMZfzddSrdxtAMh-v2t-NXm_yfjjULuN-CeamA7He4A8tn0ZqR1vbs1npn7bHgZwUgh8dvIUyldWm-h94Uo_YyG7nN_zDans_pfnDocpPJHEDB7nuGK27F6qM-0X3WyLXzGrAmVHQ5Iz80\",", + " \"alg\": \"RS256\",", + " \"dq\": \"QJCBFaiV7WoNSMpwHQMcM11iY4nF4YYA_C8os_WuhcF3A3jMfWHfoBU4RVOS_u0JIM1tFtAznk3PoXvQxLc_eBeIfjf_-VjTVikMcwLPb9P9wC7oCyNx-4CWGwY4hI5P6_8-pcGKTrVjOUBKdFITfa-9CSJMuRunouVLMg0-D3E\",", + " \"n\": \"gfSTlsfcU4hCChv8FZ-ffFfNlPesknqVsZubLOlPKB5JHEHEhyy4yRjRmUQK9hzgsSKNyNVrqJIePZnCBKdjs1wxR3uM895hCLfJTQXU1p1hON0VfX0lbG2-y-ZLD6zTkUbn3JNLFc1mxWn4IAiXeKmpXlBqUM3QPCKQWp95lmhfq45UkbdEPkCssdeqYoShkCJWd7GFhC5qOoMLfbNT71q5s3L7VoqKnEpbCsKhvN6BkT4jE1m4xy4jCpc80uZR6bnKG1gryN_LSjeubrodjWpfUrDKCXT23R-RKhMRjCK3sJQbnERJkzpIQ8b_GcOhRzL07BNAoXafRqhrsMhvVQ\"", + "};", + "var certificate = eval(pm.environment.get('bind_certificate'));", + "var public_key_jwk = pmlib.rs.KEYUTIL.getJWK(certificate);", + "// Set headers for JWT", + "var header = {", + " \"x5t#S256\" : public_key_jwk['x5t#S256'],", + "\t\"alg\": \"RS256\"", + "};", + "var payload = {}", + "var acceptedClaims = pm.environment.get('acceptedClaims');", + "if(acceptedClaims != undefined && Array.isArray(acceptedClaims)){", + " payload[\"acceptedClaims\"] = acceptedClaims.sort()", + "}", + "var permittedAuthorizedScopes = eval(pm.environment.get('permittedAuthorizedScopes'));", + "if(permittedAuthorizedScopes != undefined && Array.isArray(permittedAuthorizedScopes)){", + " payload[\"permittedAuthorizedScopes\"] = permittedAuthorizedScopes.sort()", + "}", + "const signedPayload = pmlib.rs.jws.JWS.sign(", + " null, header, payload, private_key_jwk", + ");", + "var parts = signedPayload.split(\".\");", + "var detachedSignature = parts[0] + \".\" + parts[2];", + "pm.environment.set(\"detachedSignature\", detachedSignature);" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "method": "POST", + "header": [ + { + "key": "X-XSRF-TOKEN", + "value": "{{csrf_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"requestTime\": \"{{$isoTimestamp}}\",\n \"request\": {\n \"linkedTransactionId\": \"{{linkTransactionId}}\",\n \"acceptedClaims\": {{acceptedClaims}},\n \"signature\": \"{{detachedSignature}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/linked-authorization/v2/consent", + "host": [ + "{{url}}" + ], + "path": [ + "linked-authorization", + "v2", + "consent" + ] + } + }, + "response": [] + }, { "name": "Link auth code request", "event": [ diff --git a/docs/Esignet.postman_environment.json b/docs/Esignet.postman_environment.json index fda24995f..8e016b910 100644 --- a/docs/Esignet.postman_environment.json +++ b/docs/Esignet.postman_environment.json @@ -9,7 +9,7 @@ }, { "key": "relayingPartyId", - "value": "mpartner-default-idp-relyparty-new", + "value": "mpartner-default-esignet", "enabled": true }, { @@ -29,7 +29,7 @@ }, { "key": "claims", - "value": "{\n \"userinfo\": {\n \"given_name\": {\n \"essential\": true\n },\n \"phone_number\": null,\n ...", + "value": "{\"userinfo\":{\"given_name\":{\"essential\":true},\"phone_number\":{\"essential\":false},\"email\":{\"essential\":true},\"picture\":{\"essential\":false},\"gender\":{\"essential\":false},\"birthdate\":{\"essential\":false},\"address\":{\"essential\":false}},\"id_token\":{}}", "enabled": true }, { @@ -44,17 +44,12 @@ }, { "key": "url", - "value": "https://api.dev.mosip.net/v1/idp", + "value": "https://esignet.dev.mosip.net/v1/esignet", "enabled": true }, { "key": "aud", - "value": "https://api.dev.mosip.net/v1/idp/oauth/token", - "enabled": true - }, - { - "key": "csrf_token", - "value": "", + "value": "https://esignet.dev.mosip.net/v1/esignet/oauth/token", "enabled": true }, { diff --git a/docs/esignet-openapi-1.1.0.yaml b/docs/esignet-openapi-1.1.0.yaml new file mode 100644 index 000000000..8b4f6039d --- /dev/null +++ b/docs/esignet-openapi-1.1.0.yaml @@ -0,0 +1,2777 @@ +openapi: 3.1.0 +x-stoplight: + id: 6f1syzijynu40 +info: + title: e-Signet + version: '1.0' + contact: + name: MOSIP Team + email: info@mosip.io + url: 'https://www.mosip.io/' + description: |- + This API document details on the below categories of endpoints +
    +
  • Management - Endpoints for creation and updation of OIDC client details
  • +
  • OIDC - All OIDC compliant endpoints for performing the Open ID Connect flows
  • +
  • UI - All endpoints used by the UI application
  • +
  • Wallet - All endpoints used by the Wallet application
  • +
  • binding-service - All endpoints used by the UI application
  • +
+ + Abbreviations:

+ OIDC - Open ID Connect
+ IdP - Identity provider
+ PMP - Partner Management portal
+ KYC - Know Your Customer
+ IDA - Authentication server
+ UIN - Unique Identification Number
+ VID - Virtual Identifier
+ PSUT - Partner(Relying Party) Specific User Token
+ + + + + license: + name: MPL-2.0 + url: 'https://www.mozilla.org/en-US/MPL/2.0/' + summary: Open ID Connect based identity provider for large scale authentication +servers: + - url: 'https://api.esignet.io/v1/esignet' +paths: + /client-mgmt/oidc-client: + post: + summary: Create OIDC Client Endpoint + operationId: post-client + requestBody: + content: + application/json: + schema: + type: object + description: OIDC client details + properties: + requestTime: + type: string + format: date-time + pattern: '' + description: Current date and time when the request is sent + request: + type: object + required: + - clientId + - clientName + - relyingPartyId + - logoUri + - authContextRefs + - publicKey + - userClaims + - grantTypes + - clientAuthMethods + properties: + clientId: + type: string + description: 'Unique OIDC client id (Case-Sensitive). If duplicates found, request will be rejected.' + example: 785b806d0e594657b05aabdb30fff8a4 + maxLength: 50 + minLength: 1 + clientName: + type: string + minLength: 1 + maxLength: 256 + description: Name of OIDC client. + example: ABC Health Care + relyingPartyId: + type: string + description: |- + Relying Party ID of the client. This will be passed on to authentications servers when KYC is fetched. + + Note: Use the client Id as relyingPartyId if there is no separate concept of relying party in the ID authentication system. + example: bharathi-inc + minLength: 1 + maxLength: 50 + logoUri: + type: string + description: Relying party logo URI which will used to display logo in OIDC login and consent pages. + format: uri + minLength: 1 + maxLength: 1024 + redirectUris: + type: array + description: |- + Valid list of callback Uris of the relying party. + When OIDC authorize API is called, any one Uri from this list should be sent as redirect_uri. authorization_code will be redirected to this Uri on successful authentication. + items: + type: string + authContextRefs: + type: array + description: The Authentication Context Class Reference is case-sensitive string specifying a list of Authentication Context Class values that identifies the Authentication Context Class. Values that the authentication performed satisfied implying a Level Of Assurance. + minItems: 1 + items: + type: string + enum: + - 'mosip:idp:acr:static-code' + - 'mosip:idp:acr:generated-code' + - 'mosip:idp:acr:linked-wallet' + - 'mosip:idp:acr:biometrics' + publicKey: + type: object + description: |- + OIDC client's public key used to verify the client's private_key_jwt when OIDC token endpoint is invoked. + This field will not be allowed to udpate later, if the private key is compromised, then new OIDC client to be created. + Format : Json Web Key (JWK). + userClaims: + type: array + description: 'Allowed user info claims, that can be requested by OIDC client in the authorize API' + minItems: 1 + items: + type: string + enum: + - name + - given_name + - middle_name + - preferred_username + - nickname + - gender + - birthdate + - email + - phone_number + - picture + - address + grantTypes: + type: array + description: Form of Authorization Grant presented to token endpoint + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - authorization_code + clientAuthMethods: + type: array + description: Auth method supported for token endpoint. At present only "private_key_jwt" is supported. + minItems: 1 + items: + type: string + enum: + - private_key_jwt + required: + - requestTime + - request + examples: + example-1: + value: + requestTime: '2011-10-05T14:48:00.000Z' + request: + clientId: e-health-service + clientName: Health Service + relyingPartyId: bharath-gov + logoUri: 'http://example.com' + publicKey: {} + authContextRefs: + - 'mosip:idp:acr:static-code' + userClaims: + - name + grantTypes: + - authorization_code + clientAuthMethods: + - private_key_jwt + description: '' + description: |- + API to add new open ID connect (OIDC) clients, it can be invoked by other modules which manages the relying parties / partners. + + Each relying party can associate to one or multiple OIDC client ids. + + On create, OIDC client status will be by default set to "**active**". + x-internal: false + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + description: ate and time when the response is generated + response: + type: object + properties: + clientId: + type: string + description: Client id as provided in the request. + errors: + type: array + items: + type: object + additionalProperties: false + properties: + errorCode: + type: string + enum: + - duplicate_client_id + - invalid_public_key + - invalid_input + - invalid_client_id + - invalid_client_name + - invalid_rp_id + - invalid_claim + - invalid_acr + - invalid_uri + - invalid_redirect_uri + - invalid_grant_type + - invalid_client_auth + errorMessage: + type: string + '401': + description: Unauthorized + tags: + - Management + parameters: [] + security: + - Authorization-add_oidc_client: [] + parameters: [] + '/client-mgmt/oidc-client/{client_id}': + parameters: + - schema: + type: string + example: 785b806d0e594657b05aabdb30fff8a4 + name: client_id + in: path + required: true + description: Client Identifier + put: + summary: Update OIDC Client Endpoint + operationId: put-oidc-client-client_id + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + description: Date and time when the response is generated + response: + type: object + properties: + clientId: + type: string + description: OIDC client identifier. + required: + - clientId + errors: + type: array + description: 'List of Errors in case of request validation / processing failure in Idp server. When request processing is fully successful, this array will be empty.' + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_client_id + - invalid_client_name + - invalid_claim + - invalid_acr + - invalid_uri + - invalid_redirect_uri + - invalid_grant_type + - invalid_client_auth + errorMessage: + type: string + description: |- + API to update existing Open ID Connect (OIDC) client, it can be invoked by other modules which manages the relying parties / partners when there any updates on the fields accepted in this API. + + **Authentication and authorization** is based on a valid JWT issued by a trusted IAM system including "**update_oidc_client**" scope. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: '' + description: Current date and time when the request is sent + request: + type: object + required: + - clientName + - status + - logoUri + - redirectUris + - userClaims + - authContextRefs + - grantTypes + - clientAuthMethods + properties: + clientName: + type: string + description: Name of the OIDC client. + minLength: 1 + maxLength: 256 + example: ABC Health Care + status: + type: string + enum: + - active + - inactive + description: Status of OIDC client. + logoUri: + type: string + description: Relying party logo URI which will used to display logo in OIDC login and consent pages. + format: uri + minLength: 1 + maxLength: 1024 + redirectUris: + type: array + description: 'Valid list of callback Uris of the relying party. When OIDC authorize API is called, any one Uri from this list should be sent as redirect_uri. authorization_code will be redirected to this Uri on successful authentication.' + minItems: 1 + uniqueItems: true + items: + type: string + format: uri + userClaims: + type: array + description: 'Allowed user info claims, that can be requested by OIDC client in the authorize API' + minItems: 1 + items: + type: string + enum: + - name + - given_name + - middle_name + - preferred_username + - nickname + - gender + - birthdate + - email + - phone_number + - picture + - address + authContextRefs: + type: array + description: The Authentication Context Class Reference is case-sensitive string specifying a list of Authentication Context Class values that identifies the Authentication Context Class. Values that the authentication performed satisfied implying a Level Of Assurance. + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - 'mosip:idp:acr:static-code' + - 'mosip:idp:acr:generated-code' + - 'mosip:idp:acr:linked-wallet' + - 'mosip:idp:acr:biometrics' + grantTypes: + type: array + description: Form of Authorization Grant presented to token endpoint. + minItems: 1 + items: + type: string + enum: + - authorization_code + clientAuthMethods: + type: array + description: Auth method supported for token endpoint. At present only "private_key_jwt" is supported. + minItems: 1 + items: + type: string + enum: + - private_key_jwt + required: + - requestTime + - request + examples: + example-1: + value: + requestTime: '2022-09-22T08:03:45.000Z' + request: + clientName: Health Service + status: active + logoUri: 'http://example.com' + redirectUris: + - 'http://example.com' + userClaims: + - name + authContextRefs: + - 'mosip:idp:acr:static-code' + grantTypes: + - authorization_code + clientAuthMethods: + - private_key_jwt + description: '' + tags: + - Management + security: + - Authorization-update_oidc_client: [] + /authorize: + get: + summary: Authorization Endpoint + operationId: get-authorize + description: |- + This is the authorize endpoint of Open ID Connect (OIDC). The relying party applications will do a browser redirect to this endpoint with all required details passed as query parameters. + + This endpoint will respond with a basic HTML page to load a JS application in the browser. UI JS application will then echo all the query parameters received in this endpoint to the "/authorization/oauth-details" endpoint as the request body. + + All the validations on the query parameter values will be performed in the "/authorization/oauth-details" endpoint. + + **Authentication & Authroization**: None + parameters: + - schema: + type: string + default: openid profile + enum: + - openid profile + - openid + - profile + - email + - address + - phone + - offline_access + in: query + name: scope + description: Specifies what access privileges are being requested for Access Tokens. The scopes associated with Access Tokens determine what resources will be available when they are used to access OAuth 2.0 protected endpoints. OpenID Connect requests MUST contain the OpenID scope value. + required: true + - schema: + type: string + enum: + - code + in: query + name: response_type + description: 'The value set here determines the authorization processing flow. To use the Authorization Code Flow, the value should be configured to "code".' + required: true + - schema: + type: string + maxLength: 256 + in: query + name: client_id + required: true + description: Valid OAuth 2.0 Client Identifier in the Authorization Server. + - schema: + type: string + format: uri + in: query + name: redirect_uri + description: Redirection URI to which the response would be sent. This URI must match one of the redirection URI values during the client ID creation. + required: true + - schema: + type: string + maxLength: 256 + in: query + description: 'Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie.' + name: state + - schema: + type: string + in: query + description: 'String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token.' + name: nonce + - schema: + type: string + enum: + - page + - popup + - touch + - wap + in: query + name: display + description: ASCII string value that specifies how the Authorization Server displays the authentication and consent user interface pages to the end user. + - schema: + type: string + enum: + - none + - login + - consent + - select_account + example: consent + in: query + name: prompt + description: Space delimited case-sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. + - schema: + type: number + in: query + name: max_age + description: 'Maximum Authentication Age. This specifies the allowable elapsed time in seconds since the last time the end user was actively authenticated by the OP. If the elapsed time is greater than this value, then the OP MUST attempt to actively re-authenticate the end user. The max_age request parameter corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age request parameter. When max_age is used, the ID Token returned MUST include an auth_time claim value.' + - schema: + type: string + in: query + name: ui_locales + description: 'End user''s preferred languages and scripts for the user interface, represented as a space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. For instance, the value "fr-CA fr en" represents a preference for French as spoken in Canada, then French (without a region designation), followed by English (without a region designation). An error SHOULD NOT result if some or all of the requested locales are not supported by the OpenID Provider.' + - schema: + type: string + in: query + name: acr_values + description: 'Requested Authentication Context Class Reference values. Space-separated string that specifies the acr values that the Authorization Server is being requested to use for processing this Authentication Request, with the values appearing in order of preference. The Authentication Context Class satisfied by the authentication performed is returned as the acr Claim Value, as specified in Section 2. The acr Claim is requested as a Voluntary Claim by this parameter.' + - schema: + type: string + in: query + name: claims_locales + description: 'End-User''s preferred languages and scripts for Claims being returned, represented as a space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. An error SHOULD NOT result if some or all of the requested locales are not supported by the OpenID Provider.' + - schema: + type: string + in: query + name: claims + description: This parameter is used to request specific claims to be returned. The value is a JSON object listing the requested claims. The claims parameter value is represented in an OAuth 2.0 request as UTF-8 encoded JSON. + responses: + '200': + description: |- + OK + + Loads JS application, and validates the provided query parameters using oauth-details endpoint. + tags: + - OIDC + parameters: [] + /authorization/oauth-details: + post: + summary: OAuth Details Endpoint + operationId: post-oauth-details + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + transactionId: + type: string + description: This value is passed through unmodified from the /oauth-details request to the /auth-code request. + authFactors: + description: |- + Auth factors defines the authentication screens displayed in IDP frontend. + More than one authFactor may be resolved or combination of auth factors. + Precedence of authFactors is based on its order + type: array + items: + type: array + items: + $ref: '#/components/schemas/AuthFactor' + essentialClaims: + type: array + description: Array holds all the requested essential claims. + items: + type: string + voluntaryClaims: + type: array + description: Array holds all the requested optional claims. + items: + type: string + authorizeScopes: + type: array + description: Scopes to be permitted by the end user. + items: + type: string + configs: + type: object + description: UI configuration key-value pairs. + clientName: + type: string + description: OIDC client name as registered. + logoUrl: + type: string + description: Registered OIDC client logo URL. + required: + - transactionId + - authFactors + - essentialClaims + errors: + type: + - array + - 'null' + description: List of errors in case of request validation / processing failure in Idp server. + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_client_id + - invalid_redirect_uri + - invalid_scope + - no_acr_registered + - invalid_response_type + - invalid_display + - invalid_prompt + errorMessage: + type: string + examples: + example-1: + value: + responseTime: '2022-09-22T08:03:45.287Z' + response: + transactionId: vKb8cVbq9PX_yt46_hX0xlBJNExl9cnYtL8kGRxU5OM + clientName: Health service OIDC Client + logoUrl: 'https://health-services.com/logo.png' + authFactors: + - - type: PIN + count: 0 + subTypes: null + authorizeScopes: [] + essentialClaims: + - given_name + - email + voluntaryClaims: + - birthdate + - gender + - phone + configs: + sbi.env: Staging + sbi.threshold.face: 40 + sbi.threshold.finger: 40 + sbi.threshold.iris: 40 + errors: null + description: | + OAuth details request is raised from the UI JS application on page load. + + OAuth details endpoint validates the provided request parameters and resolves the required authentication factors. Combination of resolved authentication factors and the consent details are sent back as response with a unique transactionId. + + The transcationId in the response is used to identify/maintain the end user pre-auth session. This pre-auth session has timeout (configurable in Idp service). + + All the query params passed to /authorize API MUST be sent to /oauth-details endpoint. All these parameters will be validated in IdP before returning success response. + + 1. Validates the clientId. + 2. validates redirectUri is one of the redirectUri during client create/update. + 3. validates display,responseType,prompts values are part of supported values in Idp properties. + 4. scope / acrValues / claims / locales / claim_locales - unknown values are ignored. Only valid values are considered. + 5. scopes like profile, email and phone are allowed only if "openid" is also part of the requested scope. + 6. Claims request parameter is allowed, only if 'openid' is part of the scope request parameter + 7. claims considered only if part of registered claims. + 8. ACR in claims request parameter is given the first priority over acr_values query parameter. if none of them are part of the registered acrs, registered ACRs are only considered to derive the auth factors. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: '' + request: + type: object + required: + - scope + - responseType + - clientId + - redirectUri + properties: + scope: + type: string + description: Specifies what access privileges are being requested for Access Tokens. The scopes associated with Access Tokens determine what resources will be available when they are used to access OAuth 2.0 protected endpoints. OpenID Connect requests MUST contain the OpenID scope value. + responseType: + type: string + description: 'Value that determines the authorization processing flow to be used. When using the Authorization Code Flow, this value is code.' + clientId: + type: string + description: OAuth 2.0 Client Identifier valid at the Authorization Server + redirectUri: + type: string + description: Redirection URI to which the response will be sent. This URI MUST exactly match one of the Redirection URI values for the Client pre-registered + state: + type: string + description: client state value echoed. + nonce: + type: string + description: Client's nonce value echoed. + display: + type: string + description: ASCII string value that specifies how the Authorization Server displays the authentication and consent user interface pages to the End-User. + prompt: + type: string + description: 'Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent.' + acrValues: + type: string + description: |- + Space separated ACR values, Unknown ACR are ignored. Only registered ACR values will be considered. + if none of the provided acr value is among the registered values, Error response is returned with error code "invalid_acr". + claims: + $ref: '#/components/schemas/Claim' + maxAge: + type: number + description: 'Maximum Authentication Age. Specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated by the OP. If the elapsed time is greater than this value, the OP MUST attempt to actively re-authenticate the End-User. (The max_age request parameter corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age request parameter.) When max_age is used, the ID Token returned MUST include an auth_time Claim Value.' + claimsLocales: + type: string + description: 'End-User''s preferred languages and scripts for Claims being returned, represented as a space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. An error SHOULD NOT result if some or all of the requested locales are not supported by the OpenID Provider.' + uiLocales: + type: string + description: 'End-User''s preferred languages and scripts for the user interface, represented as a space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. For instance, the value "fr-CA fr en" represents a preference for French as spoken in Canada, then French (without a region designation), followed by English (without a region designation). An error SHOULD NOT result if some or all of the requested locales are not supported by the OpenID Provider.' + required: + - requestTime + - request + examples: + example-1: + value: + requestTime: '2022-09-22T08:01:10.000Z' + request: + clientId: healthservicev1 + scope: openid resident-service profile + responseType: code + redirectUri: 'http://health-services.com/userprofile' + display: popup + prompt: login + acrValues: 'mosip:idp:acr:static-code mosip:idp:acr:generated-code' + claims: + userinfo: + given_name: + essential: true + phone: null + email: + essential: true + picture: + essential: false + gender: + essential: false + id_token: {} + nonce: 973eieljzng + state: eree2311 + claimsLocales: en + parameters: + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + tags: + - UI + parameters: [] + /authorization/send-otp: + post: + summary: Send OTP Endpoint + operationId: post-send-otp + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + description: 'Successful message, or null if failed to deliver OTP.' + properties: + transactionId: + type: string + description: oauth-details transactionId is used until the /token call. + maskedEmail: + type: string + description: Masked email id to which generated OTP was mailed. + maskedMobile: + type: string + description: Masked phone number to which generated OTP was messaged. + errors: + type: array + description: 'List of Errors in case of request validation / processing failure in Idp server. if failure from IDA, the same error is relayed in this response.' + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction + - invalid_transaction_id + - invalid_identifier + - invalid_otp_channel + - invalid_captcha + - send_otp_failed + - unknown_error + errorMessage: + type: string + description: |- + When end user want to authenticate using OTP auth factor, he/she will enter their individual id (UIN/VID) and click on the "Generate OTP" button on the UI application. Then this endpoint will be invoked by the JS UI application. + + Since the OTP generation and delivery to end user is to be handled by the integrated authentication system, the request will be relayed to the same. + + 1. Validates the transactionId. + 2. Validates null / empty individualId. + 3. Validates captchaToken, if enabled. + 3. Delegates the call to integrated authentication system. + 4. Relays error from authentication system to UI on failure. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - transactionId + - individualId + - otpChannels + properties: + transactionId: + type: string + description: oauth-details transactionId is used until the /token call. + individualId: + type: string + description: Actual UIN or VID value of the authenticating the end user. + otpChannels: + type: array + enum: + - email + - sms + description: Channel to be used to deliver request OTP. + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - sms + - email + captchaToken: + type: string + description: 'Captcha token, if enabled.' + required: + - requestTime + - request + description: '' + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction Id + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + tags: + - UI + parameters: [] + /linked-authorization/send-otp: + post: + summary: Link authorization Send OTP Endpoint + operationId: post-send-linked-otp + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + description: 'Successful message, or null if failed to deliver OTP.' + properties: + transactionId: + type: string + description: oauth-details transactionId is used until the /token call. + maskedEmail: + type: string + description: Masked email id to which generated OTP was mailed. + maskedMobile: + type: string + description: Masked phone number to which generated OTP was messaged. + errors: + type: array + description: 'List of Errors in case of request validation / processing failure in Idp server. if failure from IDA, the same error is relayed in this response.' + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction + - invalid_transaction_id + - invalid_identifier + - invalid_otp_channel + - send_otp_failed + - unknown_error + errorMessage: + type: string + description: |- + When end user want to authenticate using OTP auth factor, he/she will enter their individual id (UIN/VID) and click on the "Generate OTP" button on the UI application. Then this endpoint will be invoked by wallet app with linked transactionId. + + Since the OTP generation and delivery to end user is to be handled by the integrated authentication system, the request will be relayed to the same. + + 1. Validates the linked transactionId. + 2. Validates null / empty individualId. + 3. Delegates the call to integrated authentication system. + 4. Relays error from authentication system to UI on failure. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - transactionId + - individualId + - otpChannels + properties: + transactionId: + type: string + description: oauth-details transactionId is used until the /token call. + individualId: + type: string + description: Actual UIN or VID value of the authenticating the end user. + otpChannels: + type: array + enum: + - email + - sms + description: Channel to be used to deliver request OTP. + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - sms + - email + required: + - requestTime + - request + description: '' + parameters: [] + tags: + - UI + parameters: [] + /authorization/authenticate: + post: + summary: Authentication Endpoint + operationId: post-authenticate + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + errors: + type: array + description: List of Errors in case of request validation / processing failure in Idp server. + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction + - invalid_identifier + - invalid_no_of_challenges + - auth_failed + - unknown_error + errorMessage: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - transactionId + - individualId + - challengeList + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + individualId: + type: string + description: ' User identifier (UIN/VID).' + challengeList: + type: array + description: Authentication Challenge. + items: + $ref: '#/components/schemas/AuthChallenge' + required: + - requestTime + - request + description: '' + description: |- + Once end user provides the user identifier (UIN/VID) and all the required auth challenge to the UI application, this endpoint will be invoked. + + Supported auth-challenge depends on the integrated authentication server. + + 1. Validates transactionId/linkTransactionId. + 2. Validates null / empty individualId. + 3. Invokes kyc-auth call to integrated authentication server (IDA). + 4. Relays error from integrated authentication server to UI on failure. + + On Authentication Success: Only transaction Id is returned in the below response without any errors. + + On Authentication Failure: Error list will be set with the errors returned from the integrated authentication server. + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction ID + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + tags: + - UI + - WALLET + parameters: [] + /authorization/v2/authenticate: + post: + summary: Authentication Endpoint V2 + operationId: post-authenticate-v2 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + consentAction: + type: string + x-stoplight: + id: gq30p6w6eprol + enum: + - CAPTURE + - NOCAPTURE + description: | + This field indicates the need to capture user consent or not + errors: + type: array + description: List of Errors in case of request validation / processing failure in Idp server. + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction + - invalid_identifier + - invalid_no_of_challenges + - auth_failed + - unknown_error + errorMessage: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - transactionId + - individualId + - challengeList + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + individualId: + type: string + description: ' User identifier (UIN/VID).' + challengeList: + type: array + description: Authentication Challenge. + items: + $ref: '#/components/schemas/AuthChallenge' + required: + - requestTime + - request + description: '' + description: |- + Once end user provides the user identifier (UIN/VID) and all the required auth challenge to the UI application, this endpoint will be invoked. + + Supported auth-challenge depends on the integrated authentication server. + + 1. Validates transactionId/linkTransactionId. + 2. Validates null / empty individualId. + 3. Invokes kyc-auth call to integrated authentication server (IDA). + 4. It validates stored userconsent against the requested claims and scopes + 5. Relays error from integrated authentication server to UI on failure. + + On Authentication Success: transaction Id and consentAction is returned in the below response without any errors. + + On Authentication Failure: Error list will be set with the errors returned from the integrated authentication server. + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction ID + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + tags: + - UI + - WALLET + parameters: [] + /authorization/auth-code: + post: + summary: Authorization Code Endpoint + operationId: post-auth-code + description: |- + Once the authentication is successful and user consent is obtained, this endpoint will be invoked by the UI application to send the accepted consent and permitted scopes. + + Then UI application will receive the authorization code and few other details required for redirecting to the client / relying party application. + + 1. Validates transactionId. If valid, stores the accepted claims and permitted scopes in the cache and returns back the authorization code. + 2. Validate accepted claims and permitted scopes in the request. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - transactionId + properties: + transactionId: + type: string + description: Transaction id echoed starting from /authorize call. + permittedAuthorizeScopes: + type: array + description: List of permitted scopes by end-user. + items: + type: string + acceptedClaims: + type: array + description: List of accepted essential and voluntary claims by end-user. + items: + type: string + required: + - requestTime + - request + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction Id + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + code: + type: string + description: Authorization code. Required to obtain the ID token and / or access token from the /token endpoint. + redirectUri: + type: string + description: Client's validated redirect URI. + nonce: + type: string + description: 'The echoed nonce value, if one was passed with the request. Clients must validate the value before proceeding.' + state: + type: string + description: 'The echoed state value, used to maintain state between the request and the callback.' + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_transaction + - invalid_accepted_claim + - invalid_permitted_scope + errorMessage: + type: string + tags: + - UI + - WALLET + parameters: [] + /linked-authorization/link-code: + post: + summary: Generate Link Code endpoint + tags: + - UI + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + transactionId: + type: string + description: TransactionId same the one passed in the request. + linkCode: + type: string + description: Unique random string mapped to this transactionId. + expireDateTime: + type: string + description: Expire date time (ISO format) for the generated linkCode. After this date time linkCode in this request is not valid. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - link_code_gen_failed + - invalid_transaction + errorMessage: + type: string + operationId: get-authorization-generate-link-code + description: |- + Generate link code request is raised from JS application. + + 1. JS application creates a deeplink with this link-code as parameter. + 2. This deeplink is embedded in a Machine-readable-code and the same is rendered in the UI. + 3. End user scans this machine-readable-code to open wallet app. + 4. On open of wallet-app, wallet-app invokes /link-transaction endpoint. + 5. In the JS application, once machine-readable-code is rendered, at the same time /link-status endpoint is invoked as a polling request. + + **Configuration to decide the expire date time of linkCode**: mosip.idp.link-code-expire-in-secs + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - transactionId + properties: + transactionId: + type: string + description: TransactionId returned in the oauth-details response. + required: + - requestTime + - request + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction Id + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + parameters: [] + /linked-authorization/link-transaction: + post: + summary: Link Transaction endpoint + operationId: post-authorization-link-transaction + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + linkTransactionId: + type: string + description: Unique link-transaction-id. + clientName: + type: string + description: Registered name of the OIDC client. + logoUrl: + type: string + description: Registered OIDC client Logo URL. + authorizeScopes: + type: array + description: List of requested scopes to be permitted by the end user. + items: + type: string + essentialClaims: + type: array + description: List of client request mandatory claim names. + items: + type: string + voluntaryClaims: + type: array + description: List of client request optional claim names. + items: + type: string + authFactors: + type: array + description: Auth factors defines the authentication screens displayed in IDP frontend. More than one authFactor may be resolved or combination of auth factors. Precedence of authFactors is based on its order + items: + type: array + items: + $ref: '#/components/schemas/AuthFactor' + configs: + type: object + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_link_code + - invalid_transaction + - invalid_client_id + - unknown_error + errorMessage: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - linkCode + properties: + linkCode: + type: string + description: Link code as received by the wallet-app from the QR code scanning. + required: + - requestTime + - request + description: |- + The link transaction endpoint is invoked from Wallet-app. + + 1. Validates the link-code and its expiry and generates the linkTransactionId. This linkTransactionId is linked to transactionId returned from /oauth-details endpoint. + + 2. Returns the auth-factors, clientName, logoUrl, User claims, authorize scopes along with linkTransactionId. + + **Note:** + Wallet-app will hereafter address the transaction with this linkTransactionId for the /authenticate and /consent endpoints. + tags: + - WALLET + parameters: [] + /linked-authorization/link-status: + post: + summary: Link status endpoint + tags: + - UI + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTIme: + type: string + response: + type: object + properties: + transactionId: + type: string + description: This is the same transactionId as sent in the request. + linkStatus: + type: string + description: Link status of the linkCode passed in the request. + enum: + - LINKED + linkedDateTime: + type: string + description: Epoch in milliseconds at which the wallet-app acknowledged the link-code. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_link_code + - response_timeout + - unknown_error + errorMessage: + type: string + operationId: post-authorization-link-status + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - transactionId + - linkCode + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + linkCode: + type: string + description: This is same linkCode returned in the generate-link-code response. + required: + - requestTime + - request + description: |- + The link transaction endpoint is invoked from Wallet-app. + + 1. Validates the link-code and its expiry and generates the linkTransactionId. This linkTransactionId is linked to transactionId returned from /oauth-details endpoint. + + 2. Returns the auth-factors, clientName, logoUrl, User claims, authorize scopes along with linkTransactionId. + + **Note:** + Wallet-app will hereafter address the transaction with this linkTransactionId for the /authenticate and /consent endpoints. + parameters: + - schema: + type: string + in: header + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + name: oauth-details-hash + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction Id + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + parameters: [] + /linked-authorization/authenticate: + post: + summary: Linked Authentication Endpoint + operationId: post-linked-authenticate + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + errors: + type: array + description: List of Errors in case of request validation / processing failure in Idp server. + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_transaction + - invalid_identifier + - invalid_no_of_challenges + - auth_failed + - unknown_error + errorMessage: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - linkedTransactionId + - individualId + - challengeList + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the link-transaction response. + individualId: + type: string + description: User identifier (UIN/VID). + challengeList: + type: array + description: Authentication Challenge. + items: + $ref: '#/components/schemas/AuthChallenge' + required: + - requestTime + - request + description: '' + description: |- + Once end user provides the user identifier (UIN/VID) and all the required auth challenge to the Wallet-app, this endpoint will be invoked from wallet-app. + + Supported auth-challenge depends on the integrated authentication server. + + 1. Validates linkedTransactionId. + 2. Validates null / empty individualId. + 4. Invokes kyc-auth call to integrated authentication server (IDA). + 5. Relays error from integrated authentication server to UI on failure. + + On Authentication Success: Only linkTransactionId is returned in the below response without any errors. + + On Authentication Failure: Error list will be set with the errors returned from the integrated authentication server. + parameters: [] + tags: + - WALLET + parameters: [] + /linked-authorization/v2/authenticate: + post: + summary: Linked Authentication Endpoint V2 + operationId: post-linked-authenticate-v2 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + consentAction: + type: string + x-stoplight: + id: n2zh4pnppn8kt + enum: + - CAPTURE + - NOCAPTURE + description: | + This field indicates the need to capture user consent or not + errors: + type: array + description: List of Errors in case of request validation / processing failure in Idp server. + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_transaction + - invalid_identifier + - invalid_no_of_challenges + - auth_failed + - unknown_error + errorMessage: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - linkedTransactionId + - individualId + - challengeList + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the link-transaction response. + individualId: + type: string + description: User identifier (UIN/VID). + challengeList: + type: array + description: Authentication Challenge. + items: + $ref: '#/components/schemas/AuthChallenge' + required: + - requestTime + - request + description: '' + description: |- + Once end user provides the user identifier (UIN/VID) and all the required auth challenge to the Wallet-app, this endpoint will be invoked from wallet-app. + + Supported auth-challenge depends on the integrated authentication server. + + 1. Validates linkedTransactionId. + 2. Validates null / empty individualId. + 4. Invokes kyc-auth call to integrated authentication server (IDA). + 5. Relays error from integrated authentication server to UI on failure. + 6. It validates stored userconsent against the requested claims and scopes + + On Authentication Success: linkTransactionId and consentAction is returned in the below response without any errors. + + On Authentication Failure: Error list will be set with the errors returned from the integrated authentication server. + parameters: [] + tags: + - WALLET + parameters: [] + /linked-authorization/consent: + post: + summary: Linked Consent Endpoint + operationId: post-linked-consent + description: | + Once the authentication is successful and user consent is obtained, this endpoint will be invoked by the wallet app to send the accepted consent and permitted scopes. + + 1. Validates linkedTransactionId. + 2. Validate accepted claims and permitted scopes in the request. + 3. If valid, stores the accepted claims and permitted scopes in the cache. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - linkedTransactionId + properties: + linkedTransactionId: + type: string + description: Transaction id echoed starting from /authorize call. + permittedAuthorizeScopes: + type: array + description: List of permitted scopes by end-user. + items: + type: string + acceptedClaims: + type: array + description: List of accepted essential and voluntary claims by end-user. + items: + type: string + required: + - requestTime + - request + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the link-transaction response. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_transaction + - invalid_accepted_claim + - invalid_permitted_scope + errorMessage: + type: string + tags: + - WALLET + parameters: [] + /linked-authorization/v2/consent: + post: + summary: Linked Consent Endpoint V2 + operationId: post-linked-consent-v2 + description: | + Once the authentication is successful and user consent is obtained, this endpoint will be invoked by the wallet app to send the accepted consent and permitted scopes. + + 1. Validates linkedTransactionId. + 2. Validate accepted claims and permitted scopes in the request and the signature. + 3. If valid, stores the accepted claims, permitted scopes and signature in the consent registry. + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + pattern: 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''' + request: + type: object + required: + - linkedTransactionId + properties: + linkedTransactionId: + type: string + description: Transaction id echoed starting from /authorize call. + permittedAuthorizeScopes: + type: array + description: List of permitted scopes by end-user. + items: + type: string + acceptedClaims: + type: array + description: List of accepted essential and voluntary claims by end-user. + items: + type: string + signature: + type: string + x-stoplight: + id: hw1q1wqlgf98v + description: Signature of permittedscopes and acceptedclaims from inji + required: + - requestTime + - request + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + linkedTransactionId: + type: string + description: This is the same transactionId sent in the link-transaction response. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction_id + - invalid_transaction + - invalid_accepted_claim + - invalid_permitted_scope + errorMessage: + type: string + tags: + - WALLET + parameters: [] + /linked-authorization/link-auth-code: + post: + summary: Link authorization code endpoint + tags: + - UI + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + code: + type: string + description: Authorization code. Required to obtain the ID token and / or access token from the /token endpoint. + redirectUri: + type: string + description: Client's validated redirect URI. + state: + type: string + description: 'The echoed state value, used to maintain state between the request and the callback' + nonce: + type: string + description: 'The echoed nonce value, if one was passed with the request. Clients must validate the value before proceeding.' + errors: + type: + - array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_transaction + - invalid_transaction_id + - invalid_link_code + - response_timeout + - unknown_error + errorMessage: + type: string + operationId: post-authorization-link-auth + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - transactionId + - linkedCode + properties: + transactionId: + type: string + description: This is the same transactionId sent in the oauth-details response. + linkedCode: + type: string + description: LINKED linkCode. + required: + - requestTime + - request + description: |- + Link authorization code endpoint is invoked from JS application. + + 1. This is a Long polling request to IdP-service. + 2. validates the transactionId + 3. validates the linkCode if its LINKED. + 4. checks the cache to see if the auth-code is generated, if yes returns the response. + 5. If the auth-code is not yet generated, polling request waits for the configured time. + 6. On successful response, IdP-UI should redirect to the provided redirectUri and auth-code or errors. + + + **Configuration to decide the wait interval**: mosip.idp.link-auth-code-deferred-response-timeout-secs + parameters: + - schema: + type: string + in: header + name: oauth-details-hash + description: Base64 encoded SHA-256 hash of the oauth-details endpoint response. + required: true + - schema: + type: string + in: header + name: oauth-details-key + description: Transaction Id + required: true + - schema: + type: string + in: header + name: X-XSRF-TOKEN + description: CSRF token as set in cookie key 'XSRF-TOKEN' + required: true + parameters: [] + /oauth/token: + post: + summary: Token Endpoint + operationId: post-token + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + id_token: + type: string + description: |- + Identity token in JWT format. Will have the below claims in the payload. +
    +
  • iss
  • +
  • sub - (PSUT)
  • +
  • aud
  • +
  • exp
  • +
  • iat
  • +
  • auth_time
  • +
  • nonce
  • +
  • acr
  • +
  • at_hash
  • +
+ access_token: + type: string + description: The access token in JWT format. This token that will be used to call the UserInfo endpoint. + token_type: + type: string + default: Bearer + enum: + - Bearer + description: 'The type of the access token, set to Bearer' + expires_in: + type: number + format: duration + description: 'The lifetime of the access token, in seconds.' + required: + - id_token + - access_token + - token_type + - expires_in + headers: + Cache-Control: + schema: + type: string + enum: + - no-store + Pragma: + schema: + type: string + enum: + - no-cache + '400': + description: Bad Request + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: The error code. + enum: + - invalid_transaction + - invalid_assertion + - invalid_redirect_uri + - invalid_input + - unknown_error + - invalid_request + - invalid_assertion_type + error_description: + type: string + description: Optional text providing additional information about the error that occurred. + required: + - error + description: |- + Once the client / relying party application receives the authorization code through redirect, this OIDC complaint endpoint will be called from the relying party backend application to get the ID and access token. + + 1. The only supported client authentication methods : private_key_jwt + 2. clientAssertion is a signed JWT with Clients private key, corresponding public key should be shared with IdP during the OIDC client registration process. + 3. clientAssertion JWT payload must be as below: + + The JWT MUST contain the following REQUIRED Claim Values and MAY contain the additional OPTIONAL Claim Values: + + **iss*** (Issuer): This MUST contain the client_id of the OAuth Client. + + **sub*** (Subject): This MUST contain the client_id of the OAuth Client. + + **aud*** (Audience): Value that identifies the authorization server as an intended audience. The authorization server MUST verify that it is an intended audience for the token. The audience SHOULD be the URL of the authorization server's token endpoint. + + **exp*** (Expiration): Time on or after which the ID token MUST NOT be accepted for processing. + + **iat***: Time at which the JWT was issued.

+ + **Note**: The Client Assertion JWT can contain other Claims. Any Claims used that are not understood WILL be ignored.

+ requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + grant_type: + type: string + description: Authorization code grant type. + enum: + - authorization_code + code: + type: string + description: 'Authorization code, sent as query param in the client''s redirect URI.' + client_id: + type: string + description: Client Id of the OIDC client. + client_assertion_type: + type: string + enum: + - 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + description: Type of the client assertion part of this request. + client_assertion: + type: string + description: 'Private key signed JWT, This JWT payload structure is defined above as part of request description.' + redirect_uri: + type: string + description: Valid client redirect_uri. Must be same as the one sent in the authorize call. + required: + - grant_type + - code + - client_assertion_type + - client_assertion + - redirect_uri + description: '' + tags: + - OIDC + parameters: [] + /oidc/userinfo: + get: + summary: UserInfo Endpoint + responses: + '200': + description: OK + content: + application/jwt: + schema: + type: string + format: jwt + description: 'The response is signed and then encrypted, with the result being a Nested JWT. Signed using the authentication system''s private key. Signed full JWT will then be encrypted using OIDC client''s public key.' + '401': + description: Unauthorized + headers: + WWW-AUTHENTICATE: + schema: + type: string + enum: + - invalid_token + - unknown_error + description: 'Bearer error=invalid_token, error_description=MOSIPIDP123: A user info request was made with an access token that was not recognized.' + operationId: get-userinfo + parameters: [] + description: |- + Once the access token is received via the token endpoint, relying party backend application can call this OIDC compliant endpoint to request for the user claims. + + Consented user claims will be returned as a JWT. This JWT will be a nested JWT which is a signed using JWS and then encrypted using JWE. + + + **Example**: Assuming the below are the requested claims by the relying party + + name : { "essential" : true } + + phone: { "essential" : true } + + **Response 1**: When consent is provided for both name and phone number: + + { "name" : "John Doe", "phone" : "033456743" } + + **Response 2**: When consent is provided for only name: + + { "name" : "John Doe" } + + **Response 3**: When Claims are requested with claims_locales : "en fr" + + { "name#en" : "John Doe", "name#fr" : "Jean Doe", "phone" : "033456743" } + + **Supported User Info Claims** +
    +
  • sub - Partner Specific User Token (PSUT)
  • +
  • name
  • +
  • address
  • +
  • gender
  • +
  • birthdate
  • +
  • profile photo
  • +
  • email
  • +
  • phone
  • +
  • locale
  • +
  • Custom - individual_id (You share this claim as a system-level config and it can be UIN, perceptual VID or temporary VID)
  • +
+ tags: + - OIDC + security: + - Authorization-access_token: [] + parameters: [] + /binding/binding-otp: + post: + summary: Send Binding OTP Endpoint + operationId: post-binding-otp + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTIme: + type: string + response: + type: object + properties: + maskedEmail: + type: string + description: Masked email id of the individualId user. + maskedMobile: + type: string + description: Masked mobile number of the individualId user. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - invalid_otp_channel + - unknown_error + - invalid_individual_id + - send_otp_failed + errorMessage: + type: string + required: + - responseTIme + parameters: + - schema: + type: string + in: header + name: partner-api-key + description: 'API key of the binding partner, this will be passed to binder implementation to interact with authentication system.' + - schema: + type: string + in: header + name: partner-id + description: 'Binding partner Identifier, this will be passed to binder implementation to interact with authentication system.' + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - individualId + - otpChannels + properties: + individualId: + type: string + description: User Id (UIN/VID) + otpChannels: + type: array + description: Channels to which OTP should be delivered. + items: + type: string + required: + - requestTime + - request + description: Send wallet binding OTP endpoint is invoked by Mimoto server. + security: + - Authorization-send_binding_otp: [] + tags: + - binding-service + parameters: [] + /binding/wallet-binding: + post: + summary: Wallet Binding Endpoint + operationId: post-wallet-binding + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + responseTime: + type: string + response: + type: object + properties: + walletUserId: + type: string + description: Unique identifier given to public-key and partner specific userId mapping. + certificate: + type: string + description: Key binder signed certificate. + expireDateTime: + type: string + description: Expire date time of the signed certificate. + errors: + type: array + items: + type: object + properties: + errorCode: + type: string + enum: + - unsupported_challenge_format + - key_binding_failed + - invalid_public_key + - invalid_auth_challenge + - duplicate_public_key + errorMessage: + type: string + required: + - responseTime + requestBody: + content: + application/json: + schema: + type: object + properties: + requestTime: + type: string + request: + type: object + required: + - individualId + - authFactorType + - format + - challengeList + - publicKey + properties: + individualId: + type: string + description: User Id (UIN/VID). + authFactorType: + type: string + description: Auth factor type to be binded for the provided key. + format: + type: string + description: 'Format of the auth factor type supported in the wallet app.This is not stored, this value is only validated to check if its a supported format in the keybinder implementation.' + challengeList: + type: array + items: + $ref: '#/components/schemas/AuthChallenge' + publicKey: + type: object + description: key to be binded in JWK format. + required: + - requestTime + - request + description: |- + Wallet binding endpoint is invoked by Mimoto server. + + 1. This request is invoked from wallet-app with authChallenge. + 2. Integrated keybinder implementation validates the authChallenge. + 3. Public key registry is updated with the key binding details for the provided individualId. + 4. Binded walletUserId (WUID) is returned with keybinder signed certificate. + + **Note**: Binding entry uniqueness is combination of these 3 values -> (PSUT, public-key, auth-factor-type) + parameters: + - schema: + type: string + in: header + name: partner-api-key + description: 'API key of the Binding partner, this will be passed to binder implementation to interact with authentication system.' + - schema: + type: string + in: header + name: partner-id + description: 'Binding partner identifier, this will be passed to binder implementation to interact with authentication system.' + security: + - Authorization-wallet_binding: [] + tags: + - binding-service + parameters: [] + /.well-known/jwks.json: + get: + summary: JSON Web Key Set Endpoint + tags: + - OIDC + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + keys: + type: array + items: + type: object + properties: + kid: + type: string + description: The certificate's Key ID + use: + type: string + description: 'How the Key is used. Valid value: sig' + enum: + - sig + kty: + type: string + description: 'Cryptographic algorithm family for the certificate''s Key pair. Valid value: RSA' + enum: + - RSA + e: + type: string + description: RSA Key value (exponent) for Key blinding + 'n': + type: string + description: RSA modulus value + x5t#S256: + type: string + x-stoplight: + id: 52n5251u1kfap + description: SHA-256 thumbprint of the certificate. + x5c: + type: array + description: Certificate to validate the Oauth server trust. + items: + x-stoplight: + id: l0l1kpf2b9jcn + type: string + exp: + type: string + x-stoplight: + id: q4o4nzqtvb09p + description: Expire datetime of the key. Given in ISO format. + format: date-time + example: '2026-02-05T13:43:07.979Z' + required: + - kid + - use + - kty + - e + - 'n' + - x5t#S256 + - x5c + - exp + operationId: get-certs + description: Endpoint to fetch all the public keys of the e-Signet server. Returns public key set in the JWKS format. + parameters: [] + /.well-known/openid-configuration: + get: + summary: Configuration Endpoint + tags: + - OIDC + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + issuer: + type: string + description: URL using the https scheme with no query or fragment component that the RP asserts as its Issuer Identifier. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer. + authorization_endpoint: + type: string + description: URL of the OAuth 2.0 Authorization Endpoint. + token_endpoint: + type: string + description: URL of the OAuth 2.0 Token Endpoint. + userinfo_endpoint: + type: string + description: URL of the OP's UserInfo Endpoint. + jwks_uri: + type: string + description: 'URL of the OP''s JSON Web Key Set [JWK] document.' + registration_endpoint: + type: string + description: URL of Client Registration Endpoint. + scopes_supported: + type: array + description: 'JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports.' + enum: + - openid + items: {} + response_types_supported: + type: array + description: JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. + enum: + - code + items: {} + acr_values_supported: + type: array + description: JSON array containing a list of the Authentication Context Class References that IDP supports. + items: {} + userinfo_signing_alg_values_supported: + type: array + description: 'JSON array containing a list of the JWS [JWS] signing algorithms.' + items: {} + userinfo_encryption_alg_values_supported: + type: array + description: 'JSON array containing a list of the JWE [JWE] encryption algorithms.' + items: {} + userinfo_encryption_enc_values_supported: + type: array + description: 'JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT.' + items: {} + token_endpoint_auth_methods_supported: + type: array + description: JSON array containing a list of Client Authentication methods supported by this Token Endpoint. + enum: + - private_key_jwt + items: {} + display_values_supported: + type: array + description: JSON array containing a list of the display parameter values that the OpenID Provider supports. + items: {} + claim_types_supported: + type: array + description: JSON array containing a list of the Claim Types that the OpenID Provider supports. + enum: + - normal + - aggregated + - distributed + items: {} + claims_supported: + type: array + description: JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. + items: + type: string + claims_locales_supported: + type: array + description: Languages and scripts supported for values in Claims being returned. + items: + type: string + ui_locales_supported: + type: array + description: Languages and scripts supported for the user interface. + items: + type: string + response_modes_supported: + type: array + description: Mechanism to be used for returning parameters from the Authorization Endpoint. + items: + type: string + enum: + - query + token_endpoint_auth_signing_alg_values_supported: + type: array + items: + type: string + enum: + - RS256 + id_token_signing_alg_values_supported: + type: array + items: + type: string + enum: + - RS256 + required: + - issuer + - authorization_endpoint + - token_endpoint + - userinfo_endpoint + - jwks_uri + - registration_endpoint + - scopes_supported + - response_types_supported + operationId: get-.well-known-openid-configuration + description: |- + Open ID Connect dynamic provider discovery is not supported currently, this endpoint is only for facilitating the OIDC provider details in a standard way. + + **Reference**: https://openid.net/specs/openid-connect-discovery-1_0.html + parameters: [] + /oauth/introspect: + get: + summary: Introspect Endpoint (Draft) + tags: + - OIDC + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + active: + type: boolean + '401': + description: Unauthorized + content: + application/json: + schema: + type: object + properties: + error: + type: string + error_description: + type: string + required: + - error + - error_description + operationId: get-introspect + description: |- + This endpoint takes an access token or ID token and returns a boolean that indicates whether it is active. If the token is active, additional data about the token is also returned. If the token is invalid, expired, or revoked, it is considered inactive. + + **Reference**: https://www.rfc-editor.org/rfc/rfc7662.html + parameters: + - schema: + type: string + in: query + name: token + description: An access token or ID token + required: true + - schema: + type: string + enum: + - access_token + - id_token + in: query + name: token_type_hint + description: 'Indicates the type of token being passed. Valid values: access_token, id_token' + required: true + security: + - Authorization-Bearer: [] + parameters: [] +components: + schemas: + Claim: + title: Claim + x-stoplight: + id: z7c32949w8qet + type: object + description: | + The userinfo and id_token members of the claims request both are JSON object. if null, Indicates that this Claim is being requested as Voluntary Claim. + + Note: Unknown claim names either in userinfo or id_token are ignored. + properties: + userinfo: + type: object + properties: + name: + $ref: '#/components/schemas/ClaimDetail' + given_name: + $ref: '#/components/schemas/ClaimDetail' + email: + $ref: '#/components/schemas/ClaimDetail' + gender: + $ref: '#/components/schemas/ClaimDetail' + birthdate: + $ref: '#/components/schemas/ClaimDetail' + phone_number: + $ref: '#/components/schemas/ClaimDetail' + profile_photo: + $ref: '#/components/schemas/ClaimDetail' + address: + $ref: '#/components/schemas/ClaimDetail' + locale: + $ref: '#/components/schemas/ClaimDetail' + individual_id: + $ref: '#/components/schemas/ClaimDetail' + id_token: + type: object + properties: + name: + $ref: '#/components/schemas/ClaimDetail' + acrs: + $ref: '#/components/schemas/ClaimDetail' + locales: + type: array + items: + type: string + ClaimDetail: + title: ClaimDetail + x-stoplight: + id: kypheo15jidj9 + type: object + properties: + essential: + type: boolean + description: | + Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim. The default is false. + value: + type: string + description: |- + Requests that the Claim be returned with a particular value. For instance the Claim request. + + "sub": {"value": "248289761001"} can be used to specify that the request apply to the End-User with Subject Identifier 248289761001. + values: + type: array + description: 'Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference.' + items: + type: string + AuthChallenge: + title: AuthChallenge + x-stoplight: + id: n3fy2qkg9r7h2 + type: object + description: Model to take any type of challenge from the end user as part of authenticate request. + properties: + authFactorType: + type: string + description: Defines the type of auth challenge. It should be same as authfactor.type (oauth-details response). + enum: + - OTP + - BIO + - PIN + - WLA + challenge: + type: string + description: Actual challenge as string. + format: + type: string + description: Format of the challenge provided. + enum: + - alpha-numeric + - jwt + - encoded-json + - number + required: + - authFactorType + - challenge + - format + AuthFactor: + title: AuthFactor + x-stoplight: + id: m6lnp87wondln + type: object + properties: + type: + type: string + description: Name of the authentication method + enum: + - PIN + - OTP + - L1-bio-device + - Wallet + count: + type: integer + description: 'Applicable for biometric based authentication, number of bio segments to be captured for authentication.' + bioSubTypes: + type: array + description: Applicable for biometric based authentication. Can be more specific about which bio segments should be captured. + items: + type: string + required: + - type + securitySchemes: + Authorization-Bearer: + type: http + scheme: bearer + Authorization-add_oidc_client: + type: http + scheme: bearer + description: Valid JWT issued by a trusted IAM system with "**add_oidc_client**" scope. + Authorization-update_oidc_client: + type: http + scheme: bearer + description: Valid JWT issued by a trusted IAM system including "**update_oidc_client**" scope. + Authorization-access_token: + type: http + scheme: bearer + description: Access token received from /token endpoint + Authorization-send_binding_otp: + type: http + scheme: bearer + description: Valid JWT issued by a trusted IAM system with "send_binding_otp" scope. + Authorization-wallet_binding: + type: http + scheme: bearer + description: Valid JWT issued by a trusted IAM system with "**wallet_binding**" scope. +tags: + - name: Management + description: Management level API's used for internal use. + - name: OIDC + description: API's that are supposed to be compliant to OIDC. + - name: UI + description: UI related API. diff --git a/esignet-core/pom.xml b/esignet-core/pom.xml index 1433838f7..413ee5534 100644 --- a/esignet-core/pom.xml +++ b/esignet-core/pom.xml @@ -9,7 +9,7 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet @@ -25,7 +25,7 @@ 2.12.0 2.12.0 2.12.0 - 1.2.1-SNAPSHOT + 1.2.0.1-B2 3.27.0-GA 4.13.2 2.22.0 diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/constants/ErrorConstants.java b/esignet-core/src/main/java/io/mosip/esignet/core/constants/ErrorConstants.java index c964c34eb..af25fbff1 100644 --- a/esignet-core/src/main/java/io/mosip/esignet/core/constants/ErrorConstants.java +++ b/esignet-core/src/main/java/io/mosip/esignet/core/constants/ErrorConstants.java @@ -65,4 +65,6 @@ public class ErrorConstants { public static final String FAILED_TO_GENERATE_HEADER_HASH = "failed_to_generate_header_hash"; public static final String FAILED_TO_VALIDATE_CAPTCHA = "failed_to_validate_captcha"; public static final String INVALID_CAPTCHA = "invalid_captcha"; + public static final String INVALID_AUTH_FACTOR_TYPE_OR_CHALLENGE_FORMAT = "invalid_auth_factor_type_or_challenge_format"; + } diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/AuthResponseV2.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/AuthResponseV2.java new file mode 100644 index 000000000..8983230a6 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/AuthResponseV2.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.esignet.core.dto; + +import io.mosip.esignet.api.util.ConsentAction; +import lombok.Data; + +@Data +public class AuthResponseV2 { + + private String transactionId; + private ConsentAction consentAction; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/ConsentDetail.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/ConsentDetail.java new file mode 100644 index 000000000..4752b0b51 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/ConsentDetail.java @@ -0,0 +1,29 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.core.dto; + +import io.mosip.esignet.api.dto.Claims; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Data +public class ConsentDetail { + private UUID id; + private String clientId; + private String psuToken; + private Claims claims; + Map authorizationScopes; + private LocalDateTime createdtimes; + private LocalDateTime expiredtimes; + private String signature; + private String hash; + private List acceptedClaims; + private List permittedScopes; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedConsentRequestV2.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedConsentRequestV2.java new file mode 100644 index 000000000..bc3d66960 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedConsentRequestV2.java @@ -0,0 +1,31 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.core.dto; + +import io.mosip.esignet.core.constants.ErrorConstants; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +@Data +public class LinkedConsentRequestV2 { + + @NotBlank(message = ErrorConstants.INVALID_TRANSACTION_ID) + private String linkedTransactionId; + + /** + * List of accepted claim names by end-user + */ + private List acceptedClaims; + + /** + * List of permitted authorize scopes + */ + private List permittedAuthorizeScopes; + + private String signature; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedKycAuthResponseV2.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedKycAuthResponseV2.java new file mode 100644 index 000000000..436734f84 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/LinkedKycAuthResponseV2.java @@ -0,0 +1,15 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.core.dto; + +import io.mosip.esignet.api.util.ConsentAction; +import lombok.Data; + +@Data +public class LinkedKycAuthResponseV2 { + private String linkedTransactionId; + private ConsentAction consentAction; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/OIDCTransaction.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/OIDCTransaction.java index 9b9a8ab4b..07e71a30d 100644 --- a/esignet-core/src/main/java/io/mosip/esignet/core/dto/OIDCTransaction.java +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/OIDCTransaction.java @@ -6,6 +6,7 @@ package io.mosip.esignet.core.dto; import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.api.util.ConsentAction; import io.mosip.esignet.core.util.LinkCodeQueue; import lombok.Data; @@ -16,10 +17,14 @@ @Data public class OIDCTransaction implements Serializable { + String transactionId; + String clientId; String relyingPartyId; String redirectUri; Claims requestedClaims; + List essentialClaims; + List voluntaryClaims; List requestedAuthorizeScopes; String[] claimsLocales; String authTransactionId; @@ -47,4 +52,5 @@ public class OIDCTransaction implements Serializable { String individualId; String oauthDetailsHash; + ConsentAction consentAction; } diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/PublicKeyRegistry.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/PublicKeyRegistry.java new file mode 100644 index 000000000..99ccfe159 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/PublicKeyRegistry.java @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.core.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +public class PublicKeyRegistry { + private String authFactor; + private String psuToken; + private String publicKey; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsent.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsent.java new file mode 100644 index 000000000..558051b84 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsent.java @@ -0,0 +1,21 @@ +package io.mosip.esignet.core.dto; + +import io.mosip.esignet.api.dto.Claims; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Data +public class UserConsent { + String psuToken; + String clientId; + Claims Claims; + Map authorizationScopes; + LocalDateTime expirydtimes; + String signature; + String hash; + List acceptedClaims; + List permittedScopes; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsentRequest.java b/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsentRequest.java new file mode 100644 index 000000000..0cc018f09 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/dto/UserConsentRequest.java @@ -0,0 +1,9 @@ +package io.mosip.esignet.core.dto; + +import lombok.Data; + +@Data +public class UserConsentRequest { + String psuToken; + String clientId; +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/AuthorizationService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/AuthorizationService.java index 1a8f6c980..46269a926 100644 --- a/esignet-core/src/main/java/io/mosip/esignet/core/spi/AuthorizationService.java +++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/AuthorizationService.java @@ -7,7 +7,6 @@ import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.exception.EsignetException; -import io.mosip.esignet.core.dto.*; public interface AuthorizationService { @@ -33,6 +32,13 @@ public interface AuthorizationService { */ AuthResponse authenticateUser(AuthRequest authRequest) throws EsignetException; + /** + * Authentication request for the required auth-factors + * @param authRequest + * @return + */ + AuthResponseV2 authenticateUserV2(AuthRequest authRequest) throws EsignetException; + /** * Accepted claims are verified and KYC exchange is performed * Redirects to requested redirect_uri diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/ConsentService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/ConsentService.java new file mode 100644 index 000000000..029ed6a49 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/ConsentService.java @@ -0,0 +1,34 @@ +package io.mosip.esignet.core.spi; + +import io.mosip.esignet.core.dto.ConsentDetail; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.core.dto.UserConsentRequest; +import io.mosip.esignet.core.exception.EsignetException; + +import java.util.Optional; + +public interface ConsentService { + /** + * Api to get Latest User consent data from consent registry. + * + * @param userConsentRequest Consent Request object containing client_id and psu_token + * @return the Consent wrapped in an {@link Optional} + */ + Optional getUserConsent(UserConsentRequest userConsentRequest); + + /** + * Api to Add User Consent data in Consent Registry + * + * @param userConsent consentRequest Object + * @return {@link ConsentDetail} Consent Response Object after saving the consent to registry. + * + */ + ConsentDetail saveUserConsent(UserConsent userConsent) throws EsignetException; + + /** + * Api to delete user consent from Consent Registry + * @param psuToken + * @param clientId + */ + void deleteUserConsent(String clientId, String psuToken); +} diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/LinkedAuthorizationService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/LinkedAuthorizationService.java index ecfa72a6e..9ffaae048 100644 --- a/esignet-core/src/main/java/io/mosip/esignet/core/spi/LinkedAuthorizationService.java +++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/LinkedAuthorizationService.java @@ -7,7 +7,6 @@ import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.exception.EsignetException; -import io.mosip.esignet.core.dto.*; import org.springframework.web.context.request.async.DeferredResult; public interface LinkedAuthorizationService { @@ -55,6 +54,13 @@ public interface LinkedAuthorizationService { */ LinkedKycAuthResponse authenticateUser(LinkedKycAuthRequest linkedKycAuthRequest) throws EsignetException; + /** + * Authentication request for the required auth-factors + * @param linkedKycAuthRequest + * @return + */ + LinkedKycAuthResponseV2 authenticateUserV2(LinkedKycAuthRequest linkedKycAuthRequest) throws EsignetException; + /** * Accepted claims are verified and KYC exchange is performed * Redirects to requested redirect_uri @@ -62,4 +68,11 @@ public interface LinkedAuthorizationService { */ LinkedConsentResponse saveConsent(LinkedConsentRequest linkedConsentRequest) throws EsignetException; + /** + * Accepted claims are verified and KYC exchange is performed + * Redirects to requested redirect_uri + * @param linkedConsentRequest + */ + LinkedConsentResponse saveConsentV2(LinkedConsentRequestV2 linkedConsentRequest) throws EsignetException; + } diff --git a/esignet-core/src/main/java/io/mosip/esignet/core/spi/PublicKeyRegistryService.java b/esignet-core/src/main/java/io/mosip/esignet/core/spi/PublicKeyRegistryService.java new file mode 100644 index 000000000..997e1f4c2 --- /dev/null +++ b/esignet-core/src/main/java/io/mosip/esignet/core/spi/PublicKeyRegistryService.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.esignet.core.spi; + +import io.mosip.esignet.core.dto.PublicKeyRegistry; + +import java.util.Optional; + +public interface PublicKeyRegistryService { + + Optional findLatestPublicKeyByPsuTokenAndAuthFactor(String psuToken, String authFactor); + +} diff --git a/esignet-integration-api/pom.xml b/esignet-integration-api/pom.xml index 15d783be9..30a9104ec 100644 --- a/esignet-integration-api/pom.xml +++ b/esignet-integration-api/pom.xml @@ -6,12 +6,12 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet esignet-integration-api - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT esignet-integration-api e-Signet integration Library diff --git a/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/Action.java b/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/Action.java index 3f7c70172..1ca13f461 100644 --- a/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/Action.java +++ b/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/Action.java @@ -20,5 +20,9 @@ public enum Action { LINK_AUTHENTICATE, SAVE_CONSENT, LINK_SEND_OTP, - LINK_AUTH_CODE + LINK_AUTH_CODE, + GET_USER_CONSENT, + SAVE_USER_CONSENT, + UPDATE_USER_CONSENT, + DELETE_USER_CONSENT } diff --git a/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/ConsentAction.java b/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/ConsentAction.java new file mode 100644 index 000000000..45869719d --- /dev/null +++ b/esignet-integration-api/src/main/java/io/mosip/esignet/api/util/ConsentAction.java @@ -0,0 +1,6 @@ +package io.mosip.esignet.api.util; + +public enum ConsentAction { + CAPTURE, + NOCAPTURE, +} diff --git a/esignet-service/pom.xml b/esignet-service/pom.xml index 88cad077e..e0c1744b4 100644 --- a/esignet-service/pom.xml +++ b/esignet-service/pom.xml @@ -8,12 +8,12 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet esignet-service - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT esignet-service e-Signet OIDC Service @@ -66,6 +66,11 @@ binding-service-impl ${project.version} + + io.mosip.esignet + consent-service-impl + ${project.version} + diff --git a/esignet-service/src/main/java/io/mosip/esignet/controllers/AuthorizationController.java b/esignet-service/src/main/java/io/mosip/esignet/controllers/AuthorizationController.java index 59ff4bb9d..24f2cbcaa 100644 --- a/esignet-service/src/main/java/io/mosip/esignet/controllers/AuthorizationController.java +++ b/esignet-service/src/main/java/io/mosip/esignet/controllers/AuthorizationController.java @@ -85,4 +85,19 @@ public ResponseWrapper getAuthorizationCode(@Valid @RequestBod } return responseWrapper; } + + @PostMapping("/v2/authenticate") + public ResponseWrapper authenticateEndUserV2(@Valid @RequestBody RequestWrapper + requestWrapper) throws EsignetException { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + responseWrapper.setResponseTime(IdentityProviderUtil.getUTCDateTime()); + try { + AuthResponseV2 authResponse = authorizationService.authenticateUserV2(requestWrapper.getRequest()); + responseWrapper.setResponse(authResponse); + } catch (EsignetException ex) { + auditWrapper.logAudit(Action.AUTHENTICATE, ActionStatus.ERROR, AuditHelper.buildAuditDto(requestWrapper.getRequest().getTransactionId(), null), ex); + throw ex; + } + return responseWrapper; + } } diff --git a/esignet-service/src/main/java/io/mosip/esignet/controllers/LinkedAuthorizationController.java b/esignet-service/src/main/java/io/mosip/esignet/controllers/LinkedAuthorizationController.java index 398af0eb0..680ff3710 100644 --- a/esignet-service/src/main/java/io/mosip/esignet/controllers/LinkedAuthorizationController.java +++ b/esignet-service/src/main/java/io/mosip/esignet/controllers/LinkedAuthorizationController.java @@ -107,6 +107,20 @@ public ResponseWrapper authenticate(@Valid @RequestBody R return responseWrapper; } + @PostMapping("/v2/authenticate") + public ResponseWrapper authenticateV2(@Valid @RequestBody RequestWrapper + requestWrapper) throws EsignetException { + ResponseWrapper responseWrapper = new ResponseWrapper(); + responseWrapper.setResponseTime(IdentityProviderUtil.getUTCDateTime()); + try { + responseWrapper.setResponse(linkedAuthorizationService.authenticateUserV2(requestWrapper.getRequest())); + } catch (EsignetException ex) { + auditWrapper.logAudit(Action.LINK_AUTHENTICATE, ActionStatus.ERROR, AuditHelper.buildAuditDto(requestWrapper.getRequest().getLinkedTransactionId(), null), ex); + throw ex; + } + return responseWrapper; + } + @PostMapping("/consent") public ResponseWrapper saveConsent(@Valid @RequestBody RequestWrapper requestWrapper) throws EsignetException { @@ -121,6 +135,20 @@ public ResponseWrapper saveConsent(@Valid @RequestBody Re return responseWrapper; } + @PostMapping("/v2/consent") + public ResponseWrapper saveConsentV2(@Valid @RequestBody RequestWrapper + requestWrapper) throws EsignetException { + ResponseWrapper responseWrapper = new ResponseWrapper(); + responseWrapper.setResponseTime(IdentityProviderUtil.getUTCDateTime()); + try { + responseWrapper.setResponse(linkedAuthorizationService.saveConsentV2(requestWrapper.getRequest())); + } catch (EsignetException ex) { + auditWrapper.logAudit(Action.SAVE_CONSENT, ActionStatus.ERROR, AuditHelper.buildAuditDto(requestWrapper.getRequest().getLinkedTransactionId(), null), ex); + throw ex; + } + return responseWrapper; + } + @PostMapping("/send-otp") public ResponseWrapper sendOtp(@Valid @RequestBody RequestWrapper requestWrapper) throws EsignetException { diff --git a/esignet-service/src/main/resources/application-local.properties b/esignet-service/src/main/resources/application-local.properties index ce5208ad7..9e91ab7a4 100644 --- a/esignet-service/src/main/resources/application-local.properties +++ b/esignet-service/src/main/resources/application-local.properties @@ -10,6 +10,7 @@ mosip.esignet.supported-id-regex=\\S* mosip.esignet.id-token-expire-seconds=3600 mosip.esignet.access-token.expire.seconds=3600 mosip.esignet.link-code-expire-in-secs=60 +mosip.esignet.authentication-expire-in-secs=60 mosip.esignet.header-filter.paths-to-validate={'${server.servlet.path}/authorization/send-otp', \ '${server.servlet.path}/authorization/authenticate', \ @@ -135,10 +136,9 @@ spring.cache.type=simple mosip.esignet.cache.key.hash.algorithm=SHA3-256 mosip.esignet.cache.size={'clientdetails' : 200, 'preauth': 200, 'authenticated': 200, 'authcodegenerated': 200, 'userinfo': 200, \ 'linkcodegenerated' : 500, 'linked': 200 , 'linkedcode': 200, 'linkedauth' : 200 , 'consented' :200 } -mosip.esignet.cache.expire-in-seconds={'clientdetails' : 86400, 'preauth': 180, 'authenticated': 120, 'authcodegenerated': 60, \ - 'userinfo': ${mosip.esignet.access-token.expire.seconds}, 'linkcodegenerated' : ${mosip.esignet.link-code-expire-in-secs}, \ - 'linked': 60 , 'linkedcode': ${mosip.esignet.link-code-expire-in-secs}, 'linkedauth' : 60, 'consented': 120 } - +mosip.esignet.cache.expire-in-seconds={'clientdetails' : 86400, 'preauth': 180, 'authenticated': ${mosip.esignet.authentication-expire-in-secs}, \ + 'authcodegenerated': 60, 'userinfo': ${mosip.esignet.access-token.expire.seconds}, 'linkcodegenerated' : ${mosip.esignet.link-code-expire-in-secs}, \ + 'linked': 60 , 'linkedcode': ${mosip.esignet.link-code-expire-in-secs}, 'linkedauth' : ${mosip.esignet.authentication-expire-in-secs}, 'consented': 120 } ## ------------------------------------------ Discovery openid-configuration ------------------------------------------- mosip.esignet.discovery.issuer-id=${mosipbox.public.url}${server.servlet.path} @@ -235,4 +235,11 @@ crypto.PrependThumbprint.enable=true mosip.esignet.ui.config.key-values={'sbi.env': 'Developer', 'sbi.timeout.DISC': 30, \ 'sbi.timeout.DINFO': 30, 'sbi.timeout.CAPTURE': 30, 'sbi.capture.count.face': 1, 'sbi.capture.count.finger': 2, \ - 'sbi.capture.count.iris': 1, 'sbi.capture.score.face': 70, 'sbi.capture.score.finger':70, 'sbi.capture.score.iris':70 } + 'sbi.capture.count.iris': 1, 'sbi.capture.score.face': 70, 'sbi.capture.score.finger':70, 'sbi.capture.score.iris':70, 'wallet.logo-url': 'inji_logo.png', \ + 'send.otp.channels':'email,phone', 'consent.screen.timeout-in-secs':${mosip.esignet.authentication-expire-in-secs}, \ + 'consent.screen.timeout-buffer-in-secs': 5, 'sbi.port.range': 4501-4600, 'sbi.bio.subtypes.iris': 'UNKNOWN', 'sbi.bio.subtypes.finger': 'UNKNOWN', \ + 'resend.otp.delay.secs': 120, 'captcha.enable': 'OTP', 'captcha.sitekey': '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', \ + 'mosip.esignet.link-auth-code-expire-in-secs': 120, 'mosip.esignet.link-status-deferred-response-timeout-secs': 25, \ + 'mosip.esignet.qr-code.deep-link-uri': 'inji://landing-page-name?linkCode=LINK_CODE&linkExpireDateTime=LINK_EXPIRE_DT', \ + 'mosip.esignet.qr-code.download-uri': '#', 'mosip.esignet.qr-code.enable': 'true', 'auth.txnid.length': 10, \ + 'otp.length': 6, 'password.regex': ''} \ No newline at end of file diff --git a/esignet-service/src/main/resources/messages.properties b/esignet-service/src/main/resources/messages.properties index 3cda29a76..e323ad31a 100644 --- a/esignet-service/src/main/resources/messages.properties +++ b/esignet-service/src/main/resources/messages.properties @@ -67,3 +67,4 @@ binding_auth_failed=Key bound authentication (linked-wallet) failed. captcha_validator_not_found=Failed to find captcha validator instance, Please check classpath. invalid_captcha=Invalid captcha found. invalid_bind_auth_factor_type=Invalid bind auth factor type. +invalid_auth_factor_type_or_challenge_format=Invalid auth factor type or challenge format. diff --git a/esignet-service/src/test/java/io/mosip/esignet/controllers/AuthorizationControllerTest.java b/esignet-service/src/test/java/io/mosip/esignet/controllers/AuthorizationControllerTest.java index 29d98e245..cc39be3c4 100644 --- a/esignet-service/src/test/java/io/mosip/esignet/controllers/AuthorizationControllerTest.java +++ b/esignet-service/src/test/java/io/mosip/esignet/controllers/AuthorizationControllerTest.java @@ -6,19 +6,20 @@ package io.mosip.esignet.controllers; import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.api.dto.AuthChallenge; import io.mosip.esignet.api.spi.AuditPlugin; -import io.mosip.esignet.core.dto.OAuthDetailRequest; -import io.mosip.esignet.core.dto.OAuthDetailResponse; -import io.mosip.esignet.core.dto.RequestWrapper; +import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.exception.EsignetException; import io.mosip.esignet.core.spi.AuthorizationService; import io.mosip.esignet.core.util.AuthenticationContextClassRefUtil; import io.mosip.esignet.core.constants.ErrorConstants; +import io.mosip.esignet.core.util.IdentityProviderUtil; import io.mosip.esignet.services.AuthorizationHelperService; import io.mosip.esignet.services.CacheUtilService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -29,7 +30,9 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import static io.mosip.esignet.core.constants.Constants.UTC_DATETIME_PATTERN; import static org.mockito.Mockito.when; @@ -351,4 +354,113 @@ public void getOauthDetails_withAuthorizeAndOpenIdScope_returnSuccessResponse() .andExpect(status().isOk()) .andExpect(jsonPath("$.response.transactionId").value("qwertyId")); } + + @Test + public void authenticateEndUser_withValidDetails_returnSuccessResponse() throws Exception { + AuthRequest authRequest = new AuthRequest(); + authRequest.setIndividualId("1234567890"); + authRequest.setTransactionId("quewertyId"); + + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("12345"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setFormat("numeric"); + + List authChallengeList = new ArrayList<>(); + authChallengeList.add(authChallenge); + authRequest.setChallengeList(authChallengeList); + + RequestWrapper wrapper = new RequestWrapper<>(); + wrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + wrapper.setRequest(authRequest); + + AuthResponseV2 authResponseV2 = new AuthResponseV2(); + authResponseV2.setTransactionId("quewertyId"); + when(authorizationService.authenticateUserV2(authRequest)).thenReturn(authResponseV2); + mockMvc.perform(post("/authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(wrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response.transactionId").value("quewertyId")); + } + + @Test + public void authenticateEndUser_withInvalidTimestamp_returnErrorResponse() throws Exception { + AuthRequest authRequest = new AuthRequest(); + authRequest.setIndividualId("1234567890"); + authRequest.setTransactionId("1234567890"); + + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("1234567890"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setFormat("alpha-numeric"); + + List authChallengeList = new ArrayList<>(); + authChallengeList.add(authChallenge); + + authRequest.setChallengeList(authChallengeList); + + ZonedDateTime requestTime = ZonedDateTime.now(ZoneOffset.UTC); + requestTime = requestTime.plusMinutes(10); + + RequestWrapper wrapper = new RequestWrapper<>(); + wrapper.setRequestTime(requestTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); + wrapper.setRequest(authRequest); + when(authorizationService.authenticateUserV2(authRequest)).thenReturn(new AuthResponseV2()); + mockMvc.perform(post("/authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(wrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_REQUEST)) + .andExpect(jsonPath("$.errors[0].errorMessage").value("requestTime: invalid_request")); + } + + @Test + public void authenticateEndUser_withInvalidTransectionId_returnErrorResponse() throws Exception { + AuthRequest authRequest = new AuthRequest(); + authRequest.setIndividualId("1234567890"); + + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setChallenge("1234567890"); + authChallenge.setAuthFactorType("PWD"); + authChallenge.setFormat("alpha-numeric"); + + List authChallengeList = new ArrayList<>(); + authChallengeList.add(authChallenge); + authRequest.setChallengeList(authChallengeList); + + RequestWrapper wrapper = new RequestWrapper<>(); + wrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + wrapper.setRequest(authRequest); + + + mockMvc.perform(post("/authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(wrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_TRANSACTION_ID)) + .andExpect(jsonPath("$.errors[0].errorMessage").value("request.transactionId: invalid_transaction_id")); + } + + @Test + public void authenticateEndUser_withInvalidAuthChallenge_returnErrorResponse() throws Exception { + AuthRequest authRequest = new AuthRequest(); + authRequest.setIndividualId("1234567890"); + authRequest.setTransactionId("1234567890"); + + + RequestWrapper wrapper = new RequestWrapper<>(); + wrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + wrapper.setRequest(authRequest); + + mockMvc.perform(post("/authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(wrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_CHALLENGE_LIST)) + .andExpect(jsonPath("$.errors[0].errorMessage").value("request.challengeList: invalid_no_of_challenges")); + } } diff --git a/esignet-service/src/test/java/io/mosip/esignet/controllers/LinkedAuthorizationControllerTest.java b/esignet-service/src/test/java/io/mosip/esignet/controllers/LinkedAuthorizationControllerTest.java index 9311350f3..f22c8fad8 100644 --- a/esignet-service/src/test/java/io/mosip/esignet/controllers/LinkedAuthorizationControllerTest.java +++ b/esignet-service/src/test/java/io/mosip/esignet/controllers/LinkedAuthorizationControllerTest.java @@ -49,9 +49,11 @@ import io.mosip.esignet.core.dto.LinkTransactionRequest; import io.mosip.esignet.core.dto.LinkTransactionResponse; import io.mosip.esignet.core.dto.LinkedConsentRequest; +import io.mosip.esignet.core.dto.LinkedConsentRequestV2; import io.mosip.esignet.core.dto.LinkedConsentResponse; import io.mosip.esignet.core.dto.LinkedKycAuthRequest; import io.mosip.esignet.core.dto.LinkedKycAuthResponse; +import io.mosip.esignet.core.dto.LinkedKycAuthResponseV2; import io.mosip.esignet.core.dto.OtpRequest; import io.mosip.esignet.core.dto.OtpResponse; import io.mosip.esignet.core.dto.RequestWrapper; @@ -565,4 +567,186 @@ public void getLinkAuthCode_withTimeout_thenFail() throws Exception { .andExpect(jsonPath("$.errors").isNotEmpty()) .andExpect(jsonPath("$.errors[0].errorCode").value(RESPONSE_TIMEOUT)); } + + @Test + public void authenticateV2_withValidRequest_thenPass() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("link-transaction-id"); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setFormat("format"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setChallenge("challenge"); + linkedKycAuthRequest.setChallengeList(Arrays.asList(authChallenge)); + linkedKycAuthRequest.setIndividualId("individualId"); + requestWrapper.setRequest(linkedKycAuthRequest); + + Mockito.when(linkedAuthorizationService.authenticateUserV2(Mockito.any(LinkedKycAuthRequest.class))).thenReturn(new LinkedKycAuthResponseV2()); + + mockMvc.perform(post("/linked-authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response").exists()) + .andExpect(jsonPath("$.errors").isEmpty()); + } + + @Test + public void authenticateV2_withException_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("link-transaction-id"); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setFormat("format"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setChallenge("challenge"); + linkedKycAuthRequest.setChallengeList(Arrays.asList(authChallenge)); + linkedKycAuthRequest.setIndividualId("individualId"); + requestWrapper.setRequest(linkedKycAuthRequest); + + Mockito.when(linkedAuthorizationService.authenticateUserV2(Mockito.any(LinkedKycAuthRequest.class))).thenThrow(EsignetException.class); + + mockMvc.perform(post("/linked-authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()); + } + + @Test + public void authenticateV2_withInvalidTransactionId_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId(" "); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setFormat("format"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setChallenge("challenge"); + linkedKycAuthRequest.setChallengeList(Arrays.asList(authChallenge)); + linkedKycAuthRequest.setIndividualId("individualId"); + requestWrapper.setRequest(linkedKycAuthRequest); + + mockMvc.perform(post("/linked-authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_TRANSACTION_ID)); + } + + @Test + public void authenticateV2_withInvalidIndividualId_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("txn-id"); + AuthChallenge authChallenge = new AuthChallenge(); + authChallenge.setFormat("format"); + authChallenge.setAuthFactorType("OTP"); + authChallenge.setChallenge("challenge"); + linkedKycAuthRequest.setChallengeList(Arrays.asList(authChallenge)); + linkedKycAuthRequest.setIndividualId(""); + requestWrapper.setRequest(linkedKycAuthRequest); + + mockMvc.perform(post("/linked-authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_IDENTIFIER)); + } + + @Test + public void authenticateV2_withInvalidChallengeList_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("txn-id"); + linkedKycAuthRequest.setIndividualId("individualId"); + requestWrapper.setRequest(linkedKycAuthRequest); + + mockMvc.perform(post("/linked-authorization/v2/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_CHALLENGE_LIST)); + + linkedKycAuthRequest.setChallengeList(new ArrayList<>()); + mockMvc.perform(post("/linked-authorization/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_CHALLENGE_LIST)); + + AuthChallenge authChallenge = new AuthChallenge(); + linkedKycAuthRequest.setChallengeList(Arrays.asList(authChallenge)); + MvcResult mvcResult = mockMvc.perform(post("/linked-authorization/authenticate") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + + List errorCodes = Arrays.asList(INVALID_AUTH_FACTOR_TYPE, INVALID_CHALLENGE, INVALID_CHALLENGE_FORMAT); + ResponseWrapper responseWrapper = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), ResponseWrapper.class); + Assert.assertTrue(responseWrapper.getErrors().size() == 3); + Assert.assertTrue(errorCodes.contains(((Error)responseWrapper.getErrors().get(0)).getErrorCode())); + Assert.assertTrue(errorCodes.contains(((Error)responseWrapper.getErrors().get(1)).getErrorCode())); + Assert.assertTrue(errorCodes.contains(((Error)responseWrapper.getErrors().get(2)).getErrorCode())); + } + + @Test + public void saveConsentV2_withValidRequest_thenPass() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedConsentRequestV2 linkedConsentRequestV2 = new LinkedConsentRequestV2(); + linkedConsentRequestV2.setLinkedTransactionId("link-transaction-id"); + requestWrapper.setRequest(linkedConsentRequestV2); + + LinkedConsentResponse linkedConsentResponse = new LinkedConsentResponse(); + Mockito.when(linkedAuthorizationService.saveConsentV2(Mockito.any(LinkedConsentRequestV2.class))).thenReturn(linkedConsentResponse); + + mockMvc.perform(post("/linked-authorization/v2/consent") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response").exists()) + .andExpect(jsonPath("$.errors").isEmpty()); + } + + @Test + public void saveConsentV2_withException_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedConsentRequestV2 linkedConsentRequestV2 = new LinkedConsentRequestV2(); + linkedConsentRequestV2.setLinkedTransactionId("link-transaction-id"); + requestWrapper.setRequest(linkedConsentRequestV2); + + Mockito.when(linkedAuthorizationService.saveConsentV2(Mockito.any(LinkedConsentRequestV2.class))).thenThrow(EsignetException.class); + + mockMvc.perform(post("/linked-authorization/v2/consent") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()); + } + + @Test + public void saveConsentV2_withInvalidTransactionId_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequestTime(IdentityProviderUtil.getUTCDateTime()); + LinkedConsentRequestV2 linkedConsentRequestV2 = new LinkedConsentRequestV2(); + linkedConsentRequestV2.setLinkedTransactionId(" "); + requestWrapper.setRequest(linkedConsentRequestV2); + + mockMvc.perform(post("/linked-authorization/v2/consent") + .content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(INVALID_TRANSACTION_ID)); + } } diff --git a/helm/delete-all.sh b/helm/delete-all.sh index 9e12423d1..9132d9551 100755 --- a/helm/delete-all.sh +++ b/helm/delete-all.sh @@ -7,19 +7,33 @@ if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -ROOT_DIR=`pwd` +function Deleting_All() { + ROOT_DIR=`pwd` + SOFTHSM_NS=softhsm -declare -a module=("redis" - "esignet" - "oidc-ui" - ) + helm -n $SOFTHSM_NS delete softhsm-esignet -echo Installing esignet services + declare -a module=("redis" + "esignet" + "oidc-ui" + ) -for i in "${module[@]}" -do - cd $ROOT_DIR/"$i" - ./delete.sh -done + echo Installing esignet services -echo All esignet services deleted sucessfully. + for i in "${module[@]}" + do + cd $ROOT_DIR/"$i" + ./delete.sh + done + + echo All esignet services deleted sucessfully. + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Deleting_All # calling function \ No newline at end of file diff --git a/helm/esignet/Chart.yaml b/helm/esignet/Chart.yaml index d82f2d013..37cb783b6 100644 --- a/helm/esignet/Chart.yaml +++ b/helm/esignet/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: esignet description: A Helm chart for MOSIP esignet module type: application -version: 0.0.1 +version: 1.0.1 appVersion: "" dependencies: - name: common diff --git a/helm/esignet/README.md b/helm/esignet/README.md index 729a382cd..4c232ac38 100644 --- a/helm/esignet/README.md +++ b/helm/esignet/README.md @@ -11,7 +11,7 @@ $ helm install my-release mosip/esignet ## Introduction -esignet is part of the esignet modules, but has a separate Helm chart so as to install and manage it in a completely indepedent namespace. +esignet is part of the esignet modules, but has a separate Helm chart so as to install and manage it in a completely independent namespace. ## Prerequisites @@ -23,8 +23,8 @@ esignet is part of the esignet modules, but has a separate Helm chart so as to i ## Overview Refer [Commons](https://docs.mosip.io/1.2.0/modules/commons). -## Initialize keycloak for IDP -* To initialize keycloak for IDP, run below script. +## Initialize keycloak for esignet +* To initialize keycloak for esignet, run below script. ```sh ./keycloak-init.sh ``` @@ -33,6 +33,7 @@ Refer [Commons](https://docs.mosip.io/1.2.0/modules/commons). ``` ./install.sh ``` + ## Uninstall ``` ./delete.sh diff --git a/helm/esignet/copy_cm.sh b/helm/esignet/copy_cm.sh index 363653ac5..0feece1ab 100755 --- a/helm/esignet/copy_cm.sh +++ b/helm/esignet/copy_cm.sh @@ -1,11 +1,22 @@ -#!/bin/sh +#!/bin/bash # Copy configmaps from other namespaces # DST_NS: Destination namespace -COPY_UTIL=../copy_cm_func.sh -DST_NS=esignet +function copying_cm() { + COPY_UTIL=../copy_cm_func.sh + DST_NS=esignet -$COPY_UTIL configmap global default $DST_NS -$COPY_UTIL configmap artifactory-share artifactory $DST_NS -$COPY_UTIL configmap config-server-share config-server $DST_NS -$COPY_UTIL configmap softhsm-esignet-share softhsm $DST_NS + $COPY_UTIL configmap global default $DST_NS + $COPY_UTIL configmap artifactory-share artifactory $DST_NS + $COPY_UTIL configmap config-server-share config-server $DST_NS + $COPY_UTIL configmap softhsm-esignet-share softhsm $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_cm # calling function \ No newline at end of file diff --git a/helm/esignet/copy_secrets.sh b/helm/esignet/copy_secrets.sh new file mode 100755 index 000000000..4f470a2e4 --- /dev/null +++ b/helm/esignet/copy_secrets.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copy secrets from other namespaces +# DST_NS: Destination namespace +function copying_secrets() { + COPY_UTIL=../esignet/copy_cm_func.sh + #DST_NS=esignet + $COPY_UTIL secret esignet-captcha esignet config-server + return 0 +} +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_secrets # calling function \ No newline at end of file diff --git a/helm/esignet/delete.sh b/helm/esignet/delete.sh index 6c3ffafda..817422423 100755 --- a/helm/esignet/delete.sh +++ b/helm/esignet/delete.sh @@ -1,18 +1,31 @@ -#!/bin/sh +#!/bin/bash # Uninstalls all esignet helm charts ## Usage: ./delete.sh [kubeconfig] if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -NS=esignet -while true; do - read -p "Are you sure you want to delete all esignet helm charts?(Y/n) " yn - if [ $yn = "Y" ] - then - helm -n $NS delete esignet - break - else - break - fi -done + +function Deleting_esignet() { + NS=esignet + while true; do + read -p "Are you sure you want to delete all esignet helm charts?(Y/n) " yn + if [ $yn = "Y" ] + then + helm -n $NS delete esignet + break + else + break + fi + done + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Deleting_esignet # calling function + diff --git a/helm/esignet/install.sh b/helm/esignet/install.sh index ec6318ee5..a6c3ca71e 100755 --- a/helm/esignet/install.sh +++ b/helm/esignet/install.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Installs all esignet helm charts ## Usage: ./install.sh [kubeconfig] @@ -7,16 +7,64 @@ if [ $# -ge 1 ] ; then fi NS=esignet -CHART_VERSION=0.0.1 +CHART_VERSION=1.0.1 -./keycloak-init.sh +ESIGNET_HOST=$(kubectl get cm global -o jsonpath={.data.mosip-esignet-host}) -echo Copy configmaps -./copy_cm.sh +echo Create $NS namespace +kubectl create ns $NS -echo Installing esignet -helm -n $NS install esignet . --version $CHART_VERSION +function installing_esignet() { + helm repo update -kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + ./keycloak-init.sh -echo Installed esignet service + echo Please enter the recaptcha admin site key for domain $ESIGNET_HOST + read ESITE_KEY + echo Please enter the recaptcha admin secret key for domain $ESIGNET_HOST + read ESECRET_KEY + + echo Setting up captcha secrets + kubectl -n $NS create secret generic esignet-captcha --from-literal=esignet-captcha-site-key=$ESITE_KEY --from-literal=esignet-captcha-secret-key=$ESECRET_KEY --dry-run=client -o yaml | kubectl apply -f - + + echo Copy configmaps + ./copy_cm.sh + + echo copy secrets + ./copy_secrets.sh + + kubectl -n config-server set env --keys=esignet-captcha-site-key --from secret/esignet-captcha deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ + kubectl -n config-server set env --keys=esignet-captcha-secret-key --from secret/esignet-captcha deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ + + kubectl -n config-server get deploy -o name | xargs -n1 -t kubectl -n config-server rollout status + + echo "Do you have public domain & valid SSL? (Y/n) " + echo "Y: if you have public domain & valid ssl certificate" + echo "n: If you don't have a public domain and a valid SSL certificate. Note: It is recommended to use this option only in development environments." + read -p "" flag + + if [ -z "$flag" ]; then + echo "'flag' was provided; EXITING;" + exit 1; + fi + ENABLE_INSECURE='' + if [ "$flag" = "n" ]; then + ENABLE_INSECURE='--set enable_insecure=true'; + fi + + echo Installing esignet + helm -n $NS install esignet mosip/esignet --version $CHART_VERSION $ENABLE_INSECURE + + kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + + echo Installed esignet service + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_esignet # calling function \ No newline at end of file diff --git a/helm/esignet/restart.sh b/helm/esignet/restart.sh index 2d60aa30b..a40af8bf7 100755 --- a/helm/esignet/restart.sh +++ b/helm/esignet/restart.sh @@ -1,13 +1,24 @@ -#!/bin/sh +#!/bin/bash # Restart the esignet services if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -NS=esignet -kubectl -n $NS rollout restart deploy esignet +function Restarting_esignet() { + NS=esignet + kubectl -n $NS rollout restart deploy esignet -kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status -echo Retarted esignet services + echo Retarted esignet services + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Restarting_esignet # calling function \ No newline at end of file diff --git a/helm/esignet/templates/deployment.yaml b/helm/esignet/templates/deployment.yaml index 9fef2a1c9..ca588c994 100644 --- a/helm/esignet/templates/deployment.yaml +++ b/helm/esignet/templates/deployment.yaml @@ -70,7 +70,7 @@ spec: - name: foo mountPath: bar {{- end }} - {{- if .Values.initContainers }} + {{- if .Values.enable_insecure }} {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }} {{- end }} containers: @@ -130,6 +130,17 @@ spec: {{- else if .Values.customReadinessProbe }} readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} {{- end }} + volumeMounts: + {{- if .Values.enable_insecure }} + - mountPath: /usr/local/openjdk-11/lib/security/cacerts + name: cacerts + subPath: cacerts + {{- end }} {{- if .Values.sidecars }} {{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }} {{- end }} + volumes: + {{- if .Values.enable_insecure }} + - name: cacerts + emptyDir: {} + {{- end }} diff --git a/helm/esignet/values.yaml b/helm/esignet/values.yaml index b6fa6351c..29213d153 100644 --- a/helm/esignet/values.yaml +++ b/helm/esignet/values.yaml @@ -52,8 +52,8 @@ service: image: registry: docker.io - repository: mosipdev/esignet - tag: develop + repository: mosipqa/esignet + tag: 1.0.0 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -272,7 +272,38 @@ extraVolumeMounts: [] ## - name: portname ## containerPort: 1234 ## -initContainers: {} +initContainers: + - command: + - /bin/bash + - -c + - if [ "$ENABLE_INSECURE" = "true" ]; then HOST=$( env | grep "mosip-api-internal-host" + |sed "s/mosip-api-internal-host=//g"); if [ -z "$HOST" ]; then echo "HOST + $HOST is empty; EXITING"; exit 1; fi; openssl s_client -servername "$HOST" + -connect "$HOST":443 > "$HOST.cer" 2>/dev/null & sleep 2 ; sed -i -ne '/-BEGIN + CERTIFICATE-/,/-END CERTIFICATE-/p' "$HOST.cer"; cat "$HOST.cer"; /usr/local/openjdk-11/bin/keytool + -delete -alias "$HOST" -keystore $JAVA_HOME/lib/security/cacerts -storepass + changeit; /usr/local/openjdk-11/bin/keytool -trustcacerts -keystore "$JAVA_HOME/lib/security/cacerts" + -storepass changeit -noprompt -importcert -alias "$HOST" -file "$HOST.cer" + ; if [ $? -gt 0 ]; then echo "Failed to add SSL certificate for host $host; + EXITING"; exit 1; fi; cp /usr/local/openjdk-11/lib/security/cacerts /cacerts; + fi + env: + - name: ENABLE_INSECURE + value: "true" + envFrom: + - configMapRef: + name: global + image: docker.io/openjdk:11-jre + imagePullPolicy: Always + name: cacerts + resources: {} + securityContext: + runAsUser: 0 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /cacerts + name: cacerts ## Add sidecars to the pods. ## Example: @@ -421,3 +452,5 @@ istio: - istio-system/public - istio-system/internal prefix: /v1/esignet/ + +enable_insecure: false diff --git a/helm/install-all.sh b/helm/install-all.sh index db1cf0627..17937a226 100755 --- a/helm/install-all.sh +++ b/helm/install-all.sh @@ -15,37 +15,49 @@ SOFTHSM_CHART_VERSION=12.0.2 echo Create $SOFTHSM_NS namespace kubectl create ns $SOFTHSM_NS -echo Istio label -kubectl label ns $SOFTHSM_NS istio-injection=enabled --overwrite -helm repo add mosip https://mosip.github.io/mosip-helm -helm repo update - -echo Installing Softhsm for esignet -helm -n $SOFTHSM_NS install softhsm-esignet mosip/softhsm -f softhsm-values.yaml --version $SOFTHSM_CHART_VERSION --wait -echo Installed Softhsm for esignet - -echo Copy configmaps -./copy_cm_func.sh configmap global default config-server - -echo Copy secrets -./copy_cm_func.sh secret softhsm-esignet softhsm config-server - -kubectl -n config-server set env --keys=mosip-esignet-host --from configmap/global deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ -kubectl -n config-server set env --keys=security-pin --from secret/softhsm-esignet deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_SOFTHSM_ESIGNET_ -kubectl -n config-server get deploy -o name | xargs -n1 -t kubectl -n config-server rollout status - - -declare -a module=("redis" - "esignet" - "oidc-ui" - ) - -echo Installing esignet services - -for i in "${module[@]}" -do - cd $ROOT_DIR/"$i" - ./install.sh -done - -echo All esignet services deployed sucessfully. +function installing_All() { + echo Istio label + kubectl label ns $SOFTHSM_NS istio-injection=enabled --overwrite + helm repo add mosip https://mosip.github.io/mosip-helm + helm repo update + + echo Installing Softhsm for esignet + helm -n $SOFTHSM_NS install softhsm-esignet mosip/softhsm -f softhsm-values.yaml --version $SOFTHSM_CHART_VERSION --wait + echo Installed Softhsm for esignet + + echo Copy configmaps + ./copy_cm_func.sh configmap global default config-server + + echo Copy secrets + ./copy_cm_func.sh secret softhsm-esignet softhsm config-server + + kubectl -n config-server set env --keys=mosip-esignet-host --from configmap/global deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ + kubectl -n config-server set env --keys=security-pin --from secret/softhsm-esignet deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_SOFTHSM_ESIGNET_ + kubectl -n config-server rollout restart deploy config-server + kubectl -n config-server get deploy -o name | xargs -n1 -t kubectl -n config-server rollout status + + + declare -a module=("redis" + "esignet" + "oidc-ui" + ) + + echo Installing esignet services + + for i in "${module[@]}" + do + cd $ROOT_DIR/"$i" + ./install.sh + done + + echo All esignet services deployed sucessfully. + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_All # calling function \ No newline at end of file diff --git a/helm/oidc-ui/Chart.yaml b/helm/oidc-ui/Chart.yaml index d8aa41cb5..9cf547c48 100644 --- a/helm/oidc-ui/Chart.yaml +++ b/helm/oidc-ui/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: oidc-ui description: A Helm chart for MOSIP OIDC UI module type: application -version: 12.0.2 +version: 1.0.1 appVersion: "" dependencies: - name: common diff --git a/helm/oidc-ui/copy_cm.sh b/helm/oidc-ui/copy_cm.sh index 0234aa6f1..462711fa8 100755 --- a/helm/oidc-ui/copy_cm.sh +++ b/helm/oidc-ui/copy_cm.sh @@ -1,11 +1,22 @@ -#!/bin/sh +#!/bin/bash # Copy configmaps from other namespaces # DST_NS: Destination namespace -COPY_UTIL=./copy_cm_func.sh -DST_NS=esignet +function copying_cm() { + COPY_UTIL=./copy_cm_func.sh + DST_NS=esignet -$COPY_UTIL configmap global default $DST_NS -$COPY_UTIL configmap artifactory-share artifactory $DST_NS -$COPY_UTIL configmap config-server-share config-server $DST_NS -$COPY_UTIL configmap softhsm-esignet-share softhsm $DST_NS + $COPY_UTIL configmap global default $DST_NS + $COPY_UTIL configmap artifactory-share artifactory $DST_NS + $COPY_UTIL configmap config-server-share config-server $DST_NS + $COPY_UTIL configmap softhsm-esignet-share softhsm $DST_NS + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +copying_cm # calling function \ No newline at end of file diff --git a/helm/oidc-ui/delete.sh b/helm/oidc-ui/delete.sh index 7b054a77f..10a831038 100755 --- a/helm/oidc-ui/delete.sh +++ b/helm/oidc-ui/delete.sh @@ -1,18 +1,30 @@ -#!/bin/sh +#!/bin/bash # Uninstalls oidc-ui helm charts ## Usage: ./delete.sh [kubeconfig] if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -NS=esignet -while true; do - read -p "Are you sure you want to delete all esignet helm charts?(Y/n) " yn - if [ $yn = "Y" ] - then - helm -n $NS delete oidc-ui - break - else - break - fi -done + +function Deleting_oidc-ui() { + NS=esignet + while true; do + read -p "Are you sure you want to delete all esignet helm charts?(Y/n) " yn + if [ $yn = "Y" ] + then + helm -n $NS delete oidc-ui + break + else + break + fi + done + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Deleting_oidc-ui # calling function \ No newline at end of file diff --git a/helm/oidc-ui/install.sh b/helm/oidc-ui/install.sh index b0cdf4d7c..568cbadca 100755 --- a/helm/oidc-ui/install.sh +++ b/helm/oidc-ui/install.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Installs oidc-ui helm charts ## Usage: ./install.sh [kubeconfig] @@ -7,27 +7,40 @@ if [ $# -ge 1 ] ; then fi NS=esignet -CHART_VERSION=0.0.1 +CHART_VERSION=1.0.1 echo Create $NS namespace kubectl create ns $NS -echo Istio label -kubectl label ns $NS istio-injection=enabled --overwrite -helm dependency build +function installing_oidc-ui() { + echo Istio label + kubectl label ns $NS istio-injection=enabled --overwrite -echo Copy configmaps -./copy_cm.sh + helm repo add mosip https://mosip.github.io/mosip-helm + helm repo update -ESIGNET_HOST=$(kubectl get cm global -o jsonpath={.data.mosip-esignet-host}) + echo Copy configmaps + ./copy_cm.sh -echo "Create configmaps oidc-ui-cm, delete if exists" -kubectl -n $NS delete --ignore-not-found=true configmap oidc-ui-cm -kubectl -n $NS create configmap oidc-ui-cm --from-literal="REACT_APP_API_BASE_URL=http://esignet.$NS/v1/esignet" --from-literal="REACT_APP_SBI_DOMAIN_URI=http://esignet.$NS" + ESIGNET_HOST=$(kubectl get cm global -o jsonpath={.data.mosip-esignet-host}) -echo Installing OIDC UI -helm -n $NS install oidc-ui . --set istio.hosts\[0\]=$ESIGNET_HOST + echo "Create configmaps oidc-ui-cm, delete if exists" + kubectl -n $NS delete --ignore-not-found=true configmap oidc-ui-cm + kubectl -n $NS create configmap oidc-ui-cm --from-literal="REACT_APP_API_BASE_URL=http://esignet.$NS/v1/esignet" --from-literal="REACT_APP_SBI_DOMAIN_URI=http://esignet.$NS" -kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + echo Installing OIDC UI + helm -n $NS install oidc-ui mosip/oidc-ui --set istio.hosts\[0\]=$ESIGNET_HOST -echo Installed oidc-ui + kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + + echo Installed oidc-ui + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_oidc-ui # calling function \ No newline at end of file diff --git a/helm/oidc-ui/restart.sh b/helm/oidc-ui/restart.sh index 3b47bc1fe..706a2a4c3 100755 --- a/helm/oidc-ui/restart.sh +++ b/helm/oidc-ui/restart.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Restart the oidc-ui services @@ -6,9 +6,20 @@ if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -NS=esignet -kubectl -n $NS rollout restart deploy oidc-ui +function Restarting_oidc-ui() { + NS=esignet + kubectl -n $NS rollout restart deploy oidc-ui -kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status + kubectl -n $NS get deploy -o name | xargs -n1 -t kubectl -n $NS rollout status -echo Retarted esignet services + echo Retarted esignet services + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Restarting_oidc-ui # calling function \ No newline at end of file diff --git a/helm/oidc-ui/values.yaml b/helm/oidc-ui/values.yaml index d6c472ea4..cd4b92633 100755 --- a/helm/oidc-ui/values.yaml +++ b/helm/oidc-ui/values.yaml @@ -51,8 +51,8 @@ service: image: registry: docker.io - repository: mosipdev/oidc-ui - tag: MOSIP-25524 + repository: mosipqa/oidc-ui + tag: 1.0.0 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' diff --git a/helm/redis/delete.sh b/helm/redis/delete.sh index cfdad0b1b..ba3679e18 100755 --- a/helm/redis/delete.sh +++ b/helm/redis/delete.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Uninstalls kafka ## Usage: ./delete.sh [kubeconfig] @@ -6,16 +6,26 @@ if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -NS=redis -while true; do - read -p "Are you sure you want to delete redis helm chart? Y/n ?" yn - if [ $yn = "Y" ] - then - helm -n $NS delete redis - echo Deleted Redis services. - break - else - break - fi -done +function Deleting_redis() { + NS=redis + while true; do + read -p "Are you sure you want to delete redis helm chart? Y/n ?" yn + if [ $yn = "Y" ] + then + helm -n $NS delete redis + echo Deleted Redis services. + break + else + break + fi + done + return 0 +} +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Deleting_redis # calling function diff --git a/helm/redis/install.sh b/helm/redis/install.sh index 75675e755..1f557ad9c 100755 --- a/helm/redis/install.sh +++ b/helm/redis/install.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Installs redis ## Usage: ./install.sh [kubeconfig] @@ -12,18 +12,29 @@ CHART_VERSION=17.3.14 echo Create $NS namespace kubectl create ns $NS -echo Istio label -kubectl label ns $NS istio-injection=enabled --overwrite +function installing_redis() { + echo Istio label + kubectl label ns $NS istio-injection=enabled --overwrite -echo Updating helm repos -helm repo add bitnami https://charts.bitnami.com/bitnami -helm repo update + echo Updating helm repos + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo update -echo Installing redis -helm -n $NS install redis bitnami/redis --wait --version $CHART_VERSION + echo Installing redis + helm -n $NS install redis bitnami/redis --wait --version $CHART_VERSION -./copy_cm_func.sh secret redis redis config-server + ../copy_cm_func.sh secret redis redis config-server -kubectl -n config-server set env --keys=redis-password --from secret/redis deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ + kubectl -n config-server set env --keys=redis-password --from secret/redis deployment/config-server --prefix=SPRING_CLOUD_CONFIG_SERVER_OVERRIDES_ -echo Installed redis service + echo Installed redis service + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +installing_redis # calling function \ No newline at end of file diff --git a/helm/restart-all.sh b/helm/restart-all.sh index bc62fb6ba..5bb0a8701 100755 --- a/helm/restart-all.sh +++ b/helm/restart-all.sh @@ -7,19 +7,30 @@ if [ $# -ge 1 ] ; then export KUBECONFIG=$1 fi -ROOT_DIR=`pwd` +function Restarting_All() { + ROOT_DIR=`pwd` -declare -a module=("redis" - "esignet" - "oidc-ui" - ) + declare -a module=("redis" + "esignet" + "oidc-ui" + ) -echo restarting esignet services + echo restarting esignet services -for i in "${module[@]}" -do - cd $ROOT_DIR/"$i" - ./restart.sh -done + for i in "${module[@]}" + do + cd $ROOT_DIR/"$i" + ./restart.sh + done -echo All esignet services restarted sucessfully. + echo All esignet services restarted sucessfully. + return 0 +} + +# set commands for error handling. +set -e +set -o errexit ## set -e : exit the script if any statement returns a non-true return value +set -o nounset ## set -u : exit the script if you try to use an uninitialised variable +set -o errtrace # trace ERR through 'time command' and other functions +set -o pipefail # trace ERR through pipes +Restarting_All # calling function \ No newline at end of file diff --git a/oidc-service-impl/pom.xml b/oidc-service-impl/pom.xml index d7d617e58..38ae7f778 100644 --- a/oidc-service-impl/pom.xml +++ b/oidc-service-impl/pom.xml @@ -6,7 +6,7 @@ io.mosip.esignet esignet-parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT io.mosip.esignet diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java index 9e14e7c5d..74993b7c9 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationHelperService.java @@ -189,18 +189,38 @@ protected KycAuthResult delegateAuthenticateRequest(String transactionId, String return kycAuthResult; } + /** + * This method is used to validate the requested claims against the accepted claims + *
    + *
  • Checks Performed
  • + *
      + *
    • accepted Claims should be subset of requested claims
    • + *
    • essential Claims should be a subset of accepted claims
    • + *
    + *
+ * + * @param transaction object containg OIDC transaction details + * @param acceptedClaims list of accepted claims + * @throws EsignetException + * + */ protected void validateAcceptedClaims(OIDCTransaction transaction, List acceptedClaims) throws EsignetException { - if(CollectionUtils.isEmpty(acceptedClaims)) - return; + Map userinfo = Optional.ofNullable(transaction.getRequestedClaims()) + .map(Claims::getUserinfo) + .orElse(Collections.emptyMap()); - if(CollectionUtils.isEmpty(transaction.getRequestedClaims().getUserinfo())) - throw new EsignetException(INVALID_ACCEPTED_CLAIM); + List essentialClaims = userinfo.entrySet().stream() + .filter(e -> Optional.ofNullable(e.getValue()).map(ClaimDetail::isEssential).orElse(false)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); - if(acceptedClaims.stream() - .allMatch( claim -> transaction.getRequestedClaims().getUserinfo().containsKey(claim) )) - return; + Set allRequestedClaims = userinfo.keySet(); + Set acceptedClaimsSet = new HashSet<>(Optional.ofNullable(acceptedClaims).orElse(Collections.emptyList())); - throw new EsignetException(INVALID_ACCEPTED_CLAIM); + if (essentialClaims.stream().anyMatch(c -> !acceptedClaimsSet.contains(c)) + || !allRequestedClaims.containsAll(acceptedClaimsSet)) { + throw new EsignetException(INVALID_ACCEPTED_CLAIM); + } } protected void validateAuthorizeScopes(OIDCTransaction transaction, List authorizeScopes) throws EsignetException { diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java index 6325f3599..3cee6f367 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/AuthorizationServiceImpl.java @@ -14,6 +14,7 @@ import io.mosip.esignet.api.spi.Authenticator; import io.mosip.esignet.api.util.Action; import io.mosip.esignet.api.util.ActionStatus; +import io.mosip.esignet.api.util.ConsentAction; import io.mosip.esignet.core.constants.Constants; import io.mosip.esignet.core.constants.ErrorConstants; import io.mosip.esignet.core.dto.*; @@ -21,7 +22,10 @@ import io.mosip.esignet.core.exception.InvalidTransactionException; import io.mosip.esignet.core.spi.AuthorizationService; import io.mosip.esignet.core.spi.ClientManagementService; -import io.mosip.esignet.core.util.*; +import io.mosip.esignet.core.util.AuditHelper; +import io.mosip.esignet.core.util.AuthenticationContextClassRefUtil; +import io.mosip.esignet.core.util.IdentityProviderUtil; +import io.mosip.esignet.core.util.LinkCodeQueue; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -30,8 +34,8 @@ import java.util.*; import java.util.stream.Collectors; -import static io.mosip.esignet.core.spi.TokenService.ACR; import static io.mosip.esignet.core.constants.Constants.*; +import static io.mosip.esignet.core.spi.TokenService.ACR; import static io.mosip.esignet.core.util.IdentityProviderUtil.ALGO_SHA3_256; import static io.mosip.esignet.core.util.IdentityProviderUtil.ALGO_SHA_256; @@ -60,6 +64,9 @@ public class AuthorizationServiceImpl implements AuthorizationService { @Autowired private ObjectMapper objectMapper; + @Autowired + ConsentHelperService consentHelperService; + @Value("#{${mosip.esignet.ui.config.key-values}}") private Map uiConfigMap; @@ -92,7 +99,7 @@ public OAuthDetailResponse getOauthDetails(OAuthDetailRequest oauthDetailReqDto) OAuthDetailResponse oauthDetailResponse = new OAuthDetailResponse(); oauthDetailResponse.setTransactionId(transactionId); oauthDetailResponse.setAuthFactors(authenticationContextClassRefUtil.getAuthFactors( - resolvedClaims.getId_token().get(ACR).getValues() + resolvedClaims.getId_token().get(ACR).getValues() )); Map claimsMap = authorizationHelperService.getClaimNames(resolvedClaims); @@ -106,6 +113,9 @@ public OAuthDetailResponse getOauthDetails(OAuthDetailRequest oauthDetailReqDto) //Cache the transaction OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setTransactionId(transactionId); + oidcTransaction.setEssentialClaims(oauthDetailResponse.getEssentialClaims()); + oidcTransaction.setVoluntaryClaims(oauthDetailResponse.getVoluntaryClaims()); oidcTransaction.setRedirectUri(oauthDetailReqDto.getRedirectUri()); oidcTransaction.setRelyingPartyId(clientDetailDto.getRpId()); oidcTransaction.setClientId(clientDetailDto.getId()); @@ -156,11 +166,10 @@ public AuthResponse authenticateUser(AuthRequest authRequest) throws EsignetExc transaction.setKycToken(kycAuthResult.getKycToken()); transaction.setAuthTimeInSeconds(IdentityProviderUtil.getEpochSeconds()); transaction.setProvidedAuthFactors(providedAuthFactors.stream().map(acrFactors -> acrFactors.stream() - .map(AuthenticationFactor::getType) - .collect(Collectors.toList())).collect(Collectors.toSet())); + .map(AuthenticationFactor::getType) + .collect(Collectors.toList())).collect(Collectors.toSet())); authorizationHelperService.setIndividualId(authRequest.getIndividualId(), transaction); cacheUtilService.setAuthenticatedTransaction(authRequest.getTransactionId(), transaction); - auditWrapper.logAudit(Action.AUTHENTICATE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(authRequest.getTransactionId(), transaction), null); AuthResponse authRespDto = new AuthResponse(); @@ -168,23 +177,57 @@ public AuthResponse authenticateUser(AuthRequest authRequest) throws EsignetExc return authRespDto; } + @Override + public AuthResponseV2 authenticateUserV2(AuthRequest authRequest) throws EsignetException { + OIDCTransaction transaction = cacheUtilService.getPreAuthTransaction(authRequest.getTransactionId()); + if(transaction == null) + throw new InvalidTransactionException(); + + //Validate provided challenge list auth-factors with resolved auth-factors for the transaction. + Set> providedAuthFactors = authorizationHelperService.getProvidedAuthFactors(transaction, + authRequest.getChallengeList()); + KycAuthResult kycAuthResult = authorizationHelperService.delegateAuthenticateRequest(authRequest.getTransactionId(), + authRequest.getIndividualId(), authRequest.getChallengeList(), transaction); + //cache tokens on successful response + transaction.setPartnerSpecificUserToken(kycAuthResult.getPartnerSpecificUserToken()); + transaction.setKycToken(kycAuthResult.getKycToken()); + transaction.setAuthTimeInSeconds(IdentityProviderUtil.getEpochSeconds()); + transaction.setProvidedAuthFactors(providedAuthFactors.stream().map(acrFactors -> acrFactors.stream() + .map(AuthenticationFactor::getType) + .collect(Collectors.toList())).collect(Collectors.toSet())); + authorizationHelperService.setIndividualId(authRequest.getIndividualId(), transaction); + consentHelperService.processConsent(transaction, false); + cacheUtilService.setAuthenticatedTransaction(authRequest.getTransactionId(), transaction); + auditWrapper.logAudit(Action.AUTHENTICATE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(authRequest.getTransactionId(), transaction), null); + + AuthResponseV2 authRespDto = new AuthResponseV2(); + authRespDto.setTransactionId(authRequest.getTransactionId()); + authRespDto.setConsentAction(transaction.getConsentAction()); + return authRespDto; + } + @Override public AuthCodeResponse getAuthCode(AuthCodeRequest authCodeRequest) throws EsignetException { OIDCTransaction transaction = cacheUtilService.getAuthenticatedTransaction(authCodeRequest.getTransactionId()); if(transaction == null) { throw new InvalidTransactionException(); } - - authorizationHelperService.validateAcceptedClaims(transaction, authCodeRequest.getAcceptedClaims()); - authorizationHelperService.validateAuthorizeScopes(transaction, authCodeRequest.getPermittedAuthorizeScopes()); + List acceptedClaims = authCodeRequest.getAcceptedClaims(); + List acceptedScopes = authCodeRequest.getPermittedAuthorizeScopes(); + if(ConsentAction.NOCAPTURE.equals(transaction.getConsentAction())) { + acceptedClaims = transaction.getAcceptedClaims(); + acceptedScopes = transaction.getPermittedScopes(); + } + authorizationHelperService.validateAcceptedClaims(transaction, acceptedClaims); + authorizationHelperService.validateAuthorizeScopes(transaction, acceptedScopes); String authCode = IdentityProviderUtil.generateB64EncodedHash(ALGO_SHA3_256, UUID.randomUUID().toString()); // cache consent with auth-code-hash as key transaction.setCodeHash(authorizationHelperService.getKeyHash(authCode)); - transaction.setAcceptedClaims(authCodeRequest.getAcceptedClaims()); - transaction.setPermittedScopes(authCodeRequest.getPermittedAuthorizeScopes()); + transaction.setAcceptedClaims(acceptedClaims); + transaction.setPermittedScopes(acceptedScopes); + consentHelperService.updateUserConsent(transaction, false, null); transaction = cacheUtilService.setAuthCodeGeneratedTransaction(authCodeRequest.getTransactionId(), transaction); - auditWrapper.logAudit(Action.GET_AUTH_CODE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(authCodeRequest.getTransactionId(), transaction), null); AuthCodeResponse authCodeResponse = new AuthCodeResponse(); diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/ConsentHelperService.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/ConsentHelperService.java new file mode 100644 index 000000000..a4636a87f --- /dev/null +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/ConsentHelperService.java @@ -0,0 +1,194 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.mosip.esignet.api.dto.ClaimDetail; +import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.api.spi.AuditPlugin; +import io.mosip.esignet.api.util.Action; +import io.mosip.esignet.api.util.ActionStatus; +import io.mosip.esignet.api.util.ConsentAction; +import io.mosip.esignet.core.constants.ErrorConstants; +import io.mosip.esignet.core.dto.ConsentDetail; +import io.mosip.esignet.core.dto.OIDCTransaction; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.core.dto.UserConsentRequest; +import io.mosip.esignet.core.exception.EsignetException; +import io.mosip.esignet.core.spi.ConsentService; +import io.mosip.esignet.core.util.AuditHelper; +import io.mosip.esignet.core.util.IdentityProviderUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static io.mosip.esignet.core.util.IdentityProviderUtil.ALGO_SHA3_256; + +@Slf4j +@Component +public class ConsentHelperService { + @Autowired + private ConsentService consentService; + + @Autowired + private AuditPlugin auditWrapper; + + public void processConsent(OIDCTransaction transaction, boolean linked) { + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId(transaction.getClientId()); + userConsentRequest.setPsuToken(transaction.getPartnerSpecificUserToken()); + Optional consent = consentService.getUserConsent(userConsentRequest); + + if(CollectionUtils.isEmpty(transaction.getVoluntaryClaims()) + && CollectionUtils.isEmpty(transaction.getEssentialClaims()) + && CollectionUtils.isEmpty(transaction.getRequestedAuthorizeScopes())){ + transaction.setConsentAction(ConsentAction.NOCAPTURE); + transaction.setAcceptedClaims(List.of()); + transaction.setPermittedScopes(List.of()); + } else { + ConsentAction consentAction = consent.isEmpty() ? ConsentAction.CAPTURE : evaluateConsentAction(transaction, consent.get(), linked); + + transaction.setConsentAction(consentAction); + + if (consentAction.equals(ConsentAction.NOCAPTURE)) { + transaction.setAcceptedClaims(consent.get().getAcceptedClaims()); //NOSONAR consent is already evaluated to be not null + transaction.setPermittedScopes(consent.get().getPermittedScopes()); //NOSONAR consent is already evaluated to be not null + } + } + } + + + public void updateUserConsent(OIDCTransaction transaction, boolean linked, String signature) { + if(ConsentAction.NOCAPTURE.equals(transaction.getConsentAction()) + && transaction.getEssentialClaims().isEmpty() + && transaction.getVoluntaryClaims().isEmpty() + && transaction.getRequestedAuthorizeScopes().isEmpty() + ){ + //delete old consent if it exists since this scenario doesn't require capture of consent. + consentService.deleteUserConsent(transaction.getClientId(),transaction.getPartnerSpecificUserToken()); + auditWrapper.logAudit(Action.DELETE_USER_CONSENT, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(),transaction),null); + } + if(ConsentAction.CAPTURE.equals(transaction.getConsentAction())){ + UserConsent userConsent = new UserConsent(); + userConsent.setClientId(transaction.getClientId()); + userConsent.setPsuToken(transaction.getPartnerSpecificUserToken()); + Claims claims = transaction.getRequestedClaims(); + List acceptedClaims = transaction.getAcceptedClaims(); + Claims normalizedClaims = new Claims(); + normalizedClaims.setUserinfo(normalizeClaims(claims.getUserinfo())); + normalizedClaims.setId_token(normalizeClaims(claims.getId_token())); + + userConsent.setClaims(normalizedClaims); + userConsent.setSignature(signature); + List permittedScopes = transaction.getPermittedScopes(); + List requestedAuthorizeScopes = transaction.getRequestedAuthorizeScopes(); + // defaulting the essential boolean flag as false + Map authorizeScopes = requestedAuthorizeScopes != null ? requestedAuthorizeScopes.stream() + .collect(Collectors.toMap(Function.identity(), s->false)) : Collections.emptyMap(); + userConsent.setAuthorizationScopes(authorizeScopes); + userConsent.setAcceptedClaims(acceptedClaims); + userConsent.setPermittedScopes(permittedScopes); + try { + userConsent.setHash(hashUserConsent(normalizedClaims, authorizeScopes)); + } catch (JsonProcessingException e) { + log.error("Failed to hash the user consent", e); + throw new EsignetException(ErrorConstants.INVALID_CLAIM); + } + consentService.saveUserConsent(userConsent); + auditWrapper.logAudit(Action.UPDATE_USER_CONSENT, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(),transaction),null); + } + } + + + public Map normalizeClaims(Map claims){ + return claims.entrySet().stream().collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> { + ClaimDetail claimDetail = entry.getValue(); + return claimDetail == null ? new ClaimDetail() : claimDetail; + } + ) + ); + } + + public String hashUserConsent(Claims claims,Map authorizeScopes) throws JsonProcessingException { + + Map claimsAndAuthorizeScopes = new LinkedHashMap<>(); + + List> entryList; + Map sortedMap = new LinkedHashMap<>(); + if(claims.getUserinfo()!=null){ + entryList = new ArrayList<>(claims.getUserinfo().entrySet()); + sortClaims(entryList, sortedMap); + + } + //Now for sorting id_token + if(claims.getId_token()!=null) + { + entryList = new ArrayList<>(claims.getId_token().entrySet()); + sortClaims(entryList, sortedMap); + + } + //Now for authorizeScopes + Map sortedAuthorzeScopeMap=new LinkedHashMap<>(); + + List>authorizeScopesList = new ArrayList<>(authorizeScopes.entrySet()); + authorizeScopesList.sort(new Comparator>() { + public int compare(Map.Entry o1, Map.Entry o2) { + return o1.getKey().compareTo(o2.getKey()); + } + }); + for (Map.Entry entry : authorizeScopesList) { + sortedAuthorzeScopeMap.put(entry.getKey(), entry.getValue()); + } + claimsAndAuthorizeScopes.put("claims",sortedMap); + claimsAndAuthorizeScopes.put("authorizeScopes",sortedAuthorzeScopeMap); + String s=claimsAndAuthorizeScopes.toString().trim().replace(" ",""); + return IdentityProviderUtil.generateB64EncodedHash(ALGO_SHA3_256, s); + } + + private static void sortClaims(List> entryList, Map sortedMap) { + entryList.sort(new Comparator<>() { + public int compare(Map.Entry o1, Map.Entry o2) { + return o1.getKey().compareTo(o2.getKey()); + } + }); + for (Map.Entry entry : entryList) { + sortedMap.put(entry.getKey(), sortClaimDetail(entry.getValue())); + } + } + + private static ClaimDetail sortClaimDetail(ClaimDetail claimDetail){ + if(claimDetail!= null && claimDetail.getValues() != null) { + Arrays.sort(claimDetail.getValues()); + } + return claimDetail; + } + + private ConsentAction evaluateConsentAction(OIDCTransaction transaction, ConsentDetail consentDetail, boolean linked) { + String hash; + try { + List authorizeScope = transaction.getRequestedAuthorizeScopes(); + // defaulting the essential boolean flag as false + Map authorizeScopes = authorizeScope != null ? authorizeScope.stream() + .collect(Collectors.toMap(Function.identity(), s->false)) : Collections.emptyMap(); + Claims normalizedClaims = new Claims(); + normalizedClaims.setUserinfo(normalizeClaims(transaction.getRequestedClaims().getUserinfo())); + normalizedClaims.setId_token(normalizeClaims(transaction.getRequestedClaims().getId_token())); + hash = hashUserConsent(normalizedClaims, authorizeScopes); + } catch (JsonProcessingException e) { + log.error("Failed to hash the user consent", e); + throw new EsignetException(ErrorConstants.INVALID_CLAIM); + } + return consentDetail.getHash().equals(hash) ? ConsentAction.NOCAPTURE : ConsentAction.CAPTURE; + } +} \ No newline at end of file diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/LinkedAuthorizationServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/LinkedAuthorizationServiceImpl.java index ed0e4ff32..e445a1326 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/LinkedAuthorizationServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/LinkedAuthorizationServiceImpl.java @@ -5,49 +5,14 @@ */ package io.mosip.esignet.services; -import static io.mosip.esignet.core.constants.Constants.ESSENTIAL; -import static io.mosip.esignet.core.constants.Constants.LINKED_STATUS; -import static io.mosip.esignet.core.constants.Constants.UTC_DATETIME_PATTERN; -import static io.mosip.esignet.core.constants.Constants.VOLUNTARY; -import static io.mosip.esignet.core.spi.TokenService.ACR; - -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.web.context.request.async.DeferredResult; - import io.mosip.esignet.api.dto.KycAuthResult; import io.mosip.esignet.api.dto.SendOtpResult; import io.mosip.esignet.api.spi.AuditPlugin; import io.mosip.esignet.api.util.Action; import io.mosip.esignet.api.util.ActionStatus; +import io.mosip.esignet.api.util.ConsentAction; import io.mosip.esignet.core.constants.ErrorConstants; -import io.mosip.esignet.core.dto.AuthenticationFactor; -import io.mosip.esignet.core.dto.ClientDetail; -import io.mosip.esignet.core.dto.LinkAuthCodeRequest; -import io.mosip.esignet.core.dto.LinkCodeRequest; -import io.mosip.esignet.core.dto.LinkCodeResponse; -import io.mosip.esignet.core.dto.LinkStatusRequest; -import io.mosip.esignet.core.dto.LinkTransactionMetadata; -import io.mosip.esignet.core.dto.LinkTransactionRequest; -import io.mosip.esignet.core.dto.LinkTransactionResponse; -import io.mosip.esignet.core.dto.LinkedConsentRequest; -import io.mosip.esignet.core.dto.LinkedConsentResponse; -import io.mosip.esignet.core.dto.LinkedKycAuthRequest; -import io.mosip.esignet.core.dto.LinkedKycAuthResponse; -import io.mosip.esignet.core.dto.OIDCTransaction; -import io.mosip.esignet.core.dto.OtpRequest; -import io.mosip.esignet.core.dto.OtpResponse; +import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.exception.DuplicateLinkCodeException; import io.mosip.esignet.core.exception.EsignetException; import io.mosip.esignet.core.exception.InvalidTransactionException; @@ -58,6 +23,23 @@ import io.mosip.esignet.core.util.IdentityProviderUtil; import io.mosip.esignet.core.util.KafkaHelperService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.async.DeferredResult; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static io.mosip.esignet.core.constants.Constants.*; +import static io.mosip.esignet.core.spi.TokenService.ACR; @Slf4j @Service @@ -77,10 +59,13 @@ public class LinkedAuthorizationServiceImpl implements LinkedAuthorizationServic @Autowired private KafkaHelperService kafkaHelperService; - + @Autowired private AuditPlugin auditWrapper; + @Autowired + private ConsentHelperService consentHelperService; + @Value("${mosip.esignet.link-code-expire-in-secs}") private int linkCodeExpiryInSeconds; @@ -135,8 +120,8 @@ public LinkCodeResponse generateLinkCode(LinkCodeRequest linkCodeRequest) throws linkCodeResponse.setLinkCode(linkCode); linkCodeResponse.setTransactionId(linkCodeRequest.getTransactionId()); linkCodeResponse.setExpireDateTime(expireDateTime == null ? null : - expireDateTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); - auditWrapper.logAudit(Action.LINK_CODE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(linkCodeRequest.getTransactionId(), transaction), null); + expireDateTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); + auditWrapper.logAudit(Action.LINK_CODE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return linkCodeResponse; } @@ -176,7 +161,7 @@ public LinkTransactionResponse linkTransaction(LinkTransactionRequest linkTransa //Publish message after successfully linking the transaction kafkaHelperService.publish(linkedSessionTopicName, linkCodeHash); - auditWrapper.logAudit(Action.LINK_TRANSACTION, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(linkTransactionMetadata.getTransactionId(), transaction), null); + auditWrapper.logAudit(Action.LINK_TRANSACTION, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return linkTransactionResponse; } @@ -191,7 +176,7 @@ public OtpResponse sendOtp(OtpRequest otpRequest) throws EsignetException { otpResponse.setTransactionId(otpRequest.getTransactionId()); otpResponse.setMaskedEmail(sendOtpResult.getMaskedEmail()); otpResponse.setMaskedMobile(sendOtpResult.getMaskedMobile()); - auditWrapper.logAudit(Action.LINK_SEND_OTP, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(otpRequest.getTransactionId(), transaction), null); + auditWrapper.logAudit(Action.LINK_SEND_OTP, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return otpResponse; } @@ -200,7 +185,6 @@ public LinkedKycAuthResponse authenticateUser(LinkedKycAuthRequest linkedKycAuth OIDCTransaction transaction = cacheUtilService.getLinkedSessionTransaction(linkedKycAuthRequest.getLinkedTransactionId()); if(transaction == null) throw new InvalidTransactionException(); - //Validate provided challenge list auth-factors with resolved auth-factors for the transaction. Set> providedAuthFactors = authorizationHelperService.getProvidedAuthFactors(transaction, linkedKycAuthRequest.getChallengeList()); KycAuthResult kycAuthResult = authorizationHelperService.delegateAuthenticateRequest(linkedKycAuthRequest.getLinkedTransactionId(), @@ -214,10 +198,42 @@ public LinkedKycAuthResponse authenticateUser(LinkedKycAuthRequest linkedKycAuth .map(AuthenticationFactor::getType) .collect(Collectors.toList())).collect(Collectors.toSet())); cacheUtilService.setLinkedAuthenticatedTransaction(linkedKycAuthRequest.getLinkedTransactionId(), transaction); - LinkedKycAuthResponse authRespDto = new LinkedKycAuthResponse(); authRespDto.setLinkedTransactionId(linkedKycAuthRequest.getLinkedTransactionId()); - auditWrapper.logAudit(Action.LINK_AUTHENTICATE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(null, transaction), null); + auditWrapper.logAudit(Action.LINK_AUTHENTICATE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); + return authRespDto; + } + + @Override + public LinkedKycAuthResponseV2 authenticateUserV2(LinkedKycAuthRequest linkedKycAuthRequest) throws EsignetException { + OIDCTransaction transaction = cacheUtilService.getLinkedSessionTransaction(linkedKycAuthRequest.getLinkedTransactionId()); + if(transaction == null) + throw new InvalidTransactionException(); + //Validate provided challenge list auth-factors with resolved auth-factors for the transaction. + Set> providedAuthFactors = authorizationHelperService.getProvidedAuthFactors(transaction, linkedKycAuthRequest.getChallengeList()); + KycAuthResult kycAuthResult = authorizationHelperService.delegateAuthenticateRequest(linkedKycAuthRequest.getLinkedTransactionId(), + linkedKycAuthRequest.getIndividualId(), linkedKycAuthRequest.getChallengeList(), transaction); + //cache tokens on successful response + transaction.setPartnerSpecificUserToken(kycAuthResult.getPartnerSpecificUserToken()); + transaction.setKycToken(kycAuthResult.getKycToken()); + transaction.setAuthTimeInSeconds(IdentityProviderUtil.getEpochSeconds()); + authorizationHelperService.setIndividualId(linkedKycAuthRequest.getIndividualId(), transaction); + consentHelperService.processConsent(transaction, true); + transaction.setProvidedAuthFactors(providedAuthFactors.stream().map(acrFactors -> acrFactors.stream() + .map(AuthenticationFactor::getType) + .collect(Collectors.toList())).collect(Collectors.toSet())); + if(ConsentAction.NOCAPTURE.equals(transaction.getConsentAction())){ + validateConsent(transaction, transaction.getAcceptedClaims(), transaction.getPermittedScopes()); + cacheUtilService.setLinkedConsentedTransaction(transaction.getLinkedTransactionId(), transaction); + consentHelperService.updateUserConsent(transaction,true, ""); + kafkaHelperService.publish(linkedAuthCodeTopicName, transaction.getLinkedTransactionId()); + } else { + cacheUtilService.setLinkedAuthenticatedTransaction(linkedKycAuthRequest.getLinkedTransactionId(), transaction); + } + LinkedKycAuthResponseV2 authRespDto = new LinkedKycAuthResponseV2(); + authRespDto.setLinkedTransactionId(linkedKycAuthRequest.getLinkedTransactionId()); + authRespDto.setConsentAction(transaction.getConsentAction()); + auditWrapper.logAudit(Action.LINK_AUTHENTICATE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return authRespDto; } @@ -227,9 +243,7 @@ public LinkedConsentResponse saveConsent(LinkedConsentRequest linkedConsentReque if(transaction == null) { throw new InvalidTransactionException(); } - - authorizationHelperService.validateAcceptedClaims(transaction, linkedConsentRequest.getAcceptedClaims()); - authorizationHelperService.validateAuthorizeScopes(transaction, linkedConsentRequest.getPermittedAuthorizeScopes()); + validateConsent(transaction, linkedConsentRequest.getAcceptedClaims(), linkedConsentRequest.getPermittedAuthorizeScopes()); // cache consent only, auth-code will be generated on link-auth-code-status API call transaction.setAcceptedClaims(linkedConsentRequest.getAcceptedClaims()); transaction.setPermittedScopes(linkedConsentRequest.getPermittedAuthorizeScopes()); @@ -240,7 +254,30 @@ public LinkedConsentResponse saveConsent(LinkedConsentRequest linkedConsentReque LinkedConsentResponse authRespDto = new LinkedConsentResponse(); authRespDto.setLinkedTransactionId(linkedConsentRequest.getLinkedTransactionId()); - auditWrapper.logAudit(Action.SAVE_CONSENT, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(linkedConsentRequest.getLinkedTransactionId(), transaction), null); + auditWrapper.logAudit(Action.SAVE_CONSENT, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); + return authRespDto; + } + + @Override + public LinkedConsentResponse saveConsentV2(LinkedConsentRequestV2 linkedConsentRequest) throws EsignetException { + OIDCTransaction transaction = cacheUtilService.getLinkedAuthTransaction(linkedConsentRequest.getLinkedTransactionId()); + if(transaction == null || ConsentAction.NOCAPTURE.equals(transaction.getConsentAction())) { + throw new InvalidTransactionException(); + } + List acceptedClaims = linkedConsentRequest.getAcceptedClaims(); + List permittedAuthorizeScopes = linkedConsentRequest.getPermittedAuthorizeScopes(); + validateConsent(transaction, linkedConsentRequest.getAcceptedClaims(), linkedConsentRequest.getPermittedAuthorizeScopes()); + // cache consent only, auth-code will be generated on link-auth-code-status API call + transaction.setAcceptedClaims(linkedConsentRequest.getAcceptedClaims()); + transaction.setPermittedScopes(linkedConsentRequest.getPermittedAuthorizeScopes()); + consentHelperService.updateUserConsent(transaction, true, linkedConsentRequest.getSignature()); + cacheUtilService.setLinkedConsentedTransaction(linkedConsentRequest.getLinkedTransactionId(), transaction); + //Publish message after successfully saving the consent + kafkaHelperService.publish(linkedAuthCodeTopicName, linkedConsentRequest.getLinkedTransactionId()); + + LinkedConsentResponse authRespDto = new LinkedConsentResponse(); + authRespDto.setLinkedTransactionId(linkedConsentRequest.getLinkedTransactionId()); + auditWrapper.logAudit(Action.SAVE_CONSENT, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return authRespDto; } @@ -275,10 +312,16 @@ public void getLinkAuthCode(DeferredResult deferredResult, LinkAuthCodeRequest l OIDCTransaction oidcTransaction = cacheUtilService.getConsentedTransaction(linkTransactionMetadata.getLinkedTransactionId()); if(oidcTransaction != null) { - auditWrapper.logAudit(Action.LINK_AUTH_CODE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(linkAuthCodeRequest.getTransactionId(), oidcTransaction), null); + auditWrapper.logAudit(Action.LINK_AUTH_CODE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(oidcTransaction.getTransactionId(), oidcTransaction), null); deferredResult.setResult(authorizationHelperService.getLinkAuthStatusResponse(linkTransactionMetadata.getTransactionId(), oidcTransaction)); } else { authorizationHelperService.addEntryInLinkAuthCodeStatusDeferredResultMap(linkTransactionMetadata.getLinkedTransactionId(), deferredResult); } } + + + private void validateConsent(OIDCTransaction transaction, List acceptedClaims, List permittedScopes) { + authorizationHelperService.validateAcceptedClaims(transaction, acceptedClaims); + authorizationHelperService.validateAuthorizeScopes(transaction, permittedScopes); + } } diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/OAuthServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/OAuthServiceImpl.java index cd7abed0a..882fd8f8b 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/OAuthServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/OAuthServiceImpl.java @@ -75,7 +75,7 @@ public TokenResponse getTokens(TokenRequest tokenRequest) throws EsignetExceptio if(transaction == null || transaction.getKycToken() == null) throw new InvalidRequestException(ErrorConstants.INVALID_TRANSACTION); - if(StringUtils.isEmpty(tokenRequest.getClient_id()) || !transaction.getClientId().equals(tokenRequest.getClient_id())) + if(StringUtils.hasText(tokenRequest.getClient_id()) && !transaction.getClientId().equals(tokenRequest.getClient_id())) throw new InvalidRequestException(ErrorConstants.INVALID_CLIENT_ID); if(!transaction.getRedirectUri().equals(tokenRequest.getRedirect_uri())) @@ -98,14 +98,14 @@ public TokenResponse getTokens(TokenRequest tokenRequest) throws EsignetExceptio transaction.getClientId(), kycExchangeDto); } catch (KycExchangeException e) { log.error("KYC exchange failed", e); - auditWrapper.logAudit(Action.DO_KYC_EXCHANGE, ActionStatus.ERROR, AuditHelper.buildAuditDto(codeHash, transaction), e); + auditWrapper.logAudit(Action.DO_KYC_EXCHANGE, ActionStatus.ERROR, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), e); throw new EsignetException(e.getErrorCode()); } if(kycExchangeResult == null || kycExchangeResult.getEncryptedKyc() == null) throw new EsignetException(DATA_EXCHANGE_FAILED); - auditWrapper.logAudit(Action.DO_KYC_EXCHANGE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(codeHash, transaction), null); + auditWrapper.logAudit(Action.DO_KYC_EXCHANGE, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); TokenResponse tokenResponse = new TokenResponse(); tokenResponse.setAccess_token(tokenService.getAccessToken(transaction)); @@ -119,7 +119,7 @@ public TokenResponse getTokens(TokenRequest tokenRequest) throws EsignetExceptio transaction.setEncryptedKyc(kycExchangeResult.getEncryptedKyc()); cacheUtilService.setUserInfoTransaction(accessTokenHash, transaction); - auditWrapper.logAudit(Action.GENERATE_TOKEN, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(codeHash, + auditWrapper.logAudit(Action.GENERATE_TOKEN, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return tokenResponse; } diff --git a/oidc-service-impl/src/main/java/io/mosip/esignet/services/OpenIdConnectServiceImpl.java b/oidc-service-impl/src/main/java/io/mosip/esignet/services/OpenIdConnectServiceImpl.java index fd3278e62..035720487 100644 --- a/oidc-service-impl/src/main/java/io/mosip/esignet/services/OpenIdConnectServiceImpl.java +++ b/oidc-service-impl/src/main/java/io/mosip/esignet/services/OpenIdConnectServiceImpl.java @@ -61,13 +61,13 @@ public String getUserInfo(String accessToken) throws EsignetException { throw new NotAuthenticatedException(); tokenService.verifyAccessToken(transaction.getClientId(), transaction.getPartnerSpecificUserToken(), tokenParts[1]); - auditWrapper.logAudit(Action.GET_USERINFO, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(accessTokenHash, + auditWrapper.logAudit(Action.GET_USERINFO, ActionStatus.SUCCESS, AuditHelper.buildAuditDto(transaction.getTransactionId(), transaction), null); return transaction.getEncryptedKyc(); } catch (EsignetException ex) { auditWrapper.logAudit(Action.GET_USERINFO, ActionStatus.ERROR, AuditHelper.buildAuditDto(accessTokenHash, - transaction), null); + transaction), ex); throw ex; } } diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java index f73e1d20c..0786a3a7d 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationHelperServiceTest.java @@ -250,6 +250,18 @@ public void validateAcceptedClaims_withEmptyAcceptedClaims_thenPass() { authorizationHelperService.validateAcceptedClaims(new OIDCTransaction(), new ArrayList<>()); } + @Test + public void validateAcceptedClaims_withNullRequestedClaims_thenFail() { + + try { + authorizationHelperService.validateAcceptedClaims( + new OIDCTransaction(), Arrays.asList("name", "gender") + ); + Assert.fail(); + } catch (EsignetException e) { + Assert.assertEquals(INVALID_ACCEPTED_CLAIM, e.getErrorCode()); + } + } @Test public void validateAcceptedClaims_withEmptyRequestedClaims_thenFail() { Claims resolvedClaims = new Claims(); @@ -284,6 +296,76 @@ public void validateAcceptedClaims_withInvalidAcceptedClaims_thenFail() { } } + @Test + public void validateAcceptedClaims_withValidAcceptedEssentialClaims_thenPass() { + Claims resolvedClaims = new Claims(); + resolvedClaims.setUserinfo(new HashMap<>()); + Map userinfoClaims = new HashMap<>(); + userinfoClaims.put("name", new ClaimDetail(null, null, true)); + userinfoClaims.put("birthdate", new ClaimDetail(null, null, true)); + userinfoClaims.put("address", new ClaimDetail(null, null, false)); + userinfoClaims.put("gender", null); + resolvedClaims.setUserinfo(userinfoClaims); + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setRequestedClaims(resolvedClaims); + authorizationHelperService.validateAcceptedClaims(oidcTransaction, Arrays.asList("name", "birthdate")); + } + + @Test + public void validateAcceptedClaims_withAllOptionalClaimsNotAccepted_thenPass() { + Claims resolvedClaims = new Claims(); + resolvedClaims.setUserinfo(new HashMap<>()); + Map userinfoClaims = new HashMap<>(); + userinfoClaims.put("name", new ClaimDetail(null, null, false)); + userinfoClaims.put("birthdate", new ClaimDetail(null, null, false)); + userinfoClaims.put("address", new ClaimDetail(null, null, false)); + userinfoClaims.put("gender", null); + resolvedClaims.setUserinfo(userinfoClaims); + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setRequestedClaims(resolvedClaims); + authorizationHelperService.validateAcceptedClaims(oidcTransaction, List.of()); + } + + @Test + public void validateAcceptedClaims_withSomeValidAcceptedEssentialClaims_thenFail() { + Claims resolvedClaims = new Claims(); + resolvedClaims.setUserinfo(new HashMap<>()); + Map userinfoClaims = new HashMap<>(); + userinfoClaims.put("name", new ClaimDetail(null, null, true)); + userinfoClaims.put("birthdate", new ClaimDetail(null, null, true)); + userinfoClaims.put("address", new ClaimDetail(null, null, false)); + userinfoClaims.put("gender", null); + resolvedClaims.setUserinfo(userinfoClaims); + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setRequestedClaims(resolvedClaims); + try { + authorizationHelperService.validateAcceptedClaims(oidcTransaction, Arrays.asList("name", "address")); + Assert.fail(); + } catch (EsignetException e) { + Assert.assertEquals(INVALID_ACCEPTED_CLAIM, e.getErrorCode()); + } + } + + @Test + public void validateAcceptedClaims_withAllOptionalClaims_thenFail() { + Claims resolvedClaims = new Claims(); + resolvedClaims.setUserinfo(new HashMap<>()); + Map userinfoClaims = new HashMap<>(); + userinfoClaims.put("name", new ClaimDetail(null, null, false)); + userinfoClaims.put("birthdate", new ClaimDetail(null, null, false)); + userinfoClaims.put("address", new ClaimDetail(null, null, false)); + userinfoClaims.put("gender", null); + resolvedClaims.setUserinfo(userinfoClaims); + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setRequestedClaims(resolvedClaims); + try { + authorizationHelperService.validateAcceptedClaims(oidcTransaction, Arrays.asList("email", "phone_number")); + Assert.fail(); + } catch (EsignetException e) { + Assert.assertEquals(INVALID_ACCEPTED_CLAIM, e.getErrorCode()); + } + } + @Test public void validateAuthorizeScopes_withEmptyAcceptedScopes_thenPass() { OIDCTransaction oidcTransaction = new OIDCTransaction(); diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java index eb6255f48..d2a179b0c 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/AuthorizationServiceTest.java @@ -58,6 +58,9 @@ public class AuthorizationServiceTest { @Mock AuditPlugin auditWrapper; + @Mock + ConsentHelperService consentHelperService; + @Before public void setUp() { @@ -509,6 +512,137 @@ public void authenticate_multipleRegisteredAcrsWithInvalidMultiFactor_thenPass() Assert.assertTrue(ex.getErrorCode().equals(ErrorConstants.AUTH_FACTOR_MISMATCH)); } } + + @Test + public void authenticateV2_withInvalidTransaction_thenFail() { + String transactionId = "test-transaction"; + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(null); + + AuthRequest authRequest = new AuthRequest(); + authRequest.setTransactionId(transactionId); + try { + authorizationServiceImpl.authenticateUserV2(authRequest); + Assert.fail(); + } catch (EsignetException ex) { + Assert.assertTrue(ex.getErrorCode().equals(ErrorConstants.INVALID_TRANSACTION)); + } + } + + @Test + public void authenticateV2_multipleRegisteredAcrsWithSingleFactor_thenPass() throws EsignetException, KycAuthException { + String transactionId = "test-transaction"; + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(createIdpTransaction( + new String[]{"mosip:idp:acr:generated-code", "mosip:idp:acr:static-code"})); + + List> allAuthFactors=new ArrayList<>(); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:generated-code")); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:static-code")); + when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:generated-code", + "mosip:idp:acr:static-code"})).thenReturn(allAuthFactors); + + KycAuthResult kycAuthResult = new KycAuthResult(); + kycAuthResult.setKycToken("test-kyc-token"); + kycAuthResult.setPartnerSpecificUserToken("test-psut"); + when(authenticationWrapper.doKycAuth(anyString(), anyString(), any())).thenReturn(kycAuthResult); + + AuthRequest authRequest = new AuthRequest(); + authRequest.setTransactionId(transactionId); + authRequest.setIndividualId("23423434234"); + List authChallenges = new ArrayList<>(); + authChallenges.add(getAuthChallengeDto("OTP")); + authRequest.setChallengeList(authChallenges); + + AuthResponseV2 authResponseV2 = authorizationServiceImpl.authenticateUserV2(authRequest); + Assert.assertNotNull(authResponseV2); + Assert.assertEquals(transactionId, authResponseV2.getTransactionId()); + } + + @Test + public void authenticateV2_multipleRegisteredAcrsWithInvalidSingleFactor_thenFail() throws EsignetException { + String transactionId = "test-transaction"; + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(createIdpTransaction( + new String[]{"mosip:idp:acr:generated-code", "mosip:idp:acr:static-code"})); + + List> allAuthFactors=new ArrayList<>(); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:generated-code")); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:static-code")); + when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:generated-code", + "mosip:idp:acr:static-code"})).thenReturn(allAuthFactors); + + AuthRequest authRequest = new AuthRequest(); + authRequest.setTransactionId(transactionId); + authRequest.setIndividualId("23423434234"); + List authChallenges = new ArrayList<>(); + authChallenges.add(getAuthChallengeDto("BIO")); + authRequest.setChallengeList(authChallenges); + + try { + authorizationServiceImpl.authenticateUserV2(authRequest); + Assert.fail(); + } catch (EsignetException ex) { + Assert.assertTrue(ex.getErrorCode().equals(ErrorConstants.AUTH_FACTOR_MISMATCH)); + } + } + + @Test + public void authenticateV2_multipleRegisteredAcrsWithMultiFactor_thenPass() throws EsignetException, KycAuthException { + String transactionId = "test-transaction"; + String consentAction="Capture"; + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(createIdpTransaction( + new String[]{"mosip:idp:acr:biometrics-generated-code", "mosip:idp:acr:static-code"})); + + List> allAuthFactors=new ArrayList<>(); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:biometrics-generated-code")); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:static-code")); + when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:biometrics-generated-code", + "mosip:idp:acr:static-code"})).thenReturn(allAuthFactors); + + KycAuthResult kycAuthResult = new KycAuthResult(); + kycAuthResult.setKycToken("test-kyc-token"); + kycAuthResult.setPartnerSpecificUserToken("test-psut"); + when(authenticationWrapper.doKycAuth(anyString(), anyString(), any())).thenReturn(kycAuthResult); + + AuthRequest authRequest = new AuthRequest(); + authRequest.setTransactionId(transactionId); + authRequest.setIndividualId("23423434234"); + List authChallenges = new ArrayList<>(); + authChallenges.add(getAuthChallengeDto("OTP")); + authChallenges.add(getAuthChallengeDto("BIO")); + authRequest.setChallengeList(authChallenges); + + AuthResponseV2 authResponseV2 = authorizationServiceImpl.authenticateUserV2(authRequest); + Assert.assertNotNull(authResponseV2); + Assert.assertEquals(transactionId, authResponseV2.getTransactionId()); + //Assert.assertEquals(consentAction,authResponseV2.getConsentAction()); + } + + @Test + public void authenticateV2_multipleRegisteredAcrsWithInvalidMultiFactor_thenFail() throws EsignetException { + String transactionId = "test-transaction"; + when(cacheUtilService.getPreAuthTransaction(transactionId)).thenReturn(createIdpTransaction( + new String[]{"mosip:idp:acr:biometrics-generated-code", "mosip:idp:acr:linked-wallet"})); + + List> allAuthFactors=new ArrayList<>(); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:biometrics-generated-code")); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:linked-wallet")); + when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:biometrics-generated-code", + "mosip:idp:acr:linked-wallet"})).thenReturn(allAuthFactors); + + AuthRequest authRequest = new AuthRequest(); + authRequest.setTransactionId(transactionId); + authRequest.setIndividualId("23423434234"); + List authChallenges = new ArrayList<>(); + authChallenges.add(getAuthChallengeDto("OTP")); + authChallenges.add(getAuthChallengeDto("PIN")); + authRequest.setChallengeList(authChallenges); + + try { + authorizationServiceImpl.authenticateUserV2(authRequest); + Assert.fail(); + } catch (EsignetException ex) { + Assert.assertTrue(ex.getErrorCode().equals(ErrorConstants.AUTH_FACTOR_MISMATCH)); + } + } @Test public void getAuthCode_withValidInput_thenPass() { diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/ConsentHelperServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/ConsentHelperServiceTest.java new file mode 100644 index 000000000..5d10cfc57 --- /dev/null +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/ConsentHelperServiceTest.java @@ -0,0 +1,278 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +package io.mosip.esignet.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.esignet.api.dto.ClaimDetail; +import io.mosip.esignet.api.dto.Claims; +import io.mosip.esignet.api.spi.AuditPlugin; +import io.mosip.esignet.api.util.ConsentAction; +import io.mosip.esignet.core.dto.ConsentDetail; +import io.mosip.esignet.core.dto.OIDCTransaction; +import io.mosip.esignet.core.dto.UserConsent; +import io.mosip.esignet.core.dto.UserConsentRequest; +import io.mosip.esignet.core.spi.ConsentService; +import io.mosip.esignet.core.util.KafkaHelperService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + + +@RunWith(MockitoJUnitRunner.class) +public class ConsentHelperServiceTest { + + + @Mock + ConsentService consentService; + + @Mock + KafkaHelperService kafkaHelperService; + + @Mock + PasswordEncoder passwordEncoder; + + @InjectMocks + ConsentHelperService consentHelperService; + + @Autowired + ObjectMapper objectMapper; + + @Mock + AuditPlugin auditHelper; + + + @Test + public void addUserConsent_withValidLinkedTransaction_thenPass() + { + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setAuthTransactionId("123"); + oidcTransaction.setAcceptedClaims(List.of("name")); + oidcTransaction.setPermittedScopes(null); + oidcTransaction.setConsentAction(ConsentAction.CAPTURE); + + Claims claims = new Claims(); + Map userinfo = new HashMap<>(); + Map id_token = new HashMap<>(); + ClaimDetail userinfoNameClaimDetail = new ClaimDetail("name", new String[]{"value1a", "value1b"}, true); + ClaimDetail idTokenClaimDetail = new ClaimDetail("token", new String[]{"value2a", "value2b"}, false); + userinfo.put("name", userinfoNameClaimDetail); + id_token.put("idTokenKey", idTokenClaimDetail); + claims.setUserinfo(userinfo); + claims.setId_token(id_token); + + oidcTransaction.setRequestedClaims(claims); + consentHelperService.updateUserConsent(oidcTransaction, true, null); + UserConsent userConsent = new UserConsent(); + userConsent.setHash("PxQIckCdFC5TPmL7_G7NH0Zs4UmHC74rGpOkyldqRpg"); + userConsent.setClaims(claims); + userConsent.setAuthorizationScopes(Map.of()); + userConsent.setAcceptedClaims(List.of("name")); + Mockito.verify(consentService).saveUserConsent(userConsent); + + } + + @Test + public void addUserConsent_withValidWebTransaction_thenPass() + { + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setAuthTransactionId("123"); + oidcTransaction.setAcceptedClaims(List.of("value1")); + oidcTransaction.setPermittedScopes(null); + oidcTransaction.setConsentAction(ConsentAction.CAPTURE); + + Claims claims = new Claims(); + Map userinfo = new HashMap<>(); + Map id_token = new HashMap<>(); + ClaimDetail userinfoClaimDetail = new ClaimDetail("value1", new String[]{"value1a", "value1b"}, true); + ClaimDetail idTokenClaimDetail = new ClaimDetail("value2", new String[]{"value2a", "value2b"}, false); + userinfo.put("userinfoKey", userinfoClaimDetail); + id_token.put("idTokenKey", idTokenClaimDetail); + claims.setUserinfo(userinfo); + claims.setId_token(id_token); + + oidcTransaction.setRequestedClaims(claims); + + Mockito.when(consentService.saveUserConsent(Mockito.any())).thenReturn(new ConsentDetail()); + + consentHelperService.updateUserConsent(oidcTransaction, false, ""); + UserConsent userConsent = new UserConsent(); + userConsent.setHash("Cgh8oWpNM84WPYQVvluGj616_kd4z60elVXtc7R_lXw"); + userConsent.setClaims(claims); + userConsent.setAuthorizationScopes(Map.of()); + userConsent.setAcceptedClaims(List.of("value1")); + userConsent.setSignature(""); + Mockito.verify(consentService).saveUserConsent(userConsent); + } + + @Test + public void addUserConsent_withValidWebTransactionNoClaimsAndScopes_thenPass() + { + String clientId = "clientId"; + String psuToken = "psuToken"; + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setAuthTransactionId("123"); + oidcTransaction.setAcceptedClaims(List.of()); + oidcTransaction.setRequestedAuthorizeScopes(List.of()); + oidcTransaction.setConsentAction(ConsentAction.NOCAPTURE); + oidcTransaction.setVoluntaryClaims(List.of()); + oidcTransaction.setEssentialClaims(List.of()); + oidcTransaction.setAcceptedClaims(List.of()); + oidcTransaction.setPermittedScopes(List.of()); + oidcTransaction.setClientId(clientId); + oidcTransaction.setPartnerSpecificUserToken(psuToken); + consentHelperService.updateUserConsent(oidcTransaction, false, ""); + Mockito.verify(consentService).deleteUserConsent(clientId, psuToken); + } + + @Test + public void processConsent_withValidConsentAndConsentActionAsNoCapture_thenPass() throws JsonProcessingException { + + OIDCTransaction oidcTransaction=new OIDCTransaction(); + oidcTransaction.setClientId("abc"); + oidcTransaction.setPartnerSpecificUserToken("123"); + oidcTransaction.setRequestedAuthorizeScopes(List.of("openid","profile")); + oidcTransaction.setPermittedScopes(List.of("openid","profile")); + oidcTransaction.setEssentialClaims(List.of("name")); + oidcTransaction.setVoluntaryClaims(List.of("email")); + + Claims claims = new Claims(); + Map userinfo = new HashMap<>(); + Map id_token = new HashMap<>(); + ClaimDetail userinfoNameClaimDetail = new ClaimDetail("name", new String[]{"value1a", "value1b"}, true); + ClaimDetail idTokenClaimDetail = new ClaimDetail("token", new String[]{"value2a", "value2b"}, false); + userinfo.put("name", userinfoNameClaimDetail); + userinfo.put("email",null); + id_token.put("idTokenKey", idTokenClaimDetail); + claims.setUserinfo(userinfo); + claims.setId_token(id_token); + + oidcTransaction.setRequestedClaims(claims); + + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId(oidcTransaction.getClientId()); + userConsentRequest.setPsuToken(oidcTransaction.getPartnerSpecificUserToken()); + + ConsentDetail consentDetail = new ConsentDetail(); + consentDetail.setClientId("123"); + consentDetail.setSignature("signature"); + consentDetail.setAuthorizationScopes(Map.of("openid",false,"profile",false)); + consentDetail.setClaims(claims); + Claims normalizedClaims = new Claims(); + normalizedClaims.setUserinfo(consentHelperService.normalizeClaims(claims.getUserinfo())); + normalizedClaims.setId_token(consentHelperService.normalizeClaims(claims.getId_token())); + String hashCode =consentHelperService.hashUserConsent(normalizedClaims,consentDetail.getAuthorizationScopes()); + consentDetail.setHash(hashCode); + + Mockito.when(consentService.getUserConsent(userConsentRequest)).thenReturn(Optional.of(consentDetail)); + + consentHelperService.processConsent(oidcTransaction,true); + + Assert.assertEquals(oidcTransaction.getConsentAction(),ConsentAction.NOCAPTURE); + Assert.assertEquals(oidcTransaction.getAcceptedClaims(),consentDetail.getAcceptedClaims()); + Assert.assertEquals(oidcTransaction.getPermittedScopes(),consentDetail.getPermittedScopes()); + } + + @Test + public void processConsent_withValidConsentAndConsentActionAsCapture_thenPass() throws JsonProcessingException { + + OIDCTransaction oidcTransaction=new OIDCTransaction(); + oidcTransaction.setClientId("abc"); + oidcTransaction.setPartnerSpecificUserToken("123"); + oidcTransaction.setRequestedAuthorizeScopes(List.of("openid","profile")); + oidcTransaction.setPermittedScopes(List.of("openid","profile")); + oidcTransaction.setEssentialClaims(List.of("name")); + oidcTransaction.setVoluntaryClaims(List.of("email")); + Claims claims = new Claims(); + Map userinfo = new HashMap<>(); + Map id_token = new HashMap<>(); + ClaimDetail userinfoNameClaimDetail = new ClaimDetail("name", new String[]{"value1a", "value1b"}, true); + ClaimDetail idTokenClaimDetail = new ClaimDetail("token", new String[]{"value2a", "value2b"}, false); + userinfo.put("name", userinfoNameClaimDetail); + userinfo.put("email",null); + id_token.put("idTokenKey", idTokenClaimDetail); + claims.setUserinfo(userinfo); + claims.setId_token(id_token); + + oidcTransaction.setRequestedClaims(claims); + + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId(oidcTransaction.getClientId()); + userConsentRequest.setPsuToken(oidcTransaction.getPartnerSpecificUserToken()); + + ConsentDetail consentDetail = new ConsentDetail(); + consentDetail.setClientId("123"); + consentDetail.setSignature("signature"); + consentDetail.setAuthorizationScopes(Map.of("openid",true,"profile",true)); + + Claims consentClaims = new Claims(); + userinfo = new HashMap<>(); + id_token = new HashMap<>(); + userinfoNameClaimDetail = new ClaimDetail("gender", new String[]{"value1a", "value1b"}, false); + idTokenClaimDetail = new ClaimDetail("token", new String[]{"value1a", "value2b"}, false); + userinfo.put("gender", userinfoNameClaimDetail); + userinfo.put("email",null); + id_token.put("idTokenKey", idTokenClaimDetail); + consentClaims.setUserinfo(userinfo); + consentClaims.setId_token(id_token); + + consentDetail.setClaims(consentClaims); + String hashCode =consentHelperService.hashUserConsent(consentClaims,consentDetail.getAuthorizationScopes()); + consentDetail.setHash(hashCode); + + Mockito.when(consentService.getUserConsent(userConsentRequest)).thenReturn(Optional.of(consentDetail)); + consentHelperService.processConsent(oidcTransaction,true); + + Assert.assertEquals(oidcTransaction.getConsentAction(),ConsentAction.CAPTURE); + + } + + @Test + public void processConsent_withEmptyConsent_thenPass(){ + + OIDCTransaction oidcTransaction=new OIDCTransaction(); + oidcTransaction.setClientId("abc"); + oidcTransaction.setPartnerSpecificUserToken("123"); + oidcTransaction.setVoluntaryClaims(List.of("email")); + oidcTransaction.setEssentialClaims(List.of()); + oidcTransaction.setRequestedAuthorizeScopes(List.of()); + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId(oidcTransaction.getClientId()); + userConsentRequest.setPsuToken(oidcTransaction.getPartnerSpecificUserToken()); + + Mockito.when(consentService.getUserConsent(userConsentRequest)).thenReturn(Optional.empty()); + + consentHelperService.processConsent(oidcTransaction,true); + Assert.assertEquals(oidcTransaction.getConsentAction(),ConsentAction.CAPTURE); + + } + + @Test + public void processConsent_withEmptyRequestedClaims_thenPass(){ + OIDCTransaction oidcTransaction=new OIDCTransaction(); + oidcTransaction.setClientId("abc"); + oidcTransaction.setPartnerSpecificUserToken("123"); + oidcTransaction.setVoluntaryClaims(List.of()); + oidcTransaction.setEssentialClaims(List.of()); + oidcTransaction.setRequestedAuthorizeScopes(List.of()); + UserConsentRequest userConsentRequest = new UserConsentRequest(); + userConsentRequest.setClientId(oidcTransaction.getClientId()); + userConsentRequest.setPsuToken(oidcTransaction.getPartnerSpecificUserToken()); + consentHelperService.processConsent(oidcTransaction,true); + Assert.assertEquals(oidcTransaction.getConsentAction(),ConsentAction.NOCAPTURE); + } +} \ No newline at end of file diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/LinkedAuthorizationServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/LinkedAuthorizationServiceTest.java index 5505cfb8b..514745f64 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/LinkedAuthorizationServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/LinkedAuthorizationServiceTest.java @@ -11,9 +11,10 @@ import io.mosip.esignet.api.exception.SendOtpException; import io.mosip.esignet.api.spi.AuditPlugin; import io.mosip.esignet.api.spi.Authenticator; +import io.mosip.esignet.api.util.ConsentAction; import io.mosip.esignet.core.constants.ErrorConstants; -import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.dto.Error; +import io.mosip.esignet.core.dto.*; import io.mosip.esignet.core.exception.DuplicateLinkCodeException; import io.mosip.esignet.core.exception.EsignetException; import io.mosip.esignet.core.exception.InvalidTransactionException; @@ -69,6 +70,9 @@ public class LinkedAuthorizationServiceTest { @Mock AuditPlugin auditWrapper; + @Mock + ConsentHelperService consentHelperService; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -328,6 +332,50 @@ public void authenticateUser_withInvalidTransaction_thenFail() { } } + @Test + public void authenticateUserV2_withValidInput_thenPass() throws KycAuthException { + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("link-transaction-id"); + + OIDCTransaction oidcTransaction = createIdpTransaction(new String[]{"mosip:idp:acr:generated-code", "mosip:idp:acr:static-code"}); + oidcTransaction.setConsentAction(ConsentAction.NOCAPTURE); + oidcTransaction.setLinkedTransactionId("link-transaction-id"); + Mockito.when(cacheUtilService.getLinkedSessionTransaction(linkedKycAuthRequest.getLinkedTransactionId())).thenReturn(oidcTransaction); + + List> allAuthFactors=new ArrayList<>(); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:generated-code")); + allAuthFactors.add(getAuthFactors("mosip:idp:acr:static-code")); + when(authenticationContextClassRefUtil.getAuthFactors(new String[]{"mosip:idp:acr:generated-code", + "mosip:idp:acr:static-code"})).thenReturn(allAuthFactors); + + KycAuthResult kycAuthResult = new KycAuthResult(); + kycAuthResult.setKycToken("test-kyc-token"); + kycAuthResult.setPartnerSpecificUserToken("test-psut"); + when(authenticationWrapper.doKycAuth(anyString(), anyString(), any())).thenReturn(kycAuthResult); + + linkedKycAuthRequest.setIndividualId("23423434234"); + List authChallenges = new ArrayList<>(); + authChallenges.add(getAuthChallengeDto("OTP")); + linkedKycAuthRequest.setChallengeList(authChallenges); + + LinkedKycAuthResponseV2 authResponse = linkedAuthorizationService.authenticateUserV2(linkedKycAuthRequest); + Assert.assertNotNull(authResponse); + Assert.assertEquals(linkedKycAuthRequest.getLinkedTransactionId(), authResponse.getLinkedTransactionId()); + } + + @Test + public void authenticateUserV2_withInvalidTransaction_thenFail() { + LinkedKycAuthRequest linkedKycAuthRequest = new LinkedKycAuthRequest(); + linkedKycAuthRequest.setLinkedTransactionId("link-transaction-id"); + + try { + linkedAuthorizationService.authenticateUser(linkedKycAuthRequest); + Assert.fail(); + } catch (InvalidTransactionException ex) { + Assert.assertEquals(ErrorConstants.INVALID_TRANSACTION, ex.getErrorCode()); + } + } + @Test public void saveConsent_withValidInput_thenPass() { Mockito.when(cacheUtilService.getLinkedAuthTransaction("link-transaction-id")).thenReturn(new OIDCTransaction()); @@ -350,6 +398,28 @@ public void saveConsent_withInvalidTransaction_thenFail() { } } + @Test + public void saveConsentV2_withValidInput_thenPass() { + Mockito.when(cacheUtilService.getLinkedAuthTransaction("link-transaction-id")).thenReturn(new OIDCTransaction()); + LinkedConsentRequestV2 linkedConsentRequestV2 = new LinkedConsentRequestV2(); + linkedConsentRequestV2.setLinkedTransactionId("link-transaction-id"); + LinkedConsentResponse linkedConsentResponse = linkedAuthorizationService.saveConsentV2(linkedConsentRequestV2); + Assert.assertNotNull(linkedConsentResponse); + Assert.assertEquals(linkedConsentRequestV2.getLinkedTransactionId(), linkedConsentResponse.getLinkedTransactionId()); + } + + @Test + public void saveConsentV2_withInvalidTransaction_thenFail() { + LinkedConsentRequestV2 linkedConsentRequestV2 = new LinkedConsentRequestV2(); + linkedConsentRequestV2.setLinkedTransactionId("link-transaction-id"); + try { + linkedAuthorizationService.saveConsentV2(linkedConsentRequestV2); + Assert.fail(); + } catch (InvalidTransactionException ex) { + Assert.assertEquals(ErrorConstants.INVALID_TRANSACTION, ex.getErrorCode()); + } + } + @Test public void linkStatus_withValidInput_thenPass() { LinkStatusRequest linkStatusRequest = new LinkStatusRequest(); diff --git a/oidc-service-impl/src/test/java/io/mosip/esignet/services/OAuthServiceTest.java b/oidc-service-impl/src/test/java/io/mosip/esignet/services/OAuthServiceTest.java index a05fcdddb..b73c08015 100644 --- a/oidc-service-impl/src/test/java/io/mosip/esignet/services/OAuthServiceTest.java +++ b/oidc-service-impl/src/test/java/io/mosip/esignet/services/OAuthServiceTest.java @@ -114,12 +114,85 @@ public void getTokens_withInvalidAuthCode_thenFail() { } @Test - public void getTokens_withInvalidClientId_thenFail() { + public void getTokens_withNullClientIdInRequest_thenPass() throws KycExchangeException { TokenRequest tokenRequest = new TokenRequest(); tokenRequest.setCode("test-code"); + tokenRequest.setRedirect_uri("https://test-redirect-uri/test-page"); + tokenRequest.setClient_assertion_type(JWT_BEARER_TYPE); + tokenRequest.setClient_assertion("client-assertion"); + + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setClientId("client-id"); + oidcTransaction.setKycToken("kyc-token"); + oidcTransaction.setAuthTransactionId("auth-transaction-id"); + oidcTransaction.setRelyingPartyId("rp-id"); + oidcTransaction.setRedirectUri("https://test-redirect-uri/test-page"); + oidcTransaction.setIndividualId("individual-id"); + ClientDetail clientDetail = new ClientDetail(); + clientDetail.setRedirectUris(Arrays.asList("https://test-redirect-uri/**", "http://test-redirect-uri-2")); + KycExchangeResult kycExchangeResult = new KycExchangeResult(); + kycExchangeResult.setEncryptedKyc("encrypted-kyc"); + + Mockito.when(authorizationHelperService.getKeyHash(Mockito.anyString())).thenReturn("code-hash"); + ReflectionTestUtils.setField(authorizationHelperService, "secureIndividualId", false); + Mockito.when(cacheUtilService.getAuthCodeTransaction(Mockito.anyString())).thenReturn(oidcTransaction); + Mockito.when(clientManagementService.getClientDetails(Mockito.anyString())).thenReturn(clientDetail); + Mockito.when(authenticationWrapper.doKycExchange(Mockito.anyString(), Mockito.anyString(), Mockito.any())).thenReturn(kycExchangeResult); + Mockito.when(tokenService.getAccessToken(Mockito.any())).thenReturn("test-access-token"); + Mockito.when(tokenService.getIDToken(Mockito.any())).thenReturn("test-id-token"); + TokenResponse tokenResponse = oAuthService.getTokens(tokenRequest); + Assert.assertNotNull(tokenResponse); + Assert.assertNotNull(tokenResponse.getId_token()); + Assert.assertNotNull(tokenResponse.getAccess_token()); + Assert.assertEquals(BEARER, tokenResponse.getToken_type()); + Assert.assertEquals(kycExchangeResult.getEncryptedKyc(), oidcTransaction.getEncryptedKyc()); + } + + @Test + public void getTokens_withEmptyClientIdInRequest_thenPass() throws KycExchangeException { + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setCode("test-code"); + tokenRequest.setClient_id(" "); + tokenRequest.setRedirect_uri("https://test-redirect-uri/test-page"); + tokenRequest.setClient_assertion_type(JWT_BEARER_TYPE); + tokenRequest.setClient_assertion("client-assertion"); + + OIDCTransaction oidcTransaction = new OIDCTransaction(); + oidcTransaction.setClientId("client-id"); + oidcTransaction.setKycToken("kyc-token"); + oidcTransaction.setAuthTransactionId("auth-transaction-id"); + oidcTransaction.setRelyingPartyId("rp-id"); + oidcTransaction.setRedirectUri("https://test-redirect-uri/test-page"); + oidcTransaction.setIndividualId("individual-id"); + ClientDetail clientDetail = new ClientDetail(); + clientDetail.setRedirectUris(Arrays.asList("https://test-redirect-uri/**", "http://test-redirect-uri-2")); + KycExchangeResult kycExchangeResult = new KycExchangeResult(); + kycExchangeResult.setEncryptedKyc("encrypted-kyc"); + + Mockito.when(authorizationHelperService.getKeyHash(Mockito.anyString())).thenReturn("code-hash"); + ReflectionTestUtils.setField(authorizationHelperService, "secureIndividualId", false); + Mockito.when(cacheUtilService.getAuthCodeTransaction(Mockito.anyString())).thenReturn(oidcTransaction); + Mockito.when(clientManagementService.getClientDetails(Mockito.anyString())).thenReturn(clientDetail); + Mockito.when(authenticationWrapper.doKycExchange(Mockito.anyString(), Mockito.anyString(), Mockito.any())).thenReturn(kycExchangeResult); + Mockito.when(tokenService.getAccessToken(Mockito.any())).thenReturn("test-access-token"); + Mockito.when(tokenService.getIDToken(Mockito.any())).thenReturn("test-id-token"); + TokenResponse tokenResponse = oAuthService.getTokens(tokenRequest); + Assert.assertNotNull(tokenResponse); + Assert.assertNotNull(tokenResponse.getId_token()); + Assert.assertNotNull(tokenResponse.getAccess_token()); + Assert.assertEquals(BEARER, tokenResponse.getToken_type()); + Assert.assertEquals(kycExchangeResult.getEncryptedKyc(), oidcTransaction.getEncryptedKyc()); + } + + @Test + public void getTokens_withInvalidClientIdInRequest_thenFail() { + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setCode("test-code"); + tokenRequest.setClient_id("t"); OIDCTransaction oidcTransaction = new OIDCTransaction(); oidcTransaction.setKycToken("kyc-token"); + oidcTransaction.setClientId("test-test"); Mockito.when(authorizationHelperService.getKeyHash(Mockito.anyString())).thenReturn("code-hash"); Mockito.when(cacheUtilService.getAuthCodeTransaction(Mockito.anyString())).thenReturn(oidcTransaction); diff --git a/oidc-ui/.env b/oidc-ui/.env index 6a7282874..452441a6f 100644 --- a/oidc-ui/.env +++ b/oidc-ui/.env @@ -17,11 +17,12 @@ REACT_APP_SBI_IRIS_CAPTURE_SCORE=70 REACT_APP_SBI_IRIS_BIO_SUBTYPES="UNKNOWN" REACT_APP_SBI_FINGER_BIO_SUBTYPES="UNKNOWN" -REACT_APP_LINK_CODE_TIMEOUT_IN_SEC=60 +REACT_APP_LINK_AUTH_CODE_TIMEOUT_IN_SEC=120 REACT_APP_LINK_CODE_DEFERRED_TIMEOUT_IN_SEC=25 REACT_APP_QRCODE_DEEP_LINK_URI="inji://landing-page-name?linkCode=LINK_CODE&linkExpireDateTime=LINK_EXPIRE_DT" REACT_APP_QRCODE_APP_DOWNLOAD_URI="#" REACT_APP_QRCODE_ENABLE="true" +REACT_APP_CONSENT_SCREEN_EXPIRE_IN_SEC=60 #inclusive REACT_APP_SBI_PORT_RANGE="4501-4600" @@ -34,4 +35,12 @@ REACT_APP_CAPTCHA_ENABLE="OTP" #site key for testing purposes only REACT_APP_CAPTCHA_SITE_KEY="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" -REACT_APP_AUTH_TXN_ID_LENGTH=10 \ No newline at end of file +REACT_APP_AUTH_TXN_ID_LENGTH=10 + +REACT_APP_OTP_LENGTH=6 + +REACT_APP_PASSWORD_REGEX="" + +REACT_APP_WALLET_LOGO_URL="" + +REACT_APP_CONSENT_SCREEN_TIME_OUT_BUFFER_IN_SEC=5 \ No newline at end of file diff --git a/oidc-ui/Dockerfile b/oidc-ui/Dockerfile index a35fab3b1..b22f4af27 100644 --- a/oidc-ui/Dockerfile +++ b/oidc-ui/Dockerfile @@ -30,6 +30,8 @@ ARG container_user_gid=1001 ENV base_path=/usr/share/nginx/html ENV i18n_path=${base_path}/locales +ENV plugins_path=${base_path}/plugins +ENV plugins_format=iife # can be passed during Docker build as build time environment for artifactory URL ARG artifactory_url @@ -42,11 +44,11 @@ WORKDIR /home/${container_user} # install packages and create user RUN apt-get -y update \ - && apt-get install -y curl npm python wget unzip zip \ + && apt-get install -y curl npm wget unzip zip \ && groupadd -g ${container_user_gid} ${container_user_group} \ && useradd -u ${container_user_uid} -g ${container_user_group} -s /bin/sh -m ${container_user} \ - && mkdir -p /var/run/nginx /var/tmp/nginx ${base_path}/locales \ - && chown -R ${container_user}:${container_user} /usr/share/nginx /var/run/nginx /var/tmp/nginx ${base_path}/locales + && mkdir -p /var/run/nginx /var/tmp/nginx ${base_path}/locales ${base_path}/plugins ${base_path}/plugins/temp \ + && chown -R ${container_user}:${container_user} /usr/share/nginx /var/run/nginx /var/tmp/nginx ${base_path}/locales ${base_path}/plugins ${base_path}/plugins/temp ADD configure_start.sh configure_start.sh diff --git a/oidc-ui/configure_start.sh b/oidc-ui/configure_start.sh index 2d9c1723a..e4f2650c1 100644 --- a/oidc-ui/configure_start.sh +++ b/oidc-ui/configure_start.sh @@ -3,15 +3,34 @@ #installs the pre-requisites. set -e -echo "Downloading pre-requisites install scripts" +echo "Downloading pre-requisites started." + +#i18n bundle +echo "Downloading i18n bundle files" wget --no-check-certificate --no-cache --no-cookies $artifactory_url_env/artifactory/libs-release-local/i18n/esignet-i18n-bundle.zip -O $i18n_path/esignet-i18n-bundle.zip -echo "unzip pre-requisites.." +echo "unzip i18n bundle files.." chmod 775 $i18n_path/* cd $i18n_path unzip -o esignet-i18n-bundle.zip -echo "unzip pre-requisites completed." +#sign-in-button-plugin +echo "Downloading plugins" + +wget --no-check-certificate --no-cache --no-cookies $artifactory_url_env/artifactory/libs-release-local/esignet-plugins/sign-in-with-esignet.zip -O $plugins_path/temp/sign-in-button-plugin.zip + +echo "unzip plugins.." +cd $plugins_path/temp +unzip -o sign-in-button-plugin.zip + +#move the required js file +mv $plugins_path/temp/sign-in-with-esignet/$plugins_format/index.js $plugins_path/sign-in-button-plugin.js + +# delete temp folder +cd $plugins_path +rm -r temp + +echo "Pre-requisites download completed." exec "$@" \ No newline at end of file diff --git a/oidc-ui/package.json b/oidc-ui/package.json index 7731a8ee2..5d307f69f 100644 --- a/oidc-ui/package.json +++ b/oidc-ui/package.json @@ -5,7 +5,7 @@ "dependencies": { "@emotion/react": "^11.10.4", "@tailwindcss/line-clamp": "^0.4.2", - "axios": "^0.27.2", + "axios": "^1.4.0", "buffer": "^6.0.3", "cra-template": "1.1.3", "crypto-js": "^4.1.1", @@ -13,9 +13,10 @@ "i18next-browser-languagedetector": "^7.0.0", "i18next-http-backend": "^2.0.1", "jose": "^4.9.3", + "secure-biometric-interface-integrator": "^0.1.0", "qrcode": "^1.5.1", "react": "^18.2.0", - "react-dom": "^18.0.0", + "react-dom": "^18.2.0", "react-google-recaptcha": "^2.1.0", "react-i18next": "^11.18.6", "react-pin-input": "^1.3.0", diff --git a/oidc-ui/public/inji_logo.png b/oidc-ui/public/inji_logo.png new file mode 100644 index 000000000..fe810a5e2 Binary files /dev/null and b/oidc-ui/public/inji_logo.png differ diff --git a/oidc-ui/public/locales/ar.json b/oidc-ui/public/locales/ar.json index fb1171de8..f5e27a48d 100644 --- a/oidc-ui/public/locales/ar.json +++ b/oidc-ui/public/locales/ar.json @@ -3,6 +3,7 @@ "authorization_failed_msg": "فشل التفويض", "consent_request_rejected": "تم رفض طلب الموافقة", "consent_request_msg": "يطلب {{clientName}} الوصول إلى ما يلي:", + "transaction_timeout_msg": "يرجى اتخاذ الإجراء المناسب في", "authorize_scope": "تفويض النطاقات", "essential_claims": "الادعاءات الأساسية", "voluntary_claims": "المطالبات الطوعية", @@ -60,6 +61,15 @@ "uin_placeholder": "UIN أو VID", "pin_placeholder": "أدخل رقم التعريف الشخصي" }, + "password": { + "sign_in_with_password": "تسجيل الدخول باستخدام كلمة المرور", + "remember_me": "تذكرنى", + "login": "تسجيل الدخول", + "uin_label_text": "أدخل VID الخاص بك", + "password_label_text": "أدخل كلمة المرور", + "uin_placeholder": "VID", + "password_placeholder": "كلمة المرور" + }, "LoginQRCode": { "sign_in_with_inji": "تسجيل الدخول باستخدام Inji", "scan_with_inji": "امسح باستخدام تطبيق Inji لتسجيل الدخول", @@ -97,7 +107,8 @@ "BIO": "القياسات الحيوية", "QRCode": "رمز الاستجابة السريعة", "WALLET": "إنجي", - "OTP": "OTP" + "OTP": "OTP", + "PWD": "كلمة المرور" }, "esignetDetails": { "esignet_details_heading": "واجهات برمجة التطبيقات Esignet معروفة جيدا", @@ -232,6 +243,7 @@ "invalid_version": "نسخة غير صالحة", "invalid_value": "قيمة غير صالحة", "duplicate_individual_id": "المعرف الفردي موجود بالفعل.", + "password_error_msg": "رمز مرور خاطئ", "0": "النجاح", "100": "الجهاز غير مسجل", "101": "غير قادر على الكشف عن كائن القياسات الحيوية", diff --git a/oidc-ui/public/locales/en.json b/oidc-ui/public/locales/en.json index 43aa1105e..8bf37f89f 100644 --- a/oidc-ui/public/locales/en.json +++ b/oidc-ui/public/locales/en.json @@ -3,6 +3,7 @@ "authorization_failed_msg": "Authorization Failed", "consent_request_rejected": "Consent Request Rejected", "consent_request_msg": "{{clientName}} is requesting access to the following: ", + "transaction_timeout_msg":"Please take appropriate action in", "authorize_scope": "Authorize Scopes", "essential_claims": "Essential Claims", "voluntary_claims": "Voluntary Claims", @@ -60,6 +61,15 @@ "uin_placeholder": "UIN or VID", "pin_placeholder": "Enter PIN" }, + "password": { + "sign_in_with_password": "Login with Password", + "remember_me": "Remember Me", + "login": "Login", + "uin_label_text": "Enter your VID", + "password_label_text": "Enter Password", + "uin_placeholder": "VID", + "password_placeholder": "Password" + }, "LoginQRCode": { "sign_in_with_inji": "Login with Inji", "scan_with_inji": "Scan with Inji App to Log In", @@ -97,7 +107,8 @@ "BIO": "Biometrics", "QRCode": "QR Code", "WALLET": "Inji", - "OTP": "OTP" + "OTP": "OTP", + "PWD": "Password" }, "esignetDetails": { "esignet_details_heading": "Esignet wellknown APIs", @@ -213,7 +224,7 @@ "sha256_thumbprint_header_missing": "Missing x5t#S256 header in WLA token.", "invalid_challenge_format": "Invalid Authentication challenge format found.", "link_code_limit_reached": "Number of allowed Link code per transaction limit reached.", - "failed_to_generate_header_hash":"Failed to generate oauth-details-hash http header value.", + "failed_to_generate_header_hash": "Failed to generate oauth-details-hash http header value.", "invalid_individual_id": "Invalid Individual ID", "invalid_pin": "Invalid Pin", "invalid_email": "Invalid Email ID", @@ -232,6 +243,7 @@ "invalid_version": "Invalid version", "invalid_value": "Invalid value", "duplicate_individual_id": "Individual ID already exists.", + "password_error_msg": "Invalid Password", "0": "Success", "100": "Device Not Registered", "101": "Unable to Detect a Biometrics", diff --git a/oidc-ui/src/components/Background.js b/oidc-ui/src/components/Background.js index 68d9fa80f..f3892706d 100644 --- a/oidc-ui/src/components/Background.js +++ b/oidc-ui/src/components/Background.js @@ -17,9 +17,11 @@ export default function Background({ }) { const tabs = [ { + id: "wallet_tab_id", name: "inji_tab_name", }, { + id: "here_tab_id", name: "here_tab_name", }, ]; @@ -51,7 +53,7 @@ export default function Background({ {t("logo_alt")}
-

+

{heading}

@@ -69,6 +71,7 @@ export default function Background({ className="-mb-px flex-auto text-center" >

{t("dont_have_inji")}  - + {t("download_now")}

@@ -116,6 +119,7 @@ export default function Background({
- +
+
+
+ {t(claimScope.label)} + + +
+
+
+ {!claimScope?.required && + claimScope.values.length > 1 && + sliderButtonDiv(claimScope.label, (e) => + selectUnselectAllScopeClaim(e, claimScope, true) + )} +
@@ -227,35 +408,10 @@ export default function Consent({ {t("required")} )} - {!claimScope?.required && ( -
- -
- )} + {!claimScope?.required && + sliderButtonDiv(item, (e) => + selectUnselectAllScopeClaim(e, claimScope) + )}
@@ -277,15 +433,25 @@ export default function Consent({ type={buttonTypes.cancel} text={t("cancel")} handleClick={handleCancel} + id="cancel" /> )} +
+ {timeLeft && timeLeft > 0 && status !== LoadingStates.LOADING && ( +
+

{t("transaction_timeout_msg")}

+

{formatTime(timeLeft)}

+
+ )} +
); diff --git a/oidc-ui/src/components/FormAction.js b/oidc-ui/src/components/FormAction.js index 7e9df356b..223fc4e3c 100644 --- a/oidc-ui/src/components/FormAction.js +++ b/oidc-ui/src/components/FormAction.js @@ -5,6 +5,7 @@ export default function FormAction({ type = "Button", //valid values: Button, Submit and Reset text, disabled = false, + id }) { const className = "flex justify-center w-full font-medium rounded-lg text-sm px-5 py-2 text-center border border-2 "; @@ -23,6 +24,7 @@ export default function FormAction({ } onClick={handleClick} disabled={disabled} + id={id} > {text} @@ -39,6 +41,7 @@ export default function FormAction({ } onSubmit={handleClick} disabled={disabled} + id={id} > {text} @@ -71,6 +74,7 @@ export default function FormAction({ } onClick={handleClick} disabled={disabled} + id={id} > {text} diff --git a/oidc-ui/src/components/InputWithImage.js b/oidc-ui/src/components/InputWithImage.js index 627825f95..6574a197d 100644 --- a/oidc-ui/src/components/InputWithImage.js +++ b/oidc-ui/src/components/InputWithImage.js @@ -41,9 +41,11 @@ export default function InputWithImage({ )}
-
- -
+ {imgPath && +
+ +
+ } (fieldsState["sbi_" + field.id] = "")); const [loginState, setLoginState] = useState(fieldsState); @@ -58,9 +42,6 @@ export default function L1Biometrics({ const navigate = useNavigate(); - const [modalityDevices, setModalityDevices] = useState(null); - const [selectedDevice, setSelectedDevice] = useState(null); - const authTxnIdLengthValue = openIDConnectService.getEsignetConfiguration( configurationKeys.authTxnIdLength @@ -68,91 +49,33 @@ export default function L1Biometrics({ const authTxnIdLength = parseInt(authTxnIdLengthValue); - // handle onChange event of the dropdown - const handleDeviceChange = (device) => { - setSelectedDevice(device); - }; - const handleInputChange = (e) => { setLoginState({ ...loginState, [e.target.id]: e.target.value }); }; - const submitHandler = (e) => { - e.preventDefault(); - startCapture(); - }; - - const startCapture = async () => { + /* authenticate method after removing startCapture + * which have capturing & authenticate as well + */ + const authenticateBiometricResponse = async (biometricResponse) => { setError(null); - - let transactionId = getSBIAuthTransactionId( - openIDConnectService.getTransactionId() - ); - - let vid = loginState["sbi_mosip-vid"]; - - if (selectedDevice === null) { - setError({ - errorCode: "device_not_found_msg", - }); - return; - } - - let biometricResponse = null; - - try { - setStatus({ - state: states.AUTHENTICATING, - msg: "capture_initiated_msg", - msgParam: { - modality: t(selectedDevice.type), - deviceModel: selectedDevice.model, - }, - }); - - biometricResponse = await capture_Auth( - host, - selectedDevice.port, - transactionId, - selectedDevice.specVersion, - selectedDevice.type, - selectedDevice.deviceId - ); - - let { errorCode, defaultMsg } = - validateBiometricResponse(biometricResponse); - - setStatus({ state: states.LOADED, msg: "" }); - - if (errorCode !== null) { + setStatus({ state: states.LOADED, msg: "" }); + const { errorCode } = validateBiometricResponse(biometricResponse); + + const vid = loginState["sbi_mosip-vid"]; + if (errorCode === null) { + try { + await Authenticate( + transactionId, + vid, + openIDConnectService.encodeBase64(biometricResponse["biometrics"]) + ); + } catch (error) { setError({ - prefix: "biometric_capture_failed_msg", - errorCode: errorCode, - defaultMsg: defaultMsg, + prefix: "authentication_failed_msg", + errorCode: error.message, + defaultMsg: error.message, }); - return; } - } catch (error) { - setError({ - prefix: "biometric_capture_failed_msg", - errorCode: error.message, - defaultMsg: error.message, - }); - return; - } - - try { - await Authenticate( - openIDConnectService.getTransactionId(), - vid, - openIDConnectService.encodeBase64(biometricResponse["biometrics"]) - ); - } catch (error) { - setError({ - prefix: "authentication_failed_msg", - errorCode: error.message, - defaultMsg: error.message, - }); } }; @@ -200,15 +123,11 @@ export default function L1Biometrics({ }; const Authenticate = async (transactionId, uin, bioValue) => { - let challengeType = challengeTypes.bio; - let challenge = bioValue; - let challengeFormat = challengeFormats.bio; - - let challengeList = [ + const challengeList = [ { - authFactorType: challengeType, - challenge: challenge, - format: challengeFormat, + authFactorType: challengeTypes.bio, + challenge: bioValue, + format: challengeFormats.bio, }, ]; @@ -225,7 +144,7 @@ export default function L1Biometrics({ setStatus({ state: states.LOADED, msg: "" }); - const { errors } = authenticateResponse; + const { response, errors } = authenticateResponse; if (errors != null && errors.length > 0) { setError({ @@ -240,7 +159,8 @@ export default function L1Biometrics({ let params = buildRedirectParams( nonce, state, - openIDConnectService.getOAuthDetails() + openIDConnectService.getOAuthDetails(), + response.consentAction ); navigate("/consent" + params, { @@ -249,81 +169,46 @@ export default function L1Biometrics({ } }; - const handleScan = (e) => { - e.preventDefault(); - scanDevices(); - }; - useEffect(() => { - scanDevices(); - }, []); - - const scanDevices = () => { - setError(null); - try { - setStatus({ - state: states.LOADING, - msg: "scanning_devices_msg", - }); - - mosipdisc_DiscoverDevicesAsync(host).then(() => { - setStatus({ state: states.LOADED, msg: "" }); - refreshDeviceList(); - }); - } catch (error) { - setError({ - prefix: "device_disc_failed", - errorCode: error.message, - defaultMsg: error.message, - }); - } - }; - - const refreshDeviceList = () => { - let deviceInfosPortsWise = getDeviceInfos(); - - if (!deviceInfosPortsWise) { - setModalityDevices(null); - setError({ - errorCode: "no_devices_found_msg", + let mosipProp = { + container: document.getElementById( + "secure-biometric-interface-integration" + ), + buttonLabel: "scan_and_verify", + transactionId: getSBIAuthTransactionId(transactionId), + sbiEnv: { + env: "Staging", + captureTimeout: 30, + irisBioSubtypes: "UNKNOWN", + fingerBioSubtypes: "UNKNOWN", + faceCaptureCount: 1, + faceCaptureScore: 70, + fingerCaptureCount: 1, + fingerCaptureScore: 70, + irisCaptureCount: 1, + irisCaptureScore: 70, + portRange: "4501-4512", + discTimeout: 15, + dinfoTimeout: 30, + domainUri: `${window.origin}`, + }, + langCode: i18n.language, + disable: true, + }; + + if (firstRender.current) { + firstRender.current = false; + init(mosipProp); + i18n.on("languageChanged", () => { + propChange({ langCode: i18n.language }); }); return; } - - let modalitydevices = []; - - Object.keys(deviceInfosPortsWise).map((port) => { - let deviceInfos = deviceInfosPortsWise[port]; - - deviceInfos?.forEach((deviceInfo) => { - let deviceDetail = { - port: port, - specVersion: deviceInfo.specVersion[0], - type: deviceInfo.digitalId.type, - deviceId: deviceInfo.deviceId, - model: deviceInfo.digitalId.model, - serialNo: deviceInfo.digitalId.serialNo, - text: deviceInfo.digitalId.make + "-" + deviceInfo.digitalId.model, - value: deviceInfo.digitalId.serialNo, - icon: modalityIconPath[deviceInfo.digitalId.type], - }; - - modalitydevices.push(deviceDetail); - }); + propChange({ + disable: !loginState["sbi_mosip-vid"].length, + onCapture: (e) => authenticateBiometricResponse(e), }); - - setModalityDevices(modalitydevices); - - if (modalitydevices.length === 0) { - setError({ - errorCode: "no_devices_found_msg", - }); - return; - } - - let selectedDevice = modalitydevices[0]; - setSelectedDevice(selectedDevice); - }; + }, [loginState]); return ( <> @@ -333,7 +218,7 @@ export default function L1Biometrics({ > {t("sign_in_with_biometric")} -
+
{inputFields.map((field) => ( )} - {(status.state === states.LOADED || - status.state === states.AUTHENTICATING) && - modalityDevices && ( - <> - {selectedDevice && ( - <> -
- -
-