From 1ddbabbc7f9b077c31bcaa30d24156efd9760607 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Date: Sun, 20 Oct 2024 21:26:54 +0530 Subject: [PATCH] [INJICERT-434] improve JSON templating edge cases (#112) --- .../VelocityTemplatingEngineImpl.java | 17 +++- .../resources/application-local.properties | 2 +- .../VelocityTemplatingEngineImplTest.java | 82 ++++++++++++++++--- 3 files changed, 86 insertions(+), 15 deletions(-) diff --git a/certify-service/src/main/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImpl.java b/certify-service/src/main/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImpl.java index dca1614d..b3ae436a 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImpl.java +++ b/certify-service/src/main/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImpl.java @@ -10,16 +10,20 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; + import io.mosip.certify.api.spi.VCFormatter; import io.mosip.certify.core.constants.Constants; import io.mosip.certify.core.constants.VCDM2Constants; import io.mosip.certify.core.repository.TemplateRepository; import jakarta.annotation.PostConstruct; +import lombok.SneakyThrows; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.EscapeTool; import org.json.JSONArray; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -58,6 +62,7 @@ public void initialize() { * internal work such as locating the appropriate template * @return templated VC as a String */ + @SneakyThrows @Override public String format(Map templateInput, Map defaultSettings) { // TODO: Isn't template name becoming too complex with VC_CONTEXTS & CREDENTIAL_TYPES both? @@ -66,6 +71,7 @@ public String format(Map templateInput, Map defa String t = templateCache.get(templateName); StringWriter writer = new StringWriter(); // 1. Prepare map + // TODO: Eventually, the credentialSubject from the plugin will be templated as-is Map finalTemplate = new HashMap<>(); for (String key : templateInput.keySet()) { Object value = templateInput.get(key); @@ -76,15 +82,22 @@ public String format(Map templateInput, Map defa finalTemplate.put(key, new JSONArray((List) value)); } else if (value.getClass().isArray()) { finalTemplate.put(key, new JSONArray(List.of(value))); - } else { + } else if (value instanceof Integer | value instanceof Float | value instanceof Long | value instanceof Double) { + // entities which don't need to be quoted finalTemplate.put(key, value); + } else if (value instanceof String){ + // entities which need to be quoted + finalTemplate.put(key, JSONObject.wrap(value)); } } + // Date: https://velocity.apache.org/tools/3.1/apidocs/org/apache/velocity/tools/generic/DateTool.html + finalTemplate.put("_dateTool", new DateTool()); + // Escape: https://velocity.apache.org/tools/3.1/apidocs/org/apache/velocity/tools/generic/EscapeTool.html + finalTemplate.put("_esc", new EscapeTool()); // add the issuer value finalTemplate.put("issuer", issuer); if (shouldHaveDates && !(templateInput.containsKey(VCDM2Constants.VALID_FROM) && templateInput.containsKey(VCDM2Constants.VALID_UNITL))) { - templateInput.put("_dateTool", new DateTool()); String time = ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern(Constants.UTC_DATETIME_PATTERN)); // hardcoded time String expiryTime = ZonedDateTime.now(ZoneOffset.UTC).plusYears(2).format(DateTimeFormatter.ofPattern(Constants.UTC_DATETIME_PATTERN)); diff --git a/certify-service/src/main/resources/application-local.properties b/certify-service/src/main/resources/application-local.properties index c0eca24d..b1b0b347 100644 --- a/certify-service/src/main/resources/application-local.properties +++ b/certify-service/src/main/resources/application-local.properties @@ -329,4 +329,4 @@ spring.datasource.password=postgres spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=none -spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true \ No newline at end of file diff --git a/certify-service/src/test/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImplTest.java b/certify-service/src/test/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImplTest.java index 5158fde9..03a65e53 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImplTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/templating/VelocityTemplatingEngineImplTest.java @@ -5,18 +5,20 @@ import junit.framework.TestCase; import lombok.SneakyThrows; import net.javacrumbs.jsonunit.assertj.JsonAssertions; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,11 +34,11 @@ public class VelocityTemplatingEngineImplTest extends TestCase { @Mock TemplateRepository templateRepository; + @SneakyThrows @Before public void setUp() { List templates = new ArrayList<>(); TemplateData vc1 = initTemplate(""" - { "@context": [ "https://www.w3.org/2018/credentials/v1"] @@ -87,9 +89,37 @@ public void setUp() { """, "MockVerifiableCredential,VerifiableCredential", "https://example.org/Person.json,https://www.w3.org/ns/credentials/v2" - ); - when(templateRepository.findAll()).thenReturn(List.of(vc1, vc2)); + TemplateData vc3 = initTemplate(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://vharsh.github.io/DID/mock-context.json"], + "issuer": "${issuer}", + "type": ["VerifiableCredential", "MockVerifiableCredential"], + "issuanceDate": "${validFrom}", + "expirationDate": "${validUntil}", + "credentialSubject": { + "gender": ${gender}, + "postalCode": ${postalCode}, + "fullName": ${fullName}, + "dateOfBirth": "${dateOfBirth}", + "province": ${province}, + "phone": "${phone}", + "addressLine1": ${addressLine1}, + "region": ${region}, + "vcVer": "${vcVer}", + "UIN": ${UIN}, + "email": "${email}", + "face": "${face}" + } + } + """, + "MockVerifiableCredential,VerifiableCredential", + "https://vharsh.github.io/DID/mock-context.json,https://www.w3.org/2018/credentials/v1" + ); + when(templateRepository.findAll()).thenReturn(List.of(vc1, vc2, vc3)); + ReflectionTestUtils.setField(formatter, "shouldHaveDates", true); formatter.initialize(); // engine = new VelocityEngine(); // engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); @@ -111,11 +141,6 @@ private TemplateData initTemplate(String template, String type, String context) @SneakyThrows @Test public void testTemplating() { - // 1. setup template - Resource cr = new ClassPathResource("MockCredential1.vm"); - assert cr.isFile(); - String t = Files.readString(cr.getFile().toPath(), StandardCharsets.UTF_8); - assert t != null; Map ret = new HashMap<>(); ret.put("vcVer", "VC-V1"); // ret.put("issuer", "https://example.com/fake-issuer"); @@ -142,4 +167,37 @@ public void testTemplating() { """; JsonAssertions.assertThatJson(actualJSON).isEqualTo(expectedJSON); } + + @Ignore + @Test + public void testTemplating_localOnly() { + // This test is written to rapidly test out changes against a hosted mock-identity system + RestTemplate r = new RestTemplate(); + Map x = r.getForObject("http://localhost:8082/v1/mock-identity-system/identity/12345678", + HashMap.class); + Map res = (Map) x.get("response"); + Map ret = new HashMap<>(); + ret.put("vcVer", "VC-V1"); + ret.put("UIN", 1234567); + ret.put("fullName", res.get("fullName")); + ret.put("gender", res.get("gender")); + ret.put("dateOfBirth", res.get("dateOfBirth")); + ret.put("email", res.get("email")); + ret.put("phone", res.get("phone")); + ret.put("addressLine1", res.get("streetAddress")); + ret.put("province", res.get("locality")); + ret.put("region", res.get("region")); + ret.put("postalCode", res.get("postalCode")); + ret.put("face", res.get("encodedPhoto")); + + Map templateMap = Map.of("templateName", "MockVerifiableCredential,VerifiableCredential:https://vharsh.github.io/DID/mock-context.json,https://www.w3.org/2018/credentials/v1", + "issuerURI", "https://example.com/fake-issuer"); + String actualJSON = formatter.format(ret, templateMap); + System.out.println(actualJSON); + try { + JSONObject j = new JSONObject(actualJSON); + } catch (JSONException e) { + Assert.fail(e.getMessage()); + } + } } \ No newline at end of file