diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java index 154104d..327b1a9 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,6 +34,7 @@ import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.Assertion.Type; import org.oneedtech.inspect.vc.Credential.CredentialEnum; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.ExtensionProbe; import org.oneedtech.inspect.vc.jsonld.probe.GraphFetcherProbe; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; @@ -164,23 +166,33 @@ public class OB20Inspector extends VCInspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - // Embedded endorsements. Pass document loader because it has already cached documents, and it has localdomains for testing - OB20EndorsementInspector endorsementInspector = new OB20EndorsementInspector.Builder().documentLoader(documentLoader).build(); - - // get endorsements for all JSON_LD objects in the graph - List endorsements = ctx.getGeneratedObjects().values().stream() + // extension validations + List jsonLdGeneratedObjects = ctx.getGeneratedObjects().values().stream() .filter(generatedObject -> generatedObject instanceof JsonLdGeneratedObject) - .flatMap(obj -> { - JsonNode node; + .map(obj -> { + try { - node = mapper.readTree(((JsonLdGeneratedObject) obj).getJson()); - // return endorsement node, filtering out the on inside @context - return asNodeList(node, "$..endorsement", jsonPath).stream().filter(endorsementNode -> !endorsementNode.isObject()); + return mapper.readTree(((JsonLdGeneratedObject) obj).getJson()); } catch (JsonProcessingException e) { throw new IllegalArgumentException("Couldn't not parse " + obj.getId() + ": contains invalid JSON"); } }) .collect(Collectors.toList()); + for (JsonNode generatedObject : jsonLdGeneratedObjects) { + probeCount++; + accumulator.add(new ExtensionProbe().run(generatedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + // Embedded endorsements. Pass document loader because it has already cached documents, and it has localdomains for testing + OB20EndorsementInspector endorsementInspector = new OB20EndorsementInspector.Builder().documentLoader(documentLoader).build(); + + // get endorsements for all JSON_LD objects in the graph + List endorsements = jsonLdGeneratedObjects.stream().flatMap(node -> { + // return endorsement node, filtering out the on inside @context + return asNodeList(node, "$..endorsement", jsonPath).stream().filter(endorsementNode -> !endorsementNode.isObject()); + }) + .collect(Collectors.toList()); for(JsonNode node : endorsements) { probeCount++; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/ExtensionProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/ExtensionProbe.java new file mode 100644 index 0000000..a801222 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/ExtensionProbe.java @@ -0,0 +1,37 @@ +package org.oneedtech.inspect.vc.jsonld.probe; + +import java.net.URI; +import java.util.Set; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.RunContext.Key; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ExtensionProbe extends Probe { + + @Override + public ReportItems run(JsonNode node, RunContext ctx) throws Exception { + if (!node.isObject()) { + return success(ctx); + } + + Object documentLoader = ctx.get(Key.JSON_DOCUMENT_LOADER); + Set contexts; + if (documentLoader instanceof CachingDocumentLoader) { + contexts = ((CachingDocumentLoader) documentLoader).getContexts(); + } else { + contexts = Set.of(); + } + + // TODO Auto-generated method stub + return null; + } + + private void getValidations(JsonNode node, String entryPath, Set contexts) { + + } +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java index 455773b..1ef7c47 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java @@ -268,6 +268,24 @@ public class OB20Tests { }); } + @Test + void testExtensionNode() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_EXTENSION_NODE_BASIC_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + @Test + void testInvalidExtensionNode() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_EXTENSION_NODE_INVALID_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + @Nested static class WarningTests { @BeforeAll diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index e9e9bef..c217cec 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -91,6 +91,10 @@ public class Samples { public final static Sample BADGE_WITH_COMPLEX_IMAGE_JSON = new Sample("ob20/assets/badgeclass-with-complex-image.json", true); // original: test_validate_endorsements public final static Sample ASSERTION_WITH_ENDORSEMENTS = new Sample("ob20/assertion-with-endorsements.json", true); + // original: test_validate_extensions: test_validate_extension_node_basic + public final static Sample ASSERTION_WITH_EXTENSION_NODE_BASIC_JSON = new Sample("ob20/assertion-with-extension-node-basic.json", true); + // original: test_validate_extensions: test_validate_extension_node_invalid + public final static Sample ASSERTION_WITH_EXTENSION_NODE_INVALID_JSON = new Sample("ob20/assertion-with-extension-node-invalid.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-basic.json b/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-basic.json new file mode 100644 index 0000000..0ef8631 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-basic.json @@ -0,0 +1,29 @@ +{ + "@context": ["https://w3id.org/openbadges/v2","https://w3id.org/openbadges/extensions/exampleExtension/context.json"], + "id": "http://example.org/assertion", + "type": "Assertion", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", + "issuedOn": "2016-12-31T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + }, + "evidence": { + "id": "_:b1", + "narrative": "Rocked the free world" + }, + "extensions:exampleExtension": { + "id": "_:b0", + "type": [ + "Extension", + "extensions:ExampleExtension" + ], + "schema:text": "I'm a property, short and sweet" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-invalid.json b/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-invalid.json new file mode 100644 index 0000000..af49df5 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assertion-with-extension-node-invalid.json @@ -0,0 +1,29 @@ +{ + "@context": ["https://w3id.org/openbadges/v2","https://w3id.org/openbadges/extensions/exampleExtension/context.json"], + "id": "http://example.org/assertion", + "type": "Assertion", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", + "issuedOn": "2016-12-31T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + }, + "evidence": { + "id": "_:b1", + "narrative": "Rocked the free world" + }, + "extensions:exampleExtension": { + "id": "_:b0", + "type": [ + "Extension", + "extensions:ExampleExtension" + ], + "schema:text": 1337 + } +} \ No newline at end of file