diff --git a/README.md b/README.md index 47337203..209023e9 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,5 @@ One can use the esignet base image to test the new fixes in the plugin. There ar Either of the above 2 steps should be followed and finally set the "plugin_name_env" environment variable. With this setup, eSignet service should get started with the configured plugin. -## License +## License This project is licensed under the terms of [Mozilla Public License 2.0](LICENSE). diff --git a/mock-plugin/src/main/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImpl.java b/mock-plugin/src/main/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImpl.java index 470d5b00..71b831f4 100644 --- a/mock-plugin/src/main/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImpl.java +++ b/mock-plugin/src/main/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImpl.java @@ -10,11 +10,13 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; +import java.util.*; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -50,30 +52,26 @@ public class MockProfileRegistryPluginImpl implements ProfileRegistryPlugin { private static final String UTC_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private static final List ACTIONS = Arrays.asList("CREATE", "UPDATE"); - - @Value("${mosip.signup.mock.username.field:phone}") - private String usernameField; - @Value("#{'${mosip.signup.mock.mandatory-attributes.CREATE:}'.split(',')}") - private List requiredFieldsOnCreate; - - @Value("#{'${mosip.signup.mock.mandatory-attributes.UPDATE:}'.split(',')}") - private List requiredFieldsOnUpdate; - - @Value("#{'${mosip.signup.mock.lang-based-attributes:}'.split(',')}") - private List langBasedFields; + @Value("${mosip.signup.identifier.name:phone}") + private String identifierField; + //Endpoint to add/update identity data @Value("${mosip.signup.mock.identity.endpoint}") private String identityEndpoint; + //Endpoint to fetch identity data @Value("${mosip.signup.mock.get-identity.endpoint}") private String getIdentityEndpoint; @Value("${mosip.signup.mock.add-verified-claims.endpoint}") private String addVerifiedClaimsEndpoint; - @Value("${mosip.signup.mock.get-schema.endpoint}") - private String getSchemaEndpoint; + @Value("${mosip.signup.mock.identity-schema.endpoint}") + private String identitySchemaEndpoint; + + @Value("${mosip.signup.mock.ui-schema.endpoint}") + private String uiSchemaEndpoint; @Value("${mosip.signup.mock.face.biometric.field-name:encodedPhoto}") private String faceBiometricFieldName; @@ -91,26 +89,46 @@ public class MockProfileRegistryPluginImpl implements ProfileRegistryPlugin { @Autowired private ResourceLoader resourceLoader; + private volatile JsonSchema schema; + @Override public void validate(String action, ProfileDto profileDto) throws InvalidProfileException { - if (!ACTIONS.contains(action)) { + + if(schema == null) { + synchronized (this) { + ResponseWrapper responseWrapper = request(identitySchemaEndpoint, HttpMethod.GET, null, + new ParameterizedTypeReference>() { + }); + JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012); + schema = jsonSchemaFactory.getSchema(responseWrapper.getResponse()); + } + } + + if(!ACTIONS.contains(action)) { + log.error("Invalid action value : {}. Allowed values are CREATE and UPDATE", action); throw new InvalidProfileException(ErrorConstants.INVALID_ACTION); } - JsonNode inputJson = profileDto.getIdentity(); - List requiredFields = action.equals("CREATE") ? requiredFieldsOnCreate : requiredFieldsOnUpdate; - for (String fieldName : requiredFields) { - if (!fieldName.isEmpty() && (!inputJson.hasNonNull(fieldName) || (inputJson.get(fieldName).isArray() && inputJson.get(fieldName).isEmpty()))) { - log.error("Null value found in the required field of {}, required: {}", fieldName, requiredFields); - throw new InvalidProfileException("invalid_".concat(fieldName.toLowerCase())); + Set errors = schema.validate(profileDto.getIdentity()); + + for(ValidationMessage error : errors) { + log.error("Validation error for field {} with message {}", error.getInstanceLocation(), error.getMessage()); + String fieldName = error.getInstanceLocation().getNameCount() > 0 ? error.getInstanceLocation().getName(0) : + error.getProperty(); + if(action.equals("UPDATE") && error.getCode().equals("1028")) { + //Ignore required field validation errors for update action as in an update scenario, not all fields are mandatory + continue; } + throw new InvalidProfileException(fieldName != null ? "invalid_".concat(fieldName.toLowerCase()) : + "unknown_field"); } } @Override public ProfileResult createProfile(String requestId, ProfileDto profileDto) throws ProfileException { - if(usernameField != null && !profileDto.getIndividualId().equalsIgnoreCase(profileDto.getIdentity().get(usernameField).asText())) { - log.error("{} and userName mismatch", usernameField); + if(identifierField != null && (profileDto.getIdentity().hasNonNull(identifierField) + && !profileDto.getIndividualId().equalsIgnoreCase(profileDto.getIdentity().get(identifierField).asText()))) { + log.error("{} and userName mismatch", identifierField); throw new InvalidProfileException(ErrorConstants.IDENTIFIER_MISMATCH); } ObjectNode inputJson = (ObjectNode) profileDto.getIdentity(); @@ -253,9 +271,8 @@ private String getUTCDateTime() { @Override public JsonNode getUISpecification() { - ResponseWrapper responseWrapper = request(getSchemaEndpoint, HttpMethod.GET ,null, + ResponseWrapper responseWrapper = request(uiSchemaEndpoint, HttpMethod.GET ,null, new ParameterizedTypeReference>() {}); return responseWrapper.getResponse(); } - } diff --git a/mock-plugin/src/main/resources/application.properties b/mock-plugin/src/main/resources/application.properties index 504822c5..5f74b190 100644 --- a/mock-plugin/src/main/resources/application.properties +++ b/mock-plugin/src/main/resources/application.properties @@ -25,14 +25,11 @@ mosip.signup.mock.identity-verification.story-name=mock-idv-user-story.json ## File defined in the property `mosip.signup.mock.identity-verification.story-name` is loaded with below defined URL mosip.signup.mock.config-server-url=classpath: -mosip.signup.mock.get-schema.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity/ui-spec - -mosip.signup.mock.mandatory-attributes.CREATE=fullName,phone,password,preferredLang -mosip.signup.mock.mandatory-attributes.UPDATE= -mosip.signup.mock.username.field=phone mosip.signup.mock.identity.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity mosip.signup.mock.get-identity.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity/ +mosip.signup.mock.identity-schema.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity/identity-schema +mosip.signup.mock.ui-schema.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity/ui-spec mosip.signup.mock.add-verified-claims.endpoint=${mosip.esignet.mock.domain.url}/v1/mock-identity-system/identity/add-verified-claim ## Disable authz & authn with mock-plugin diff --git a/mock-plugin/src/test/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImplTest.java b/mock-plugin/src/test/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImplTest.java index a9736eb1..22543aa1 100644 --- a/mock-plugin/src/test/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImplTest.java +++ b/mock-plugin/src/test/java/io/mosip/signup/plugin/mock/service/MockProfileRegistryPluginImplTest.java @@ -55,69 +55,224 @@ public void init(){ ReflectionTestUtils.setField(mockProfileRegistryPlugin, "objectMapper",objectMapper); } + String IDENTITY_SCHEMA = "{\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n" + + " \"type\": \"object\",\n" + + " \"$defs\": {\n" + + " \"langField\": {\n" + + " \"type\": \"array\",\n" + + " \"items\": {\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"language\": {\n" + + " \"type\": \"string\"\n" + + " },\n" + + " \"value\": {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " },\n" + + " \"required\": [\n" + + " \"language\",\n" + + " \"value\"\n" + + " ],\n" + + " \"additionalProperties\": false\n" + + " }\n" + + " }\n" + + " },\n" + + " \"properties\": {\n" + + " \"individualId\": {\n" + + " \"type\": \"string\",\n" + + " \"pattern\": \"\\\\S\"\n" + + " },\n" + + " \"fullName\": {\n" + + " \"allOf\": [\n" + + " { \"$ref\": \"#/$defs/langField\" },\n" + + " {\n" + + " \"items\": {\n" + + " \"properties\": {\n" + + " \"value\": {\n" + + " \"pattern\": \"^(?=.*[^\\\\s])^(?:[a-zA-ZÀ-ÿ\\\\s]{1,40}|[ء-ي\\\\s٩ٱ-ڿﹰ-\\uFEFF\\u0600-ۿ]{1,40}|[ក-\\u17FF᧠-᧿ᨀ-\\u1A9F ]{1,40})$\"\n" + + " },\n" + + " \"language\": {\n" + + " \"type\": \"string\",\n" + + " \"enum\": [\"eng\",\"fra\",\"ara\"]\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"preferredLang\": {\n" + + " \"type\": \"string\",\n" + + " \"enum\": [\"eng\",\"fra\",\"ara\"],\n" + + " \"nullable\": true\n" + + " },\n" + + " \"phone\": {\n" + + " \"type\": \"string\",\n" + + " \"pattern\": \"^\\\\+[1-9]\\\\d{8,13}$\"\n" + + " },\n" + + " \"password\": {\n" + + " \"type\": \"string\",\n" + + " \"pattern\": \"^[A-Za-z__1-9]{6,9}\\\\d{1}$\"\n" + + " }\n" + + " },\n" + + " \"required\": [\n" + + " \"individualId\",\n" + + " \"fullName\",\n" + + " \"phone\",\n" + + " \"password\"\n" + + " ],\n" + + " \"additionalProperties\": false\n" + + "}"; + @Test public void validate_withValidActionAndProfileDto_thenPass() throws JsonProcessingException { - - List requiredField=new ArrayList<>(); - requiredField.add("phone"); - ReflectionTestUtils.setField(mockProfileRegistryPlugin, "requiredFieldsOnCreate", requiredField); + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identitySchemaEndpoint", "http://localhost:8080/"); String action = "CREATE"; - String phone="{ \"value\": \"7408001310\", \"essential\":true }"; - String verifiedClaims="[{\"verification\":{\"trust_framework\":{\"value\":\"income-tax\"}},\"claims\":{\"name\":null,\"email\":{\"essential\":0}}},{\"verification\":{\"trust_framework\":{\"value\":\"pwd\"}},\"claims\":{\"birthdate\":{\"essential\":true},\"address\":null}},{\"verification\":{\"trust_framework\":{\"value\":\"cbi\"}},\"claims\":{\"gender\":{\"essential\":true},\"email\":{\"essential\":true}}}]"; - JsonNode addressNode = objectMapper.readValue(phone, JsonNode.class); - JsonNode verifiedClaimNode = objectMapper.readValue(verifiedClaims, JsonNode.class); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(objectMapper.readTree(IDENTITY_SCHEMA)); + ResponseEntity> responseEntity=new ResponseEntity<>(wrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.eq("http://localhost:8080/"), + Mockito.any(HttpMethod.class), + Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() { + }))).thenReturn(responseEntity); - Map userinfoMap = new HashMap<>(); - userinfoMap.put("phone", addressNode); - userinfoMap.put("verified_claims", verifiedClaimNode); - JsonNode mockIdentity=objectMapper.valueToTree(userinfoMap); + String userinfo = "{\"individualId\" : \"1234567890\",\"phone\" : \"+9134567890\", \"fullName\": [{\"value\": \"John Doe\", \"language\": \"eng\"}], \"preferredLang\": \"eng\", \"password\": \"pas_swo3\"}"; + JsonNode mockIdentity=objectMapper.readTree(userinfo); ProfileDto profileDto = new ProfileDto(); - profileDto.setIndividualId("individualId"); + profileDto.setIndividualId("1234567890"); profileDto.setIdentity(mockIdentity); - mockProfileRegistryPlugin.validate(action, profileDto); } @Test - public void validate_withInValidRequiredField_thenFail() throws JsonProcessingException { - - List requiredField=new ArrayList<>(); - requiredField.add("email"); - ReflectionTestUtils.setField(mockProfileRegistryPlugin, "requiredFieldsOnCreate", requiredField); + public void validate_withInvalidRequiredField_thenFail() throws JsonProcessingException { + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identitySchemaEndpoint", "http://localhost:8080/"); String action = "CREATE"; - String phone="{ \"value\": \"7408001310\", \"essential\":true }"; - String verifiedClaims="[{\"verification\":{\"trust_framework\":{\"value\":\"income-tax\"}},\"claims\":{\"name\":null,\"email\":{\"essential\":0}}},{\"verification\":{\"trust_framework\":{\"value\":\"pwd\"}},\"claims\":{\"birthdate\":{\"essential\":true},\"address\":null}},{\"verification\":{\"trust_framework\":{\"value\":\"cbi\"}},\"claims\":{\"gender\":{\"essential\":true},\"email\":{\"essential\":true}}}]"; - JsonNode addressNode = objectMapper.readValue(phone, JsonNode.class); - JsonNode verifiedClaimNode = objectMapper.readValue(verifiedClaims, JsonNode.class); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(objectMapper.readTree(IDENTITY_SCHEMA)); + ResponseEntity> responseEntity=new ResponseEntity<>(wrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.eq("http://localhost:8080/"), + Mockito.any(HttpMethod.class), + Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() { + }))).thenReturn(responseEntity); - Map userinfoMap = new HashMap<>(); - userinfoMap.put("phone", addressNode); - userinfoMap.put("verified_claims", verifiedClaimNode); - JsonNode mockIdentity=objectMapper.valueToTree(userinfoMap); + String userinfo = "{\"individualId\" : \"1234567890\", \"fullName\": [{\"value\": \"John Doe\", \"language\": \"eng\"}], \"preferredLang\": \"eng\", \"password\": \"pas_swo3\"}"; + JsonNode mockIdentity=objectMapper.readTree(userinfo); ProfileDto profileDto = new ProfileDto(); - profileDto.setIndividualId("individualId"); + profileDto.setIndividualId("1234567890"); profileDto.setIdentity(mockIdentity); try{ mockProfileRegistryPlugin.validate(action, profileDto); Assert.fail(); }catch (InvalidProfileException e){ - Assert.assertEquals(e.getMessage(),"invalid_email"); + Assert.assertEquals("invalid_phone", e.getErrorCode()); + } + } + + @Test + public void validate_withInvalidFieldValue_thenFail() throws JsonProcessingException { + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identitySchemaEndpoint", "http://localhost:8080/"); + String action = "CREATE"; + + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(objectMapper.readTree(IDENTITY_SCHEMA)); + ResponseEntity> responseEntity=new ResponseEntity<>(wrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.eq("http://localhost:8080/"), + Mockito.any(HttpMethod.class), + Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() { + }))).thenReturn(responseEntity); + + String userinfo = "{\"individualId\" : \"1234567890\", \"fullName\": [{\"value\": \"John Doe\", \"language\": \"eng\"}], \"preferredLang\": \"khm\", \"password\": \"pas_swo3\"}"; + JsonNode mockIdentity=objectMapper.readTree(userinfo); + ProfileDto profileDto = new ProfileDto(); + profileDto.setIndividualId("1234567890"); + profileDto.setIdentity(mockIdentity); + + try{ + mockProfileRegistryPlugin.validate(action, profileDto); + Assert.fail(); + }catch (InvalidProfileException e) { + Assert.assertEquals("invalid_preferredlang", e.getErrorCode()); + } + } + + @Test + public void validate_patternNotMatching_thenFail() throws JsonProcessingException { + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identitySchemaEndpoint", "http://localhost:8080/"); + String action = "CREATE"; + + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(objectMapper.readTree(IDENTITY_SCHEMA)); + ResponseEntity> responseEntity=new ResponseEntity<>(wrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.eq("http://localhost:8080/"), + Mockito.any(HttpMethod.class), + Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() { + }))).thenReturn(responseEntity); + + String userinfo = "{\"individualId\" : \"1234567890\",\"phone\" : \"+0134567890\", \"fullName\": [{\"value\": \"John Doe\", \"language\": \"eng\"}], \"preferredLang\": \"eng\", \"password\": \"pas_swo3\"}"; + JsonNode mockIdentity=objectMapper.readTree(userinfo); + ProfileDto profileDto = new ProfileDto(); + profileDto.setIndividualId("1234567890"); + profileDto.setIdentity(mockIdentity); + + try{ + mockProfileRegistryPlugin.validate(action, profileDto); + Assert.fail(); + }catch (InvalidProfileException e) { + Assert.assertEquals("invalid_phone", e.getErrorCode()); } + } + + @Test + public void validate_withInvalidUpdateData_thenFail() throws JsonProcessingException { + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identitySchemaEndpoint", "http://localhost:8080/"); + String action = "UPDATE"; + + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(objectMapper.readTree(IDENTITY_SCHEMA)); + ResponseEntity> responseEntity=new ResponseEntity<>(wrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.eq("http://localhost:8080/"), + Mockito.any(HttpMethod.class), + Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() { + }))).thenReturn(responseEntity); + String userinfo = "{\"individualId\": \"1234567890\", \"password\": \"@password123\"}"; + JsonNode mockIdentity=objectMapper.readTree(userinfo); + ProfileDto profileDto = new ProfileDto(); + profileDto.setIndividualId("1234567890"); + profileDto.setIdentity(mockIdentity); + + try{ + mockProfileRegistryPlugin.validate(action, profileDto); + Assert.fail(); + }catch (InvalidProfileException e) { + Assert.assertEquals("invalid_password", e.getErrorCode()); + } } @Test public void createProfile_withValidRequestAndProfileDto_thenPass() throws ProfileException { // Arrange ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identityEndpoint","http://localhost:8080/"); - ReflectionTestUtils.setField(mockProfileRegistryPlugin, "usernameField","individualId"); + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identifierField","individualId"); Map identityData = new HashMap<>(); identityData.put("individualId","1234567890"); JsonNode mockIdentity = objectMapper.valueToTree(identityData); @@ -147,7 +302,7 @@ public void createProfile_withValidRequestAndProfileDto_thenPass() throws Profil @Test public void createProfile_withInValidRequestAndProfileDto_thenFail() throws ProfileException { // Arrange - ReflectionTestUtils.setField(mockProfileRegistryPlugin, "usernameField","individualId"); + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identifierField","individualId"); Map identityData=new HashMap<>(); identityData.put("individualId","1234567890"); JsonNode mockIdentity = objectMapper.valueToTree(identityData); @@ -165,12 +320,12 @@ public void createProfile_withInValidRequestAndProfileDto_thenFail() throws Prof public void createProfile_withFacePhoto_thenPass() throws ProfileException { // Arrange ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identityEndpoint","http://localhost:8080/"); - ReflectionTestUtils.setField(mockProfileRegistryPlugin, "usernameField","individualId"); + ReflectionTestUtils.setField(mockProfileRegistryPlugin, "identifierField","individualId"); ReflectionTestUtils.setField(mockProfileRegistryPlugin, "faceBiometricFieldName","encodedPhoto"); ReflectionTestUtils.setField(mockProfileRegistryPlugin, "faceBiometricValuePrefix","data:image/jpeg;base64,"); ObjectNode mockIdentity = mock(ObjectNode.class); - Mockito.when(mockIdentity.get("individualId")).thenReturn(objectMapper.valueToTree("1234567890")); + //Mockito.when(mockIdentity.get("individualId")).thenReturn(objectMapper.valueToTree("1234567890")); Mockito.when(mockIdentity.hasNonNull("encodedPhoto")).thenReturn(true); ObjectNode encodedPhotoNode = mock(ObjectNode.class); Mockito.when(mockIdentity.get("encodedPhoto")).thenReturn(encodedPhotoNode) diff --git a/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/dto/Password.java b/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/dto/Password.java index 5ea1d1ad..4331b085 100644 --- a/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/dto/Password.java +++ b/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/dto/Password.java @@ -14,6 +14,7 @@ public class Password { private String hash; private String salt; + private String value; // Added for compatible with 1.3.0 IDRepo @Data @AllArgsConstructor diff --git a/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImpl.java b/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImpl.java index 2fc21510..1b275787 100644 --- a/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImpl.java +++ b/mosip-identity-plugin/src/main/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImpl.java @@ -12,8 +12,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.PathNotFoundException; import io.micrometer.core.annotation.Timed; import io.mosip.esignet.core.util.IdentityProviderUtil; import io.mosip.kernel.core.util.HMACUtils2; @@ -35,7 +33,6 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -71,19 +68,13 @@ public class IdrepoProfileRegistryPluginImpl implements ProfileRegistryPlugin { @Value("#{'${mosip.signup.idrepo.default.selected-handles:phone}'.split(',')}") private List defaultSelectedHandles; - - @Value("${mosip.signup.idrepo.identifier-field:phone}") + + @Value("${mosip.signup.identifier.name}") private String identifierField; @Value("${mosip.signup.idrepo.schema-url}") private String schemaUrl; - @Value("#{'${mosip.kernel.idobjectvalidator.mandatory-attributes.id-repository.new-registration:}'.split(',')}") - private List requiredFields; - - @Value("#{'${mosip.kernel.idobjectvalidator.mandatory-attributes.id-repository.update-uin:}'.split(',')}") - private List requiredUpdateFields; - @Value("${mosip.signup.idrepo.add-identity.request.id}") private String addIdentityRequestID; @@ -126,6 +117,18 @@ public class IdrepoProfileRegistryPluginImpl implements ProfileRegistryPlugin { @Value("${mosip.signup.idrepo.uin.length:10}") private int uinLength; + @Value("${mosip.signup.mosipid.get-ui-spec.endpoint}") + private String uiSpecUrl; + + @Value("${mosip.signup.mosipid.uispec.json.pointer:/0/jsonSpec/0/spec}") + private String uiSpecJsonPointer; + + @Value("${mosip.signup.mosipid.dynamic-fields.endpoint}") + private String dynamicFieldsBaseUrl; + + @Value("${mosip.signup.mosipid.doc-types-category.endpoint}") + private String docTypesAndCategoryBaseUrl; + @Autowired @Qualifier("selfTokenRestTemplate") private RestTemplate restTemplate; @@ -139,36 +142,6 @@ public class IdrepoProfileRegistryPluginImpl implements ProfileRegistryPlugin { @Autowired private BiometricUtil biometricUtil; - @Value("${mosip.signup.mosipid.get-ui-spec.endpoint}") - private String uiSpecUrl; - - @Value("${mosip.signup.mosipid.uispec.schema-jsonpath:$[0].jsonSpec[0].spec.schema}") - private String schemaJsonpath; - - @Value("${mosip.signup.mosipid.uispec.allowedvalues-jsonpath:$[0].jsonSpec[0].spec.allowedValues}") - private String allowedValuesJsonpath; - - @Value("${mosip.signup.mosipid.uispec.i18values-jsonpath:$[0].jsonSpec[0].spec.i18nValues}") - private String i18nValuesJsonpath; - - @Value("${mosip.signup.mosipid.uispec.i18values-errors-jsonpath:$[0].jsonSpec[0].spec.i18nValues.errors}") - private String i18nValuesErrorJsonpath; - - @Value("${mosip.signup.mosipid.uispec.maxuploadfilesize-jsonpath:$[0].jsonSpec[0].spec.maxUploadFileSize}") - private String maxUploadFileSizeJsonpath; - - @Value("${mosip.signup.mosipid.uispec.errors-jsonpath:$[0].jsonSpec[0].spec.errors}") - private String errorsJsonpath; - - @Value("#{${mosip.signup.mosipid.uispec.errors:null}}") - private Map errorsFromConfig = new HashMap<>(); - - @Value("${mosip.signup.mosipid.dynamic-fields.endpoint}") - private String dynamicFieldsBaseUrl; - - @Value("${mosip.signup.mosipid.doc-types-category.endpoint}") - private String docTypesAndCategoryBaseUrl; - @PostConstruct public void init() { @@ -177,191 +150,6 @@ public void init() { registry.registerServiceProvider(new J2KImageReaderSpi()); } - /** - * Read the i18nValues from the UI-spec schema - * @param responseJson the response json from ${mosip.signup.mosipid.get-ui-spec.endpoint} - * @return i18nValues - */ - private ObjectNode readI18nValues(String responseJson) { - Object i18nValueResponse; - ObjectNode i18nValues; - try { - i18nValueResponse = JsonPath.read(responseJson, i18nValuesJsonpath); - i18nValues = objectMapper.convertValue(i18nValueResponse, ObjectNode.class); - i18nValues.set("errors", objectMapper.valueToTree(readErrors(responseJson, i18nValuesErrorJsonpath))); - } catch (PathNotFoundException e) { - log.error("i18nValues not found in schema"); - i18nValues = objectMapper.createObjectNode(); - } - return i18nValues; - } - - /** - * Read the allowed values from master UI-spec and if not found read it from master data. - * @param responseJson the response json from ${mosip.signup.mosipid.get-ui-spec.endpoint} - * @return allowedValues - */ - private JsonNode readAllowedValues(String responseJson) { - JsonNode allowedValues; - ObjectNode allowedValuesFromSpec = objectMapper.convertValue(JsonPath.read(responseJson, allowedValuesJsonpath), ObjectNode.class); - if (allowedValuesFromSpec != null && !allowedValuesFromSpec.isEmpty()) { - allowedValues = allowedValuesFromSpec; //allowed values from UI-Spec - } else { - allowedValues = generateAllowedValues(); //allowed values from master-data - } - return allowedValues; - } - - /** - * Read the errors from UI-spec if not present, from config - * @param responseJson the response json from ${mosip.signup.mosipid.get-ui-spec.endpoint} - * @param jsonpath errors path inside schema - * @return errors - */ - private Object readErrors(String responseJson, String jsonpath) { - Object errors; - try { - errors = JsonPath.read(responseJson, jsonpath); - } catch (PathNotFoundException e) { - errors = errorsFromConfig; - } - return errors; - } - - /** - * Reads max upload file size from the UI-spec - * @param responseJson the response json from ${mosip.signup.mosipid.get-ui-spec.endpoint} - * @return maxUploadFileSize - */ - private Object readMaxUploadFileSize(String responseJson) { - Object maxUploadFileSize; - try { - maxUploadFileSize = JsonPath.read(responseJson, maxUploadFileSizeJsonpath); - } catch (PathNotFoundException e) { - log.error("maxUploadFileSize not found in schema, setting to default"); - maxUploadFileSize = 5242880; - } - return maxUploadFileSize; - } - - /** - * Generate combined JsonNode from List dynamicFields and List documentCategories - * @return JsonNode containing the allowed values. - */ - public JsonNode generateAllowedValues() { - ObjectNode result = objectMapper.createObjectNode(); - fetchAndProcessDynamicFields(result); - fetchAndProcessDocTypesAndCategories(result); - return result; - } - - private String buildDynamicFieldsUrl(int pageNumber, int pageSize) { - return String.format(dynamicFieldsBaseUrl, pageNumber, pageSize); - } - - /** - * Fetch and process document types and categories - */ - private void fetchAndProcessDocTypesAndCategories(ObjectNode result) { - ResponseEntity response = restTemplate.getForEntity(docTypesAndCategoryBaseUrl, JsonNode.class); - JsonNode responseBody = response.getBody(); - - if (responseBody != null && responseBody.has("response")) { - JsonNode data = responseBody.get("response").get("documentCategories"); - if (data != null && data.isArray()) { - for (JsonNode item : data) { - if (!item.has("isActive") || !item.get("isActive").asBoolean()) continue; - - String categoryCode = item.hasNonNull("code") ? item.get("code").asText() : null; - String langCode = item.hasNonNull("langCode") ? item.get("langCode").asText() : null; - JsonNode documentTypes = item.get("documentTypes"); - - if (categoryCode == null || langCode == null || documentTypes == null || !documentTypes.isArray()) - continue; - - ObjectNode docTypeMap = (ObjectNode) result.get(categoryCode); - if (docTypeMap == null) { - docTypeMap = objectMapper.createObjectNode(); - result.set(categoryCode, docTypeMap); - } - - for (JsonNode docType : documentTypes) { - String docTypeCode = docType.hasNonNull("code") ? docType.get("code").asText() : null; - String docTypeName = docType.hasNonNull("name") ? docType.get("name").asText() : null; - if (docTypeCode == null || docTypeName == null) continue; - - ObjectNode langMap = (ObjectNode) docTypeMap.get(docTypeCode); - if (langMap == null) { - langMap = objectMapper.createObjectNode(); - docTypeMap.set(docTypeCode, langMap); - } - langMap.put(langCode, docTypeName); - } - } - } - } - } - - /** - * Fetch and processes the dynamic fields JSON list and adds their structured data into the provided ObjectNode. - * @param result The ObjectNode where data is accumulated - */ - private void fetchAndProcessDynamicFields(ObjectNode result) { - int pageNumber = 0; - int pageSize = 10; - int totalPages = 1; - int totalItems = 0; - - while (pageNumber < totalPages) { - String url = buildDynamicFieldsUrl(pageNumber, pageSize); - ResponseEntity response = restTemplate.getForEntity(url, JsonNode.class); - JsonNode responseBody = response.getBody(); - if (responseBody != null && responseBody.has("response")) { - JsonNode responseNode = responseBody.get("response"); - if (pageNumber == 0) { - totalPages = objectMapper.convertValue(responseNode.get("totalPages"), Integer.class); - totalItems = objectMapper.convertValue(responseNode.get("totalItems"), Integer.class); - } - JsonNode data = responseNode.get("data"); - if (data != null && data.isArray()) { - for (JsonNode item : data) { - if (!item.has("isActive") || !item.get("isActive").asBoolean()) continue; - - String name = item.hasNonNull("name") ? item.get("name").asText() : null; - String lang = item.hasNonNull("langCode") ? item.get("langCode").asText() : null; - JsonNode fieldValues = item.get("fieldVal"); - - if (name == null || lang == null || fieldValues == null || !fieldValues.isArray()) continue; - - ObjectNode nameNode = (ObjectNode) result.get(name); - if (nameNode == null) { - nameNode = objectMapper.createObjectNode(); - result.set(name, nameNode); - } - - for (JsonNode fv : fieldValues) { - String code = fv.hasNonNull("code") ? fv.get("code").asText() : null; - String value = fv.hasNonNull("value") ? fv.get("value").asText() : null; - if (code == null || value == null) continue; - - ObjectNode langMap = (ObjectNode) nameNode.get(code); - if (langMap == null) { - langMap = objectMapper.createObjectNode(); - nameNode.set(code, langMap); - } - langMap.put(lang, value); - } - } - } - } - pageNumber++; - int remainingItems = totalItems - (pageNumber * pageSize); - if (remainingItems < pageSize) { - pageSize = remainingItems; - } - } - } - @Override public void validate(String action, ProfileDto profileDto) throws InvalidProfileException { @@ -534,26 +322,25 @@ public boolean isMatch(JsonNode identity, JsonNode inputChallenge) { @Override public JsonNode getUISpecification() { - String responseJson = request(uiSpecUrl, HttpMethod.GET, null, new ParameterizedTypeReference>() { - }) - .getResponse() - .toString(); - Object schema = JsonPath.read(responseJson, schemaJsonpath); - Object errors = readErrors(responseJson, errorsJsonpath); - ObjectNode i18nValues = readI18nValues(responseJson); - JsonNode allowedValues = readAllowedValues(responseJson); - Object maxUploadFileSize = readMaxUploadFileSize(responseJson); - - return objectMapper.valueToTree( - Map.ofEntries( - Map.entry("schema", schema), - Map.entry("errors", errors), - Map.entry("i18nValues", i18nValues), - Map.entry("language", Map.of("mandatory", mandatoryLanguages, "optional", optionalLanguages)), - Map.entry("allowedValues", allowedValues), - Map.entry("maxUploadFileSize", maxUploadFileSize) - ) - ); + JsonNode responseJson = request(uiSpecUrl, HttpMethod.GET, null, new ParameterizedTypeReference>() { + }).getResponse(); + + JsonNode extractedUiSpec = responseJson.at(uiSpecJsonPointer); + if (extractedUiSpec.isMissingNode() || extractedUiSpec.isNull() || !extractedUiSpec.isObject()) { + log.error("UI Spec is missing in the response from {} at json pointer {}", uiSpecUrl, uiSpecJsonPointer); + return objectMapper.createObjectNode(); + } + ObjectNode uiSpec = (ObjectNode) extractedUiSpec; + + uiSpec.putIfAbsent("language", objectMapper.valueToTree(Map.of("mandatory", mandatoryLanguages, + "optional", optionalLanguages))); + + if(!uiSpec.has("allowedValues")) + uiSpec.putIfAbsent("allowedValues", fetchAllowedValuesFromMasterDataService()); + + uiSpec.putIfAbsent("maxUploadFileSize", objectMapper.valueToTree(5242880)); //5MB default max upload file size + uiSpec.putIfAbsent("resetPasswordChallengeFields", objectMapper.valueToTree(List.of("fullName"))); + return uiSpec; } private SchemaResponse getSchemaJson(double version) throws ProfileException { @@ -598,7 +385,7 @@ private Password generateSaltedHash(String password) throws ProfileException { if (!StringUtils.isEmpty(responseWrapper.getResponse().getHashValue()) && !StringUtils.isEmpty(responseWrapper.getResponse().getSalt())) { return new Password(responseWrapper.getResponse().getHashValue(), - responseWrapper.getResponse().getSalt()); + responseWrapper.getResponse().getSalt(), responseWrapper.getResponse().getHashValue()); // Added for compatible with 1.3.0 IDRepo ('https://github.com/mosip/id-repository/blob/v1.3.0/id-repository/id-repository-identity-service/src/main/java/io/mosip/idrepository/identity/service/impl/IdRepoServiceImpl.java#L508') } log.error("Failed to generate salted hash {}", responseWrapper.getResponse()); throw new ProfileException(REQUEST_FAILED); @@ -837,4 +624,116 @@ private ArrayNode buildDocuments(JsonNode inputJson) { return documents; } + + /** + * Generate combined JsonNode from List dynamicFields and List documentCategories + * @return JsonNode containing the allowed values. + */ + public JsonNode fetchAllowedValuesFromMasterDataService() { + ObjectNode result = objectMapper.createObjectNode(); + fetchAndProcessDynamicFields(result); + fetchAndProcessDocTypesAndCategories(result); + return result; + } + + private String buildDynamicFieldsUrl(int pageNumber, int pageSize) { + return String.format(dynamicFieldsBaseUrl, pageNumber, pageSize); + } + + /** + * Fetch and process document types and categories + */ + private void fetchAndProcessDocTypesAndCategories(ObjectNode result) { + ResponseWrapper response = request(docTypesAndCategoryBaseUrl, HttpMethod.GET, null, + new ParameterizedTypeReference>() {}); + if (!response.getResponse().isNull()) { + JsonNode data = response.getResponse().get("documentCategories"); + if (data != null && data.isArray()) { + for (JsonNode item : data) { + if (!item.has("isActive") || !item.get("isActive").asBoolean()) continue; + + String categoryCode = item.hasNonNull("code") ? item.get("code").asText() : null; + String langCode = item.hasNonNull("langCode") ? item.get("langCode").asText() : null; + JsonNode documentTypes = item.get("documentTypes"); + + if (categoryCode == null || langCode == null || documentTypes == null || !documentTypes.isArray()) + continue; + + ObjectNode docTypeMap = (ObjectNode) result.get(categoryCode); + if (docTypeMap == null) { + docTypeMap = objectMapper.createObjectNode(); + result.set(categoryCode, docTypeMap); + } + + for (JsonNode docType : documentTypes) { + String docTypeCode = docType.hasNonNull("code") ? docType.get("code").asText() : null; + String docTypeName = docType.hasNonNull("name") ? docType.get("name").asText() : null; + if (docTypeCode == null || docTypeName == null) continue; + + ObjectNode langMap = (ObjectNode) docTypeMap.get(docTypeCode); + if (langMap == null) { + langMap = objectMapper.createObjectNode(); + docTypeMap.set(docTypeCode, langMap); + } + langMap.put(langCode, docTypeName); + } + } + } + } + } + + /** + * Fetch and processes the dynamic fields JSON list and adds their structured data into the provided ObjectNode. + * @param result The ObjectNode where data is accumulated + */ + private void fetchAndProcessDynamicFields(ObjectNode result) { + int pageNumber = 0; + int pageSize = 10; + int totalPages = 1; + int totalItems = 0; + + while (pageNumber < totalPages) { + String url = buildDynamicFieldsUrl(pageNumber, pageSize); + ResponseWrapper response = request(url, HttpMethod.GET, null, new ParameterizedTypeReference>() {}); + if (!response.getResponse().isNull()) { + JsonNode responseNode = response.getResponse(); + if (pageNumber == 0) { + totalPages = Math.max(1, responseNode.path("totalPages").asInt(1)); + } + JsonNode data = responseNode.get("data"); + if (data != null && data.isArray()) { + for (JsonNode item : data) { + if (!item.has("isActive") || !item.get("isActive").asBoolean()) continue; + + String name = item.hasNonNull("name") ? item.get("name").asText() : null; + String lang = item.hasNonNull("langCode") ? item.get("langCode").asText() : null; + JsonNode fieldValues = item.get("fieldVal"); + + if (name == null || lang == null || fieldValues == null || !fieldValues.isArray()) continue; + + ObjectNode nameNode = (ObjectNode) result.get(name); + if (nameNode == null) { + nameNode = objectMapper.createObjectNode(); + result.set(name, nameNode); + } + + for (JsonNode fv : fieldValues) { + String code = fv.hasNonNull("code") ? fv.get("code").asText() : null; + String value = fv.hasNonNull("value") ? fv.get("value").asText() : null; + if (code == null || value == null) continue; + + ObjectNode langMap = (ObjectNode) nameNode.get(code); + if (langMap == null) { + langMap = objectMapper.createObjectNode(); + nameNode.set(code, langMap); + } + langMap.put(lang, value); + } + } + } + } + pageNumber++; + } + } + } diff --git a/mosip-identity-plugin/src/main/resources/application.properties b/mosip-identity-plugin/src/main/resources/application.properties index 7449929a..41ed8a64 100644 --- a/mosip-identity-plugin/src/main/resources/application.properties +++ b/mosip-identity-plugin/src/main/resources/application.properties @@ -5,6 +5,18 @@ mosip.esignet.integration.authenticator=IdaAuthenticatorImpl mosip.esignet.integration.key-binder=IdaKeyBinderImpl mosip.esignet.integration.audit-plugin=IdaAuditPluginImpl +mosip.esignet.ida.auth.domain=${MOSIP_API_INTERNAL_HOST:http://ida-auth.ida} +mosip.esignet.ida.otp.domain=${MOSIP_API_INTERNAL_HOST:http://ida-otp.ida} +mosip.esignet.ida.internal.domain=${MOSIP_API_INTERNAL_HOST:http://ida-internal.ida} +mosip.esignet.authmanager.domain=${MOSIP_API_INTERNAL_HOST:http://authmanager.kernel} +mosip.esignet.auditmanager.domain=${MOSIP_API_INTERNAL_HOST:http://auditmanager.kernel} +mosip.esignet.masterdata.domain=${MOSIP_API_INTERNAL_HOST:http://masterdata.kernel} +mosip.signup.idrepo.domain=${MOSIP_API_INTERNAL_HOST:http://identity.idrepo} +mosip.signup.keymanager.domain=${MOSIP_API_INTERNAL_HOST:http://keymanager.keymanager} +mosip.signup.credreq.domain=${MOSIP_API_INTERNAL_HOST:http://credentialrequest.idrepo} +mosip.signup.idgenerator.domain=${MOSIP_API_INTERNAL_HOST:http://idgenerator.kernel} +mosip.signup.fileserver.domain=${MOSIP_API_INTERNAL_HOST:http://mosip-file-server.mosip-file-server} + # IDA integration props mosip.esignet.authenticator.ida-auth-id=mosip.identity.kycauth mosip.esignet.authenticator.ida-exchange-id=mosip.identity.kycexchange @@ -12,19 +24,16 @@ mosip.esignet.authenticator.ida-send-otp-id=mosip.identity.otp mosip.esignet.authenticator.ida-version=1.0 mosip.esignet.authenticator.ida.misp-license-key=${mosip.esignet.misp.key} mosip.esignet.authenticator.ida-domainUri=${mosip.esignet.domain.url} -mosip.esignet.ida.auth.url=${IDA_AUTH_URL:http://ida-auth.ida} -mosip.esignet.ida.otp.url=${IDA_OTP_URL:http://ida-otp.ida} -mosip.esignet.ida.internal.url=${IDA_INTERNAL_URL:http://ida-internal.ida} -mosip.esignet.authenticator.ida.cert-url=http://mosip-file-server.mosip-file-server/mosip-certs/ida-partner.cer -mosip.esignet.authenticator.ida.kyc-auth-url=${mosip.esignet.ida.auth.url}/idauthentication/v1/kyc-auth/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.authenticator.ida.kyc-auth-url-v2=${mosip.esignet.ida.auth.url}/idauthentication/v1/kyc-auth/v2/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.authenticator.ida.kyc-exchange-url=${mosip.esignet.ida.auth.url}/idauthentication/v1/kyc-exchange/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.authenticator.ida.kyc-exchange-url-v2=${mosip.esignet.ida.auth.url}/idauthentication/v1/kyc-exchange/v2/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.authenticator.ida.send-otp-url=${mosip.esignet.ida.otp.url}/idauthentication/v1/otp/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.binder.ida.key-binding-url=${mosip.esignet.ida.auth.url}/idauthentication/v1/identity-key-binding/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ -mosip.esignet.authenticator.ida.get-certificates-url=${mosip.esignet.ida.internal.url}/idauthentication/v1/internal/getAllCertificates -mosip.esignet.authenticator.ida.auth-token-url=http://authmanager.kernel/v1/authmanager/authenticate/clientidsecretkey -mosip.esignet.authenticator.ida.audit-manager-url=http://auditmanager.kernel/v1/auditmanager/audits +mosip.esignet.authenticator.ida.cert-url=${mosip.signup.fileserver.domain}/mosip-certs/ida-partner.cer +mosip.esignet.authenticator.ida.kyc-auth-url=${mosip.esignet.ida.auth.domain}/idauthentication/v1/kyc-auth/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.authenticator.ida.kyc-auth-url-v2=${mosip.esignet.ida.auth.domain}/idauthentication/v1/kyc-auth/v2/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.authenticator.ida.kyc-exchange-url=${mosip.esignet.ida.auth.domain}/idauthentication/v1/kyc-exchange/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.authenticator.ida.kyc-exchange-url-v2=${mosip.esignet.ida.auth.domain}/idauthentication/v1/kyc-exchange/v2/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.authenticator.ida.send-otp-url=${mosip.esignet.ida.otp.domain}/idauthentication/v1/otp/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.binder.ida.key-binding-url=${mosip.esignet.ida.auth.domain}/idauthentication/v1/identity-key-binding/delegated/${mosip.esignet.authenticator.ida.misp-license-key}/ +mosip.esignet.authenticator.ida.get-certificates-url=${mosip.esignet.ida.internal.domain}/idauthentication/v1/internal/getAllCertificates +mosip.esignet.authenticator.ida.auth-token-url=${mosip.esignet.authmanager.domain}/v1/authmanager/authenticate/clientidsecretkey +mosip.esignet.authenticator.ida.audit-manager-url=${mosip.esignet.auditmanager.domain}/v1/auditmanager/audits mosip.esignet.authenticator.ida.client-id=mosip-ida-client mosip.esignet.authenticator.ida.secret-key=${mosip.ida.client.secret} mosip.esignet.authenticator.ida.app-id=ida @@ -36,25 +45,22 @@ mosip.signup.integration.impl.basepackage=io.mosip.signup.plugin.mosipid,io.mosi mosip.kernel.xsdstorage-uri=classpath: mosip.kernel.xsdfile=mosip-cbeff.xsd mosip.signup.integration.profile-registry-plugin=MOSIPProfileRegistryPluginImpl -mosip.signup.idrepo.schema-url=http://masterdata.kernel/v1/masterdata/idschema/latest?schemaVersion= - -mosip.signup.idrepo.url=${IDREPO_IDENTITY_URL:http://identity.idrepo} +mosip.signup.idrepo.schema-url=${mosip.esignet.masterdata.domain}/v1/masterdata/idschema/latest?schemaVersion= mosip.signup.idrepo.uin.length=10 -mosip.signup.idrepo.get-identity.endpoint=${mosip.signup.idrepo.url}/idrepository/v1/identity/idvid/ -mosip.signup.idrepo.identity.endpoint=${mosip.signup.idrepo.url}/idrepository/v1/identity/v2/ -mosip.signup.idrepo.generate-hash.endpoint=http://keymanager.keymanager/v1/keymanager/generateArgon2Hash -mosip.signup.idrepo.get-uin.endpoint=http://idgenerator.kernel/v1/idgenerator/uin -mosip.signup.idrepo.get-status.endpoint=http://credentialrequest.idrepo/v1/credentialrequest/get/ +mosip.signup.idrepo.get-identity.endpoint=${mosip.signup.idrepo.domain}/idrepository/v1/identity/idvid/ +mosip.signup.idrepo.identity.endpoint=${mosip.signup.idrepo.domain}/idrepository/v1/identity/v2/ +mosip.signup.idrepo.generate-hash.endpoint=${mosip.signup.keymanager.domain}/v1/keymanager/generateArgon2Hash +mosip.signup.idrepo.get-uin.endpoint=${mosip.signup.idgenerator.domain}/v1/idgenerator/uin +mosip.signup.idrepo.get-status.endpoint=${mosip.signup.credreq.domain}/v1/credentialrequest/get/ # This is the url to fetch ui-spec from masterdata - 'esignet-signup' in the url should match with domain name of the schema in the masterdata. -mosip.signup.mosipid.get-ui-spec.endpoint=http://masterdata.kernel/v1/masterdata/uispec/esignet-signup/latest?type=schema -mosip.signup.mosipid.uispec.errors={required:{en:"This field is required"},passwordMismatch:{en:"Passwords is not matching please check your password"}} +mosip.signup.mosipid.get-ui-spec.endpoint=${mosip.esignet.masterdata.domain}/v1/masterdata/uispec/esignet-signup/latest?type=schema -mosip.signup.mosipid.dynamic-fields.endpoint=http://masterdata.kernel/v1/masterdata/dynamicfields?pageNumber=%d&pageSize=%d" +mosip.signup.mosipid.dynamic-fields.endpoint=${mosip.esignet.masterdata.domain}/v1/masterdata/dynamicfields?pageNumber=%d&pageSize=%d # for multiple languages scenario, the languages should be appended followed by ampersand as ?languages=eng&languages=fra&languages=khm -mosip.signup.mosipid.doc-types-category.endpoint=http://masterdata.kernel/v1/masterdata/applicanttype/000/languages?languages=eng +mosip.signup.mosipid.doc-types-category.endpoint=${mosip.esignet.masterdata.domain}/v1/masterdata/applicanttype/000/languages?languages=eng mosip.signup.idrepo.add-identity.request.id=mosip.id.create mosip.signup.idrepo.update-identity.request.id=mosip.id.update diff --git a/mosip-identity-plugin/src/test/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImplTest.java b/mosip-identity-plugin/src/test/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImplTest.java index 0052b72d..9b78e9e9 100644 --- a/mosip-identity-plugin/src/test/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImplTest.java +++ b/mosip-identity-plugin/src/test/java/io/mosip/signup/plugin/mosipid/service/IdrepoProfileRegistryPluginImplTest.java @@ -1,5 +1,6 @@ package io.mosip.signup.plugin.mosipid.service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -62,6 +63,7 @@ public void beforeEach(){ defaultSelectedHandles.add("email"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "defaultSelectedHandles",defaultSelectedHandles); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "objectMapper",objectMapper); + ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "uiSpecJsonPointer", "/0/jsonSpec/0/spec"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "getUinEndpoint","http://localhost:8080/identity/v1/uin"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "schemaUrl","http://localhost:8080/identity/v1/schema/"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "identityEndpoint","http://localhost:8080/identity/v1/identity/"); @@ -74,12 +76,6 @@ public void beforeEach(){ ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "mandatoryLanguages", List.of("en")); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "optionalLanguages", List.of("fr","ar")); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "uinLength", 10); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "schemaJsonpath", "$[0].jsonSpec[0].schema"); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "errorsJsonpath", "$[0].jsonSpec[0].errors"); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "i18nValuesJsonpath", "$[0].jsonSpec[0].i18nValues"); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "i18nValuesErrorJsonpath", "$[0].jsonSpec[0].i18nValues.errors"); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "allowedValuesJsonpath", "$[0].jsonSpec[0].allowedValues"); - ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "maxUploadFileSizeJsonpath", "$[0].jsonSpec[0].maxUploadFileSize"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "uiSpecUrl", "http://mock/uispec"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "biometricDataFieldName", "individualBiometrics"); ReflectionTestUtils.setField(idrepoProfileRegistryPlugin, "defaultSelectedHandles", List.of("phone")); @@ -564,55 +560,20 @@ private JsonNode createIdentity() { } @Test - public void generateAllowedValues_withValidDetails_thenPass() { - ObjectNode dynamicItem = objectMapper.createObjectNode(); - dynamicItem.put("isActive", true); - dynamicItem.put("name", "fieldName"); - dynamicItem.put("langCode", "eng"); - - ArrayNode fieldValArray = objectMapper.createArrayNode(); - ObjectNode fieldVal = objectMapper.createObjectNode(); - fieldVal.put("code", "code1"); - fieldVal.put("value", "value1"); - fieldValArray.add(fieldVal); - dynamicItem.set("fieldVal", fieldValArray); - ArrayNode dynamicDataArray = objectMapper.createArrayNode(); - dynamicDataArray.add(dynamicItem); - - ObjectNode dynamicResponseNode = objectMapper.createObjectNode(); - dynamicResponseNode.put("totalPages", 1); - dynamicResponseNode.put("totalItems", 1); - dynamicResponseNode.set("data", dynamicDataArray); - ObjectNode dynamicWrapper = objectMapper.createObjectNode(); - dynamicWrapper.set("response", dynamicResponseNode); - - ResponseEntity dynamicEntity = new ResponseEntity<>(dynamicWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("dynamic"), Mockito.eq(JsonNode.class))).thenReturn(dynamicEntity); - - ObjectNode categoryItem = objectMapper.createObjectNode(); - categoryItem.put("isActive", true); - categoryItem.put("code", "cat1"); - categoryItem.put("langCode", "eng"); - - ArrayNode docTypesArray = objectMapper.createArrayNode(); - ObjectNode docType = objectMapper.createObjectNode(); - docType.put("code", "doc1"); - docType.put("name", "Document Name"); - docTypesArray.add(docType); - - categoryItem.set("documentTypes", docTypesArray); - - ArrayNode docCategoriesArray = objectMapper.createArrayNode(); - docCategoriesArray.add(categoryItem); - ObjectNode docResponseNode = objectMapper.createObjectNode(); - docResponseNode.set("documentCategories", docCategoriesArray); - ObjectNode docWrapper = objectMapper.createObjectNode(); - docWrapper.set("response", docResponseNode); + public void fetchAllowedValues_FromMasterDataService_withValidDetails_thenPass() { + ResponseEntity> dynamicFieldResponse = new ResponseEntity<>(getDynamicFieldsEndpointResponse(), HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("dynamic"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(dynamicFieldResponse); - ResponseEntity docEntity = new ResponseEntity<>(docWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("docTypes"), Mockito.eq(JsonNode.class))).thenReturn(docEntity); + ResponseEntity> docEntity = new ResponseEntity<>(getDocumentTypesAndCategoriesEndpointResponse(), HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("docTypes"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(docEntity); - JsonNode result = idrepoProfileRegistryPlugin.generateAllowedValues(); + JsonNode result = idrepoProfileRegistryPlugin.fetchAllowedValuesFromMasterDataService(); Assert.assertNotNull(result); Assert.assertTrue(result.has("fieldName")); @@ -624,8 +585,9 @@ public void generateAllowedValues_withValidDetails_thenPass() { Assert.assertEquals("Document Name", result.get("cat1").get("doc1").get("eng").asText()); } + @Test - public void generateAllowedValues_withInactiveDynamicField_thenFail() { + public void fetchAllowedValues_FromMasterDataService_withInactiveDynamicField_thenFail() { ObjectNode dynamicItem = objectMapper.createObjectNode(); dynamicItem.put("isActive", false); @@ -635,33 +597,41 @@ public void generateAllowedValues_withInactiveDynamicField_thenFail() { dynamicResponseNode.put("totalPages", 1); dynamicResponseNode.put("totalItems", 1); dynamicResponseNode.set("data", dynamicDataArray); - ObjectNode dynamicWrapper = objectMapper.createObjectNode(); - dynamicWrapper.set("response", dynamicResponseNode); + ResponseWrapper dynamicWrapper = new ResponseWrapper(); + dynamicWrapper.setResponse(dynamicResponseNode); + ResponseEntity> dynamicFieldResponse = new ResponseEntity<>(dynamicWrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("dynamic"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(dynamicFieldResponse); - ResponseEntity dynamicEntity = new ResponseEntity<>(dynamicWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("dynamic"), Mockito.eq(JsonNode.class))).thenReturn(dynamicEntity); - ObjectNode docWrapper = objectMapper.createObjectNode(); - docWrapper.set("response", objectMapper.createObjectNode().set("documentCategories", objectMapper.createArrayNode())); - ResponseEntity docEntity = new ResponseEntity<>(docWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("docTypes"), Mockito.eq(JsonNode.class))).thenReturn(docEntity); + ResponseWrapper docWrapper = new ResponseWrapper(); + docWrapper.setResponse(objectMapper.createObjectNode().set("documentCategories", objectMapper.createArrayNode())); + ResponseEntity> docEntity = new ResponseEntity<>(docWrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("docTypes"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(docEntity); - JsonNode result = idrepoProfileRegistryPlugin.generateAllowedValues(); + JsonNode result = idrepoProfileRegistryPlugin.fetchAllowedValuesFromMasterDataService(); Assert.assertNotNull(result); Assert.assertEquals(0, result.size()); } @Test - public void generateAllowedValues_withInactiveDocumentTypesAndCategories_thenFail() { + public void fetchAllowedValues_FromMasterDataService_withInactiveDocumentTypesAndCategories_thenFail() { ObjectNode dynamicResponseNode = objectMapper.createObjectNode(); dynamicResponseNode.put("totalPages", 1); dynamicResponseNode.put("totalItems", 0); dynamicResponseNode.set("data", objectMapper.createArrayNode()); - ObjectNode dynamicWrapper = objectMapper.createObjectNode(); - dynamicWrapper.set("response", dynamicResponseNode); - - ResponseEntity dynamicEntity = new ResponseEntity<>(dynamicWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("dynamic"), Mockito.eq(JsonNode.class))).thenReturn(dynamicEntity); + ResponseWrapper dynamicWrapper = new ResponseWrapper(); + dynamicWrapper.setResponse(dynamicResponseNode); + ResponseEntity> dynamicFieldResponse = new ResponseEntity<>(dynamicWrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("dynamic"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(dynamicFieldResponse); ObjectNode inactiveCategory = objectMapper.createObjectNode(); inactiveCategory.put("isActive", false); // should be skipped @@ -686,13 +656,16 @@ public void generateAllowedValues_withInactiveDocumentTypesAndCategories_thenFai ObjectNode docResponseNode = objectMapper.createObjectNode(); docResponseNode.set("documentCategories", docCategoriesArray); - ObjectNode docWrapper = objectMapper.createObjectNode(); - docWrapper.set("response", docResponseNode); + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + responseWrapper.setResponse(docResponseNode); - ResponseEntity docEntity = new ResponseEntity<>(docWrapper, HttpStatus.OK); - Mockito.when(restTemplate.getForEntity(Mockito.contains("docTypes"), Mockito.eq(JsonNode.class))).thenReturn(docEntity); + ResponseEntity> docResponse = new ResponseEntity<>(responseWrapper, HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("docTypes"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(docResponse); - JsonNode result = idrepoProfileRegistryPlugin.generateAllowedValues(); + JsonNode result = idrepoProfileRegistryPlugin.fetchAllowedValuesFromMasterDataService(); Assert.assertNotNull(result); Assert.assertEquals(0, result.size()); @@ -724,21 +697,12 @@ public void validate_unableToFetchTheSchema_thenFail() throws ProfileException{ } @Test - public void init_shouldPopulateUiSpec_thenPass() { - ObjectNode specNode = objectMapper.createObjectNode(); - specNode.set("schema", objectMapper.createObjectNode().put("someKey", "someValue")); - specNode.set("errors", objectMapper.createObjectNode().put("errKey", "errVal")); - - ObjectNode i18n = objectMapper.createObjectNode(); - i18n.set("en", objectMapper.createObjectNode().put("label", "Name")); - ObjectNode i18nErrors = objectMapper.createObjectNode().put("required", "Required"); - i18n.set("errors", i18nErrors); - specNode.set("i18nValues", i18n); + public void getUISpecification_validResponse_thenPass() throws JsonProcessingException { + String jsonSpec = "{\"spec\":{\"schema\":{\"required\":[\"phone\"],\"properties\":{\"phone\":{\"type\":\"string\"}}}," + + "\"i18nValues\":{\"en\":{\"label\":\"Phone Number\"},\"errors\":{\"invalid_phone\":\"Phone number is invalid\"}}," + + "\"customField\":{\"gender\":\"M,F\"}}}}"; - specNode.set("allowedValues", objectMapper.createObjectNode().put("gender", "M,F")); - specNode.put("maxUploadFileSize", 1048576); - - ArrayNode specArray = objectMapper.createArrayNode().add(specNode); + ArrayNode specArray = objectMapper.createArrayNode().add(objectMapper.readTree(jsonSpec)); ObjectNode root = objectMapper.createObjectNode().set("jsonSpec", specArray); ArrayNode rootArray = objectMapper.createArrayNode().add(root); @@ -749,15 +713,28 @@ public void init_shouldPopulateUiSpec_thenPass() { Mockito.when(restTemplate.exchange(Mockito.eq("http://mock/uispec"), Mockito.eq(HttpMethod.GET), Mockito.isNull(), Mockito.>>any())).thenReturn(responseEntity); - idrepoProfileRegistryPlugin.init(); + + ResponseEntity> dynamicFieldResponse = new ResponseEntity<>(getDynamicFieldsEndpointResponse(), HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("dynamic"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(dynamicFieldResponse); + + ResponseEntity> docEntity = new ResponseEntity<>(getDocumentTypesAndCategoriesEndpointResponse(), HttpStatus.OK); + Mockito.when(restTemplate.exchange( + Mockito.contains("docTypes"), Mockito.eq(HttpMethod.GET), Mockito.any(), + Mockito.eq(new ParameterizedTypeReference>() {}) + )).thenReturn(docEntity); JsonNode uiSpec = idrepoProfileRegistryPlugin.getUISpecification(); Assert.assertNotNull(uiSpec); Assert.assertTrue(uiSpec.has("schema")); - Assert.assertTrue(uiSpec.has("errors")); + Assert.assertTrue(uiSpec.has("customField")); Assert.assertTrue(uiSpec.has("i18nValues")); Assert.assertTrue(uiSpec.has("allowedValues")); Assert.assertTrue(uiSpec.has("maxUploadFileSize")); + Assert.assertTrue(uiSpec.has("language")); + Assert.assertTrue(uiSpec.has("resetPasswordChallengeFields")); } @Test @@ -816,4 +793,53 @@ public void createProfile_withValidBuildDocuments_thenPass() throws Exception { Assert.assertNotNull(profileResult); Assert.assertEquals(profileResult.getStatus(),"SUCCESS"); } + + private ResponseWrapper getDynamicFieldsEndpointResponse() { + ObjectNode dynamicItem = objectMapper.createObjectNode(); + dynamicItem.put("isActive", true); + dynamicItem.put("name", "fieldName"); + dynamicItem.put("langCode", "eng"); + + ArrayNode fieldValArray = objectMapper.createArrayNode(); + ObjectNode fieldVal = objectMapper.createObjectNode(); + fieldVal.put("code", "code1"); + fieldVal.put("value", "value1"); + fieldValArray.add(fieldVal); + dynamicItem.set("fieldVal", fieldValArray); + ArrayNode dynamicDataArray = objectMapper.createArrayNode(); + dynamicDataArray.add(dynamicItem); + + ObjectNode dynamicResponseNode = objectMapper.createObjectNode(); + dynamicResponseNode.put("totalPages", 1); + dynamicResponseNode.put("totalItems", 1); + dynamicResponseNode.set("data", dynamicDataArray); + + ResponseWrapper dynamicWrapper = new ResponseWrapper(); + dynamicWrapper.setResponse(dynamicResponseNode); + return dynamicWrapper; + } + + private ResponseWrapper getDocumentTypesAndCategoriesEndpointResponse() { + ObjectNode docType = objectMapper.createObjectNode(); + docType.put("code", "doc1"); + docType.put("name", "Document Name"); + docType.put("langCode", "eng"); + ArrayNode docTypesArray = objectMapper.createArrayNode(); + docTypesArray.add(docType); + + ObjectNode category = objectMapper.createObjectNode(); + category.put("isActive", true); + category.put("code", "cat1"); + category.put("langCode", "eng"); + category.set("documentTypes", docTypesArray); + ArrayNode docCategoriesArray = objectMapper.createArrayNode(); + docCategoriesArray.add(category); + + ObjectNode docResponseNode = objectMapper.createObjectNode(); + docResponseNode.set("documentCategories", docCategoriesArray); + + ResponseWrapper docWrapper = new ResponseWrapper<>(); + docWrapper.setResponse(docResponseNode); + return docWrapper; + } }