From cb60fd11edc4bd1a3e1eecb249f87f5faf01bdab Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 13 Dec 2022 14:30:55 +0100 Subject: [PATCH] Added endorsement inspector for OB 2.0 --- .../inspect/vc/OB20EndorsementInspector.java | 138 ++++++++++++++++++ .../oneedtech/inspect/vc/OB20Inspector.java | 110 +++++++++----- .../org/oneedtech/inspect/vc/VCInspector.java | 10 ++ .../probe/AssertionRevocationListProbe.java | 21 ++- .../probe/VerificationDependenciesProbe.java | 25 +++- .../ob20/assertion-with-endorsements.json | 11 +- .../assets/badgeclass-with-endorsements.json | 11 ++ .../resources/ob20/assets/endorsement-1.json | 2 +- .../resources/ob20/assets/endorsement-2.json | 2 +- .../resources/ob20/assets/endorsement-3.json | 14 ++ .../resources/ob20/assets/endorsement-4.json | 14 ++ .../assets/issuer-with-allowed-origins.json | 2 +- .../test/resources/ob20/assets/issuer1.json | 2 +- 13 files changed, 319 insertions(+), 43 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-endorsements.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/endorsement-3.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/endorsement-4.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java new file mode 100644 index 0000000..7bde48b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java @@ -0,0 +1,138 @@ +package org.oneedtech.inspect.vc; + +import static org.oneedtech.inspect.core.probe.RunContext.Key.GENERATED_OBJECT_BUILDER; +import static org.oneedtech.inspect.core.probe.RunContext.Key.JACKSON_OBJECTMAPPER; +import static org.oneedtech.inspect.core.probe.RunContext.Key.JSONPATH_EVALUATOR; +import static org.oneedtech.inspect.core.probe.RunContext.Key.JSON_DOCUMENT_LOADER; +import static org.oneedtech.inspect.core.probe.RunContext.Key.JWT_CREDENTIAL_NODE_NAME; +import static org.oneedtech.inspect.core.probe.RunContext.Key.PNG_CREDENTIAL_KEY; +import static org.oneedtech.inspect.core.probe.RunContext.Key.SVG_CREDENTIAL_QNAME; +import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; +import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; +import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.oneedtech.inspect.core.SubInspector; +import org.oneedtech.inspect.core.probe.GeneratedObject; +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.vc.Assertion.Type; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.payload.PngParser; +import org.oneedtech.inspect.vc.payload.SvgParser; +import org.oneedtech.inspect.vc.probe.AssertionRevocationListProbe; +import org.oneedtech.inspect.vc.probe.ExpirationProbe; +import org.oneedtech.inspect.vc.probe.IssuanceProbe; +import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe; + +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * An inspector for EndorsementCredential objects. + * @author mgylling + */ +public class OB20EndorsementInspector extends VCInspector implements SubInspector { + + private DocumentLoader documentLoader; + + protected OB20EndorsementInspector(OB20EndorsementInspector.Builder builder) { + super(builder); + this.documentLoader = builder.documentLoader; + } + + @Override + public Report run(Resource resource, Map parentObjects) { + + /* + * The resource param is the top-level credential that embeds the endorsement, we + * expect parentObjects to provide a pointer to the JsonNode we should check. + * + * The parent inspector is responsible to decode away possible jwt-ness, so that + * what we get here is a verbatim json node. + * + */ + + Assertion endorsement = (Assertion) checkNotNull(parentObjects.get(CREDENTIAL_KEY)); + + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + RunContext ctx = new RunContext.Builder() + .put(this) + .put(resource) + .put(JACKSON_OBJECTMAPPER, mapper) + .put(JSONPATH_EVALUATOR, jsonPath) + .put(GENERATED_OBJECT_BUILDER, new Assertion.Builder()) + .put(PNG_CREDENTIAL_KEY, PngParser.Keys.OB20) + .put(SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) + .put(JSON_DOCUMENT_LOADER, documentLoader) + .put(JWT_CREDENTIAL_NODE_NAME, Assertion.JWT_NODE_NAME) + .build(); + + parentObjects.entrySet().stream().forEach(entry -> { + if (!entry.getKey().equals(CREDENTIAL_KEY)) { + ctx.addGeneratedObject(entry.getValue()); + } + }); + + List accumulator = new ArrayList<>(); + int probeCount = 0; + try { + + JsonNode endorsementNode = endorsement.getJson(); + // verification and revocation + if (endorsement.getCredentialType() == Type.Endorsement) { + for(Probe probe : List.of(new VerificationDependenciesProbe(endorsementNode.get("id").asText(), "claim"), + new AssertionRevocationListProbe(endorsementNode.get("id").asText(), "claim"))) { + probeCount++; + accumulator.add(probe.run(new JsonLdGeneratedObject(endorsementNode.toString()), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + } + + // expiration and issuance + for(Probe probe : List.of( + new ExpirationProbe(), new IssuanceProbe())) { + probeCount++; + accumulator.add(probe.run(endorsement, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + } catch (Exception e) { + accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); + } + + return new Report(ctx, new ReportItems(accumulator), probeCount); + } + + @Override + public Report run(R resource) { + throw new IllegalStateException("must use #run(resource, map)"); + } + + public static class Builder extends VCInspector.Builder { + private DocumentLoader documentLoader; + + @SuppressWarnings("unchecked") + @Override + public OB20EndorsementInspector build() { + return new OB20EndorsementInspector(this); + } + + public Builder documentLoader(DocumentLoader documentLoader) { + this.documentLoader = documentLoader; + return this; + } + } + +} 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 5ac5b96..154104d 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 @@ -4,12 +4,20 @@ import static java.lang.Boolean.TRUE; import static org.oneedtech.inspect.core.Inspector.Behavior.RESET_CACHES_ON_RUN; import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; +import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY; +import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.oneedtech.inspect.core.Inspector; -import org.oneedtech.inspect.core.probe.Outcome; +import org.oneedtech.inspect.core.probe.GeneratedObject; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.RunContext.Key; @@ -20,6 +28,7 @@ import org.oneedtech.inspect.schema.JsonSchemaCache; import org.oneedtech.inspect.util.json.ObjectMapperCache; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.Assertion.Type; import org.oneedtech.inspect.vc.Credential.CredentialEnum; @@ -41,38 +50,25 @@ import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.loader.DocumentLoader; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import foundation.identity.jsonld.ConfigurableDocumentLoader; + /** * A verifier for Open Badges 2.0. * @author xaracil */ -public class OB20Inspector extends Inspector { +public class OB20Inspector extends VCInspector { - protected OB20Inspector(OB20Inspector.Builder builder) { + protected > OB20Inspector(B builder) { super(builder); } - protected Report abort(RunContext ctx, List accumulator, int probeCount) { - return new Report(ctx, new ReportItems(accumulator), probeCount); - } - - protected boolean broken(List accumulator) { - return broken(accumulator, false); - } - - protected boolean broken(List accumulator, boolean force) { - if(!force && getBehavior(Inspector.Behavior.VALIDATOR_FAIL_FAST) == Boolean.FALSE) { - return false; - } - for(ReportItems items : accumulator) { - if(items.contains(Outcome.FATAL, Outcome.EXCEPTION)) return true; - } - return false; - } - - + /* (non-Javadoc) + * @see org.oneedtech.inspect.core.Inspector#run(org.oneedtech.inspect.util.resource.Resource) + */ @Override public Report run(Resource resource) { super.check(resource); @@ -143,14 +139,6 @@ public class OB20Inspector extends Inspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - // expiration and issuance - for(Probe probe : List.of( - new ExpirationProbe(), new IssuanceProbe())) { - probeCount++; - accumulator.add(probe.run(assertion, ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - } - // verification and revocation if (assertion.getCredentialType() == Type.Assertion) { for(Probe probe : List.of(new VerificationDependenciesProbe(assertionNode.get("id").asText()), @@ -168,6 +156,47 @@ public class OB20Inspector extends Inspector { } } + // expiration and issuance + for(Probe probe : List.of( + new ExpirationProbe(), new IssuanceProbe())) { + probeCount++; + accumulator.add(probe.run(assertion, 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 = ctx.getGeneratedObjects().values().stream() + .filter(generatedObject -> generatedObject instanceof JsonLdGeneratedObject) + .flatMap(obj -> { + JsonNode node; + 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()); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Couldn't not parse " + obj.getId() + ": contains invalid JSON"); + } + }) + .collect(Collectors.toList()); + + for(JsonNode node : endorsements) { + probeCount++; + // get endorsement json from context + UriResource uriResource = resolveUriResource(ctx, node.asText()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); + if (resolved == null) { + throw new IllegalArgumentException("endorsement " + node.toString() + " not found in graph"); + } + + Assertion endorsement = new Assertion.Builder().resource(resource).jsonData(mapper.readTree(resolved.getJson())).build(); + // pass graph to subinspector + Map parentObjects = new HashMap<>(ctx.getGeneratedObjects()); + parentObjects.put(CREDENTIAL_KEY, endorsement); + accumulator.add(endorsementInspector.run(resource, parentObjects)); + } } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); @@ -176,11 +205,7 @@ public class OB20Inspector extends Inspector { return new Report(ctx, new ReportItems(accumulator), probeCount); } - protected DocumentLoader getDocumentLoader() { - return new CachingDocumentLoader(); - } - - public static class Builder extends Inspector.Builder { + public static class Builder extends VCInspector.Builder { public Builder() { super(); @@ -204,4 +229,19 @@ public class OB20Inspector extends Inspector { public static final String ALLOW_LOCAL_REDIRECTION = "ALLOW_LOCAL_REDIRECTION"; } + protected UriResource resolveUriResource(RunContext ctx, String url) throws URISyntaxException { + URI uri = new URI(url); + UriResource initialUriResource = new UriResource(uri); + UriResource uriResource = initialUriResource; + + // check if uri points to a local resource + if (ctx.get(Key.JSON_DOCUMENT_LOADER) instanceof ConfigurableDocumentLoader) { + if (ConfigurableDocumentLoader.getDefaultHttpLoader() instanceof CachingDocumentLoader.HttpLoader) { + URI resolvedUri = ((CachingDocumentLoader.HttpLoader) ConfigurableDocumentLoader.getDefaultHttpLoader()).resolve(uri); + uriResource = new UriResource(resolvedUri); + } + } + return uriResource; + } + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java index 19a89bc..b4cf13a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java @@ -10,7 +10,9 @@ import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoader; import com.fasterxml.jackson.databind.JsonNode; /** @@ -61,6 +63,14 @@ public abstract class VCInspector extends Inspector { return Optional.empty(); } + /** + * Creates a caching document loader for loading json resources + * @return document loader for loading json resources + */ + protected DocumentLoader getDocumentLoader() { + return new CachingDocumentLoader(); + } + protected static final String REFRESHED = "is.refreshed.credential"; public abstract static class Builder> extends Inspector.Builder { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java index 9497028..b951cb0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java @@ -25,10 +25,16 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader; public class AssertionRevocationListProbe extends Probe { private final String assertionId; + private final String propertyName; public AssertionRevocationListProbe(String assertionId) { + this(assertionId, "badge"); + } + + public AssertionRevocationListProbe(String assertionId, String propertyName) { super(ID); this.assertionId = assertionId; + this.propertyName = propertyName; } @Override @@ -37,7 +43,7 @@ public class AssertionRevocationListProbe extends Probe { JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); // get badge - UriResource badgeUriResource = resolveUriResource(ctx, jsonNode.get("badge").asText().strip()); + UriResource badgeUriResource = resolveUriResource(ctx, getBadgeClaimId(jsonNode)); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); @@ -97,5 +103,18 @@ public class AssertionRevocationListProbe extends Probe { return uriResource; } + /** + * Return the ID of the node with name propertyName + * @param jsonNode node + * @return ID of the node. If node is textual, the text is returned. If node is an object, its "ID" attribute is returned + */ + protected String getBadgeClaimId(JsonNode jsonNode) { + JsonNode propertyNode = jsonNode.get(propertyName); + if (propertyNode.isTextual()) { + return propertyNode.asText().strip(); + } + return propertyNode.get("id").asText().strip(); + } + public static final String ID = AssertionRevocationListProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java index d563176..38f34be 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java @@ -28,12 +28,19 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader; */ public class VerificationDependenciesProbe extends Probe { private final String assertionId; + private final String propertyName; public VerificationDependenciesProbe(String assertionId) { + this(assertionId, "badge"); + } + + public VerificationDependenciesProbe(String assertionId, String propertyName) { super(ID); this.assertionId = assertionId; + this.propertyName = propertyName; } + @Override public ReportItems run(JsonLdGeneratedObject jsonLdGeneratedObject, RunContext ctx) throws Exception { ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); @@ -56,7 +63,7 @@ public class VerificationDependenciesProbe extends Probe if ("HostedBadge".equals(type)) { // get badge - UriResource badgeUriResource = resolveUriResource(ctx, jsonNode.get("badge").asText().strip()); + UriResource badgeUriResource = resolveUriResource(ctx, getBadgeClaimId(jsonNode)); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) @@ -104,7 +111,7 @@ public class VerificationDependenciesProbe extends Probe allowedOrigins = List.of(defaultAllowedOrigins); } } else { - JsonNodeUtil.asStringList(allowedOriginsNode); + allowedOrigins = JsonNodeUtil.asStringList(allowedOriginsNode); } if (allowedOrigins == null || allowedOrigins.isEmpty() || !issuerId.startsWith("http")) { @@ -147,6 +154,20 @@ public class VerificationDependenciesProbe extends Probe return uriResource; } + /** + * Return the ID of the node with name propertyName + * @param jsonNode node + * @return ID of the node. If node is textual, the text is returned. If node is an object, its "ID" attribute is returned + */ + protected String getBadgeClaimId(JsonNode jsonNode) { + JsonNode propertyNode = jsonNode.get(propertyName); + if (propertyNode.isTextual()) { + return propertyNode.asText().strip(); + } + return propertyNode.get("id").asText().strip(); + } + + public static final String ID = VerificationDependenciesProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json b/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json index 4395cdb..3af3824 100644 --- a/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json +++ b/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json @@ -11,7 +11,16 @@ "image": "https://example.org/beths-robot-badge.png", "evidence": "https://example.org/beths-robot-work.html", "issuedOn": "2016-12-31T23:59:59Z", - "badge": "https://example.org/robotics-badge.json", + "badge": { + "type": "BadgeClass", + "id": "https://example.org/badgeclass-with-endorsements.json", + "name": "Awesome Robotics Badge", + "description": "For doing awesome things with robots that people think is pretty great.", + "image": "https://example.org/robotics-badge.png", + "criteria": "https://example.org/badgecriteria.json", + "issuer": "https://example.org/organization.json", + "endorsement": ["https://example.org/endorsement-3.json", "https://example.org/endorsement-4.json"] + }, "verification": { "type": "hosted" }, diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-endorsements.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-endorsements.json new file mode 100644 index 0000000..181827d --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-endorsements.json @@ -0,0 +1,11 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/badgeclass-with-endorsements.json", + "name": "Awesome Robotics Badge", + "description": "For doing awesome things with robots that people think is pretty great.", + "image": "https://example.org/robotics-badge.png", + "criteria": "https://example.org/badgecriteria.json", + "issuer": "https://example.org/organization.json", + "endorsement": ["https://example.org/endorsement-3.json", "https://example.org/endorsement-4.json"] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/endorsement-1.json b/inspector-vc/src/test/resources/ob20/assets/endorsement-1.json index 44d6ac2..afa1695 100644 --- a/inspector-vc/src/test/resources/ob20/assets/endorsement-1.json +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-1.json @@ -3,7 +3,7 @@ "id": "http://example.org/endorsement-1.json", "type": "Endorsement", "claim": { - "id": "https://example.org/robotics-badge.json", + "id": "https://example.org/badgeclass-with-endorsements.json", "endorsementComment": "Pretty good" }, "issuedOn": "2017-10-01T00:00Z", diff --git a/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json b/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json index 70ccc4d..5bbf4cf 100644 --- a/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json @@ -3,7 +3,7 @@ "id": "http://example.org/endorsement-2.json", "type": "Endorsement", "claim": { - "id": "https://example.org/robotics-badge.json", + "id": "https://example.org/badgeclass-with-endorsements.json", "endorsementComment": "Pretty good" }, "issuedOn": "2017-10-01T00:00Z", diff --git a/inspector-vc/src/test/resources/ob20/assets/endorsement-3.json b/inspector-vc/src/test/resources/ob20/assets/endorsement-3.json new file mode 100644 index 0000000..89000e3 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-3.json @@ -0,0 +1,14 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/endorsement-3.json", + "type": "Endorsement", + "claim": { + "id": "https://example.org/badgeclass-with-endorsements.json", + "endorsementComment": "Pretty good" + }, + "issuedOn": "2017-10-01T00:00Z", + "issuer": "http://example.org/issuer1.json", + "verification": { + "type": "HostedBadge" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/endorsement-4.json b/inspector-vc/src/test/resources/ob20/assets/endorsement-4.json new file mode 100644 index 0000000..1100f8b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-4.json @@ -0,0 +1,14 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/endorsement-4.json", + "type": "Endorsement", + "claim": { + "id": "https://example.org/badgeclass-with-endorsements.json", + "endorsementComment": "Pretty good" + }, + "issuedOn": "2017-10-01T00:00Z", + "issuer": "http://example.org/issuer1.json", + "verification": { + "type": "HostedBadge" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json index 5a03aba..ae613e1 100644 --- a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json @@ -204,6 +204,6 @@ "email": "me@example.org", "url": "http://example.org", "verification": { - "allowedOrigins": ["example.com"] + "allowedOrigins": ["example.com", "example.org"] } } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer1.json b/inspector-vc/src/test/resources/ob20/assets/issuer1.json index 2df69c2..9b3d7a0 100644 --- a/inspector-vc/src/test/resources/ob20/assets/issuer1.json +++ b/inspector-vc/src/test/resources/ob20/assets/issuer1.json @@ -198,7 +198,7 @@ }, "verify": "verification" }, - "id": "http://example.org/issuer1", + "id": "http://example.org/issuer1.json", "type": "Issuer", "name": "Example Issuer", "email": "me@example.org",