From 21fe60f21b505a8b858e9be5cf74122f654a8049 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 22 Nov 2022 13:57:35 +0100 Subject: [PATCH 01/93] Added basic json test resource --- .../org/oneedtech/inspect/vc/Samples.java | 36 +++++++++++-------- .../test/resources/ob20/basic-assertion.json | 18 ++++++++++ .../test/resources/ob20/basic-badgeclass.json | 10 ++++++ .../src/test/resources/ob20/basic-issuer.json | 8 +++++ 4 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-badgeclass.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-issuer.json 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 f47703a..35b7bf6 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 @@ -3,41 +3,49 @@ package org.oneedtech.inspect.vc; import org.oneedtech.inspect.test.Sample; public class Samples { - public static final class OB30 { - public static final class SVG { + public static final class OB30 { + public static final class SVG { public final static Sample SIMPLE_JSON_SVG = new Sample("ob30/simple-json.svg", true); - public final static Sample SIMPLE_JWT_SVG = new Sample("ob30/simple-jwt.svg", true); + public final static Sample SIMPLE_JWT_SVG = new Sample("ob30/simple-jwt.svg", true); } - public static final class JSON { + public static final class JSON { public final static Sample COMPLETE_JSON = new Sample("ob30/complete.json", false); public final static Sample SIMPLE_JSON = new Sample("ob30/simple.json", true); public final static Sample SIMPLE_DID_METHOD_JSON = new Sample("ob30/simple-did-method.json", true); public final static Sample SIMPLE_JSON_NOPROOF = new Sample("ob30/simple-noproof.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_TYPE = new Sample("ob30/simple-err-type.json", false); - public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); - public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); - public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); - public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_DID_METHOD_ERROR = new Sample("ob30/simple-err-proof-method-unknown-did-method.json", false); - public final static Sample SIMPLE_JSON_PROOF_VALUE_ERROR = new Sample("ob30/simple-err-proof-value.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_DID_METHOD_ERROR = new Sample("ob30/simple-err-proof-method-unknown-did-method.json", false); + public final static Sample SIMPLE_JSON_PROOF_VALUE_ERROR = new Sample("ob30/simple-err-proof-value.json", false); public final static Sample SIMPLE_JSON_EXPIRED = new Sample("ob30/simple-err-expired.json", false); public final static Sample SIMPLE_JSON_ISSUED = new Sample("ob30/simple-err-issued.json", false); public final static Sample SIMPLE_JSON_ISSUER = new Sample("ob30/simple-err-issuer.json", false); public final static Sample SIMPLE_JSON_ERR_CONTEXT = new Sample("ob30/simple-err-context.json", false); } - public static final class PNG { + public static final class PNG { public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true); - public final static Sample SIMPLE_JSON_PNG = new Sample("ob30/simple-json.png", true); + public final static Sample SIMPLE_JSON_PNG = new Sample("ob30/simple-json.png", true); } - public static final class JWT { + public static final class JWT { public final static Sample SIMPLE_JWT = new Sample("ob30/simple.jwt", true); } } - public static final class CLR20 { - public static final class JSON { + public static final class CLR20 { + public static final class JSON { public final static Sample SIMPLE_JSON = new Sample("clr20/simple.json", true); public final static Sample SIMPLE_JSON_NOPROOF = new Sample("clr20/simple-noproof.json", true); public final static Sample SIMPLE_JWT = new Sample("clr20/simple.jwt", true); } } + + public static final class OB20 { + public static final class JSON { + public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); + public final static Sample SIMPLE_BADGECLASS_JSON = new Sample("ob20/basic-badgeclass.json", true); + public final static Sample SIMPLE_ISSUER_JSON = new Sample("ob20/basic-issuer.json", true); + } + } } diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion.json b/inspector-vc/src/test/resources/ob20/basic-assertion.json new file mode 100644 index 0000000..4bc982b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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", + "verification": { + "type": "hosted" + } + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-badgeclass.json b/inspector-vc/src/test/resources/ob20/basic-badgeclass.json new file mode 100644 index 0000000..b53f3cc --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-badgeclass.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/robotics-badge.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/robotics-badge.html", + "issuer": "https://example.org/organization.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-issuer.json b/inspector-vc/src/test/resources/ob20/basic-issuer.json new file mode 100644 index 0000000..151dcc5 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-issuer.json @@ -0,0 +1,8 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization.json", + "name": "An Example Badge Issuer", + "url": "https://example.org", + "email": "contact@example.org" +} \ No newline at end of file From 75d7deffda679e77ab5142a604657f800e932096 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 22 Nov 2022 18:03:06 +0100 Subject: [PATCH 02/93] Added Assertion as a sibling of Credential --- .../inspect/vc/AbstractBaseCredential.java | 110 ++++++++++++++++++ .../org/oneedtech/inspect/vc/Assertion.java | 81 +++++++++++++ .../org/oneedtech/inspect/vc/Credential.java | 107 +++++++---------- 3 files changed, 232 insertions(+), 66 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java new file mode 100644 index 0000000..1262919 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java @@ -0,0 +1,110 @@ +package org.oneedtech.inspect.vc; + +import static org.oneedtech.inspect.util.code.Defensives.*; +import static org.oneedtech.inspect.util.resource.ResourceType.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.oneedtech.inspect.core.probe.GeneratedObject; +import org.oneedtech.inspect.schema.SchemaKey; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.base.MoreObjects; + + +/** + * Base credential class for OB 2.0 Assertions and OB 3.0 and CLR 2.0 Credentials. + * This contains e.g. the origin resource and the extracted JSON data. + * @author xaracil + */ +public abstract class AbstractBaseCredential extends GeneratedObject { + final Resource resource; + final JsonNode jsonData; + final String jwt; + final Map schemas; + + protected AbstractBaseCredential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { + super(id, GeneratedObject.Type.INTERNAL); + this.resource = checkNotNull(resource); + this.jsonData = checkNotNull(data); + this.jwt = jwt; //may be null + this.schemas = schemas; + + checkTrue(RECOGNIZED_PAYLOAD_TYPES.contains(resource.getType())); + } + + public Resource getResource() { + return resource; + } + + public JsonNode getJson() { + return jsonData; + } + + public Optional getJwt() { + return Optional.ofNullable(jwt); + } + + /** + * Get the canonical schema for this credential if such exists. + */ + public Optional getSchemaKey() { + return Optional.ofNullable(schemas.get(getCredentialType())); + } + + public abstract String getCredentialType(); + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("resource", resource.getID()) + .add("resourceType", resource.getType()) + .add("json", jsonData) + .add("jwt", jwt) + .toString(); + } + + public static final List RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT); + public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY"; + + public abstract static class Builder { + private Resource resource; + private JsonNode jsonData; + private String jwt; + + public abstract B build(); + + public Builder resource(Resource resource) { + this.resource = resource; + return this; + } + + public Builder jsonData(JsonNode node) { + this.jsonData = node; + return this; + } + + public Builder jwt(String jwt) { + this.jwt = jwt; + return this; + } + + protected Resource getResource() { + return resource; + } + + protected JsonNode getJsonData() { + return jsonData; + } + + protected String getJwt() { + return jwt; + } + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java new file mode 100644 index 0000000..8cec78b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -0,0 +1,81 @@ +package org.oneedtech.inspect.vc; + +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Collectors; + +import org.oneedtech.inspect.schema.Catalog; +import org.oneedtech.inspect.schema.SchemaKey; +import org.oneedtech.inspect.util.resource.Resource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; + +/** + * A wrapper object for a OB 2.0 assertion. This contains e.g. the origin resource + * and the extracted JSON data plus any other stuff Probes need. + * @author xaracil + */ +public class Assertion extends AbstractBaseCredential { + + final Assertion.Type assertionType; + + protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { + super(ID, resource, data, jwt, schemas); + + ArrayNode typeNode = (ArrayNode)jsonData.get("type"); + this.assertionType = Assertion.Type.valueOf(typeNode); + } + + @Override + public String getCredentialType() { + return assertionType.toString(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("super", super.toString()) + .add("assertionType", assertionType) + .toString(); + } + + private static final Map schemas = new ImmutableMap.Builder() + .put(Type.Assertion, Catalog.OB_21_ASSERTION_JSON) + .build(); + + public static class Builder extends AbstractBaseCredential.Builder { + + @Override + public Assertion build() { + // transform key of schemas map to string because the type of the key in the base map is generic + // and our specific key is an Enum + return new Assertion(getResource(), getJsonData(), getJwt(), + schemas.entrySet().stream().collect(Collectors.toMap( + entry -> entry.getKey().toString(), + entry -> entry.getValue()))); + } + } + + public enum Type { + Assertion, + Unknown; + + public static Assertion.Type valueOf (ArrayNode typeArray) { + if(typeArray != null) { + Iterator iter = typeArray.iterator(); + while(iter.hasNext()) { + String value = iter.next().asText(); + if(value.equals("Assertion")) { + return Assertion; + } + } + } + return Unknown; + } + } + + public static final String ID = Assertion.class.getCanonicalName(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 1b3c501..ecd1722 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -1,19 +1,17 @@ package org.oneedtech.inspect.vc; -import static org.oneedtech.inspect.util.code.Defensives.*; -import static org.oneedtech.inspect.util.resource.ResourceType.*; -import static org.oneedtech.inspect.vc.Credential.Type.*; +import static org.oneedtech.inspect.vc.Credential.Type.AchievementCredential; +import static org.oneedtech.inspect.vc.Credential.Type.ClrCredential; +import static org.oneedtech.inspect.vc.Credential.Type.EndorsementCredential; +import static org.oneedtech.inspect.vc.Credential.Type.VerifiablePresentation; import java.util.Iterator; -import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.stream.Collectors; -import org.oneedtech.inspect.core.probe.GeneratedObject; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; -import org.oneedtech.inspect.util.resource.ResourceType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -21,76 +19,45 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; /** - * A wrapper object for a verifiable credential. This contains e.g. the origin resource - * and the extracted JSON data plus any other stuff Probes need. + * A wrapper object for a verifiable credential. This contains e.g. the origin resource + * and the extracted JSON data plus any other stuff Probes need. * @author mgylling */ -public class Credential extends GeneratedObject { - final Resource resource; - final JsonNode jsonData; +public class Credential extends AbstractBaseCredential { final Credential.Type credentialType; - final String jwt; - - public Credential(Resource resource, JsonNode data, String jwt) { - super(ID, GeneratedObject.Type.INTERNAL); - this.resource = checkNotNull(resource); - this.jsonData = checkNotNull(data); - this.jwt = jwt; //may be null - - checkTrue(RECOGNIZED_PAYLOAD_TYPES.contains(resource.getType())); - + + protected Credential(Resource resource, JsonNode data, String jwt, Map schemas) { + super(ID, resource, data, jwt, schemas); + ArrayNode typeNode = (ArrayNode)jsonData.get("type"); this.credentialType = Credential.Type.valueOf(typeNode); - } - - public Credential(Resource resource, JsonNode data) { - this(resource, data, null); - } - - public Resource getResource() { - return resource; - } - - public JsonNode getJson() { - return jsonData; + } + + public String getCredentialType() { + return credentialType.toString(); } - public Credential.Type getCredentialType() { - return credentialType; - } - - public Optional getJwt() { - return Optional.ofNullable(jwt); - } - public ProofType getProofType() { return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; } - - + private static final Map schemas = new ImmutableMap.Builder() .put(AchievementCredential, Catalog.OB_30_ACHIEVEMENTCREDENTIAL_JSON) .put(ClrCredential, Catalog.CLR_20_CLRCREDENTIAL_JSON) .put(VerifiablePresentation, Catalog.CLR_20_CLRCREDENTIAL_JSON) - .put(EndorsementCredential, Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON) + .put(EndorsementCredential, Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON) .build(); - - /** - * Get the canonical schema for this credential if such exists. - */ - public Optional getSchemaKey() { - return Optional.ofNullable(schemas.get(credentialType)); - } - + + public enum Type { AchievementCredential, OpenBadgeCredential, //treated as an alias of AchievementCredential - ClrCredential, + ClrCredential, EndorsementCredential, VerifiablePresentation, VerifiableCredential, //this is an underspecifier in our context - Unknown; - + Unknown; + public static Credential.Type valueOf (ArrayNode typeArray) { if(typeArray != null) { Iterator iter = typeArray.iterator(); @@ -110,24 +77,32 @@ public class Credential extends GeneratedObject { return Unknown; } } - + public enum ProofType { EXTERNAL, EMBEDDED } - + @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("resource", resource.getID()) - .add("resourceType", resource.getType()) + return MoreObjects.toStringHelper(this) + .add("super", super.toString()) .add("credentialType", credentialType) - .add("json", jsonData) .toString(); } - + + public static class Builder extends AbstractBaseCredential.Builder { + @Override + public Credential build() { + // transform key of schemas map to string because the type of the key in the base map is generic + // and our specific key is an Enum + return new Credential(getResource(), getJsonData(), getJwt(), + schemas.entrySet().stream().collect(Collectors.toMap( + entry -> entry.getKey().toString(), + entry -> entry.getValue()))); + } + } + public static final String ID = Credential.class.getCanonicalName(); - public static final List RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT); - public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY"; - + } From 6c9485cf029cb9b1fe2ce3d4c522d5a961b682c3 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 22 Nov 2022 18:04:02 +0100 Subject: [PATCH 03/93] parse now return the generic credential, with keys from context --- .../inspect/vc/payload/JsonParser.java | 14 ++-- .../inspect/vc/payload/JwtParser.java | 12 ++- .../inspect/vc/payload/PayloadParser.java | 18 +++-- .../inspect/vc/payload/PngParser.java | 62 +++++++++------ .../inspect/vc/payload/SvgParser.java | 61 ++++++++++----- .../vc/probe/CredentialParseProbe.java | 27 +++---- .../vc/credential/PayloadParserTests.java | 75 +++++++++---------- 7 files changed, 164 insertions(+), 105 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java index 6552312..c500743 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java @@ -4,10 +4,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.oneedtech.inspect.util.code.Defensives.checkTrue; import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.RunContext.Key; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.Credential; - +import org.oneedtech.inspect.vc.AbstractBaseCredential; import com.fasterxml.jackson.databind.JsonNode; /** @@ -22,11 +22,15 @@ public final class JsonParser extends PayloadParser { } @Override - public Credential parse(Resource resource, RunContext ctx) throws Exception { + public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { checkTrue(resource.getType() == ResourceType.JSON); String json = resource.asByteSource().asCharSource(UTF_8).read(); - JsonNode node = fromString(json, ctx); - return new Credential(resource, node); + JsonNode node = fromString(json, ctx); + + return getBuilder(ctx) + .resource(resource) + .jsonData(node) + .build(); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java index 3a946b1..eece82b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java @@ -6,7 +6,7 @@ import static org.oneedtech.inspect.util.code.Defensives.checkTrue; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import com.fasterxml.jackson.databind.JsonNode; @@ -22,11 +22,15 @@ public final class JwtParser extends PayloadParser { } @Override - public Credential parse(Resource resource, RunContext ctx) throws Exception { + public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { checkTrue(resource.getType() == ResourceType.JWT); String jwt = resource.asByteSource().asCharSource(UTF_8).read(); - JsonNode node = fromJwt(jwt, ctx); - return new Credential(resource, node, jwt); + JsonNode node = fromJwt(jwt, ctx); + return getBuilder(ctx) + .resource(resource) + .jsonData(node) + .jwt(jwt) + .build(); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java index 41ccb2c..8a18c88 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java @@ -7,6 +7,7 @@ import java.util.Base64.Decoder; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; @@ -20,13 +21,18 @@ import com.google.common.base.Splitter; public abstract class PayloadParser { public abstract boolean supports(ResourceType type); - - public abstract Credential parse(Resource source, RunContext ctx) throws Exception; + + public abstract AbstractBaseCredential parse(Resource source, RunContext ctx) throws Exception; + + @SuppressWarnings("rawtypes") + public static AbstractBaseCredential.Builder getBuilder(RunContext context) { + return ((AbstractBaseCredential.Builder) context.get(RunContext.Key.GENERATED_OBJECT_BUILDER)); + } protected static JsonNode fromString(String json, RunContext context) throws Exception { return ((ObjectMapper)context.get(RunContext.Key.JACKSON_OBJECTMAPPER)).readTree(json); } - + /** * Decode as per https://www.imsglobal.org/spec/ob/v3p0/#jwt-proof * @return The decoded JSON String @@ -34,11 +40,11 @@ public abstract class PayloadParser { public static JsonNode fromJwt(String jwt, RunContext context) throws Exception { List parts = Splitter.on('.').splitToList(jwt); if(parts.size() != 3) throw new IllegalArgumentException("invalid jwt"); - + final Decoder decoder = Base64.getUrlDecoder(); /* * For this step we are only deserializing the stored badge out of the payload. - * The entire jwt is stored separately for signature verification later. + * The entire jwt is stored separately for signature verification later. */ String jwtPayload = new String(decoder.decode(parts.get(1))); @@ -48,5 +54,5 @@ public abstract class PayloadParser { return vcNode; } - + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java index f521e47..31ab22a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java @@ -11,7 +11,7 @@ import javax.imageio.metadata.IIOMetadata; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -29,12 +29,13 @@ public final class PngParser extends PayloadParser { } @Override - public Credential parse(Resource resource, RunContext ctx) throws Exception { - + public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + checkTrue(resource.getType() == ResourceType.PNG); - + try(InputStream is = resource.asByteSource().openStream()) { - + final Keys credentialKey = (Keys) ctx.get(RunContext.Key.PNG_CREDENTIAL_KEY); + ImageReader imageReader = ImageIO.getImageReadersByFormatName("png").next(); imageReader.setInput(ImageIO.createImageInputStream(is), true); IIOMetadata metadata = imageReader.getImageMetadata(0); @@ -43,20 +44,20 @@ public final class PngParser extends PayloadParser { String jwtString = null; String formatSearch = null; JsonNode vcNode = null; - + String[] names = metadata.getMetadataFormatNames(); int length = names.length; for (int i = 0; i < length; i++) { //Check all names rather than limiting to PNG format to remain malleable through any library changes. (Could limit to "javax_imageio_png_1.0") - formatSearch = getOpenBadgeCredentialNodeText(metadata.getAsTree(names[i])); - if(formatSearch != null) { - vcString = formatSearch; + formatSearch = getOpenBadgeCredentialNodeText(metadata.getAsTree(names[i]), credentialKey); + if(formatSearch != null) { + vcString = formatSearch; break; } } - if(vcString == null) { - throw new IllegalArgumentException("No credential inside PNG"); + if(vcString == null) { + throw new IllegalArgumentException("No credential inside PNG"); } vcString = vcString.trim(); @@ -68,29 +69,33 @@ public final class PngParser extends PayloadParser { else { vcNode = fromString(vcString, ctx); } - - return new Credential(resource, vcNode, jwtString); + + return getBuilder(ctx) + .resource(resource) + .jsonData(vcNode) + .jwt(jwtString) + .build(); } } - - private String getOpenBadgeCredentialNodeText(Node node){ + + private String getOpenBadgeCredentialNodeText(Node node, Keys credentialKey){ NamedNodeMap attributes = node.getAttributes(); //If this node is labeled with the attribute keyword: 'openbadgecredential' it is the right one. Node keyword = attributes.getNamedItem("keyword"); - if(keyword != null && keyword.getNodeValue().equals("openbadgecredential")){ + if(keyword != null && keyword.getNodeValue().equals(credentialKey.getNodeName())){ Node textAttribute = attributes.getNamedItem("text"); - if(textAttribute != null) { - return textAttribute.getNodeValue(); + if(textAttribute != null) { + return textAttribute.getNodeValue(); } } //iterate over all children depth first and search for the credential node. Node child = node.getFirstChild(); while (child != null) { - String nodeValue = getOpenBadgeCredentialNodeText(child); - if(nodeValue != null) { - return nodeValue; + String nodeValue = getOpenBadgeCredentialNodeText(child, credentialKey); + if(nodeValue != null) { + return nodeValue; } child = child.getNextSibling(); } @@ -99,4 +104,19 @@ public final class PngParser extends PayloadParser { return null; } + public enum Keys { + OB20("openbadges"), + OB30("openbadgecredential"), + CLR20("clrcredential"); + + private String nodeName; + + private Keys(String nodeName) { + this.nodeName = nodeName; + } + + public String getNodeName() { + return nodeName; + } + } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java index f600bc9..34f81fa 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java @@ -11,11 +11,10 @@ import javax.xml.stream.events.Characters; import javax.xml.stream.events.XMLEvent; import org.oneedtech.inspect.core.probe.RunContext; -import org.oneedtech.inspect.util.code.Defensives; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.xml.XMLInputFactoryCache; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import com.fasterxml.jackson.databind.JsonNode; @@ -31,49 +30,75 @@ public final class SvgParser extends PayloadParser { } @Override - public Credential parse(Resource resource, RunContext ctx) throws Exception { - + public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + final QNames qNames = (QNames) ctx.get(RunContext.Key.SVG_CREDENTIAL_QNAME); + checkTrue(resource.getType() == ResourceType.SVG); - + try(InputStream is = resource.asByteSource().openStream()) { XMLEventReader reader = XMLInputFactoryCache.getInstance().createXMLEventReader(is); while(reader.hasNext()) { XMLEvent ev = reader.nextEvent(); - if(isEndElem(ev, OB_CRED_ELEM)) break; - if(isStartElem(ev, OB_CRED_ELEM)) { - Attribute verifyAttr = ev.asStartElement().getAttributeByName(OB_CRED_VERIFY_ATTR); + if(isEndElem(ev, qNames.getCredentialElem())) break; + if(isStartElem(ev, qNames.getCredentialElem())) { + Attribute verifyAttr = ev.asStartElement().getAttributeByName(qNames.getVerifyElem()); if(verifyAttr != null) { String jwt = verifyAttr.getValue(); JsonNode node = fromJwt(jwt, ctx); - return new Credential(resource, node, jwt); + return getBuilder(ctx).resource(resource).jsonData(node).jwt(jwt).build(); } else { while(reader.hasNext()) { ev = reader.nextEvent(); - if(isEndElem(ev, OB_CRED_ELEM)) break; + if(isEndElem(ev, qNames.getCredentialElem())) break; if(ev.getEventType() == XMLEvent.CHARACTERS) { Characters chars = ev.asCharacters(); - if(!chars.isWhiteSpace()) { + if(!chars.isWhiteSpace()) { JsonNode node = fromString(chars.getData(), ctx); - return new Credential(resource, node); + return getBuilder(ctx).resource(resource).jsonData(node).build(); } } - } + } } } } //while(reader.hasNext()) { } - throw new IllegalArgumentException("No credential inside SVG"); - + throw new IllegalArgumentException("No credential inside SVG"); + } private boolean isEndElem(XMLEvent ev, QName name) { return ev.isEndElement() && ev.asEndElement().getName().equals(name); } - + private boolean isStartElem(XMLEvent ev, QName name) { return ev.isStartElement() && ev.asStartElement().getName().equals(name); } - - private static final QName OB_CRED_ELEM = new QName("https://purl.imsglobal.org/ob/v3p0", "credential"); + private static final QName OB_CRED_VERIFY_ATTR = new QName("verify"); + + /* + * Know QNames whre the credential is baked into the SVG + */ + public enum QNames { + OB20(new QName("http://openbadges.org", "assertion"), OB_CRED_VERIFY_ATTR), + OB30(new QName("https://purl.imsglobal.org/ob/v3p0", "credential"), OB_CRED_VERIFY_ATTR), + CLR20(new QName("https://purl.imsglobal.org/clr/v2p0", "credential"), OB_CRED_VERIFY_ATTR); + + private final QName credentialElem; + private final QName verifyElem; + + private QNames(QName credentialElem, QName verifyElem) { + this.credentialElem = credentialElem; + this.verifyElem = verifyElem; + } + + public QName getCredentialElem() { + return credentialElem; + } + + public QName getVerifyElem() { + return verifyElem; + } + + } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java index 02a549b..3827e90 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java @@ -8,26 +8,27 @@ import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.resource.detect.TypeDetector; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.payload.PayloadParserFactory; /** * A probe that verifies that the incoming credential resource is of a recognized payload type - * and if so extracts and stores the VC json data (a 'Credential' instance) - * in the RunContext. + * and if so extracts and stores the VC json data (a 'Credential' instance) + * in the RunContext. * @author mgylling */ public class CredentialParseProbe extends Probe { - + @Override public ReportItems run(Resource resource, RunContext context) throws Exception { - + try { - + //TODO if .detect reads from a URIResource twice. Cache the resource on first call. - + Optional type = Optional.ofNullable(resource.getType()); - if(type.isEmpty() || type.get() == ResourceType.UNKNOWN) { + if(type.isEmpty() || type.get() == ResourceType.UNKNOWN) { type = TypeDetector.detect(resource, true); if(type.isEmpty()) { //TODO if URI fetch, TypeDetector likely to fail @@ -37,18 +38,18 @@ public class CredentialParseProbe extends Probe { resource.setType(type.get()); } } - + if(!Credential.RECOGNIZED_PAYLOAD_TYPES.contains(type.get())) { return fatal("Payload type not supported: " + type.get().getName(), context); } - - Credential crd = PayloadParserFactory.of(resource).parse(resource, context); + + AbstractBaseCredential crd = PayloadParserFactory.of(resource).parse(resource, context); context.addGeneratedObject(crd); return success(this, context); - + } catch (Exception e) { return fatal("Error while parsing credential: " + e.getMessage(), context); - } + } } - + } diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java index 28b8e7c..1bf3f88 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java @@ -1,10 +1,9 @@ package org.oneedtech.inspect.vc.credential; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; -import java.util.Optional; - import org.junit.jupiter.api.Test; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.RunContext.Key; @@ -12,7 +11,7 @@ import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; 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.vc.Credential; +import org.oneedtech.inspect.vc.AbstractBaseCredential; import org.oneedtech.inspect.vc.OB30Inspector; import org.oneedtech.inspect.vc.Samples; import org.oneedtech.inspect.vc.payload.PayloadParser; @@ -21,28 +20,14 @@ import org.oneedtech.inspect.vc.payload.PayloadParserFactory; import com.fasterxml.jackson.databind.ObjectMapper; public class PayloadParserTests { - - @Test + + @Test void testSvgStringExtract() { assertDoesNotThrow(()->{ Resource res = Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource(ResourceType.SVG); PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); - //System.out.println(crd.getJson().toPrettyString()); - assertNotNull(crd); - assertNotNull(crd.getJson()); - assertNotNull(crd.getJson().get("@context")); - }); - } - - @Test - void testSvgJwtExtract() { - assertDoesNotThrow(()->{ - Resource res = Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource(ResourceType.SVG); - PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -50,62 +35,76 @@ public class PayloadParserTests { }); } - @Test + @Test + void testSvgJwtExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource(ResourceType.SVG); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test void testPngStringExtract() { assertDoesNotThrow(()->{ Resource res = Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource(ResourceType.PNG); PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); assertNotNull(crd.getJson().get("@context")); }); } - - @Test + + @Test void testPngJwtExtract() { assertDoesNotThrow(()->{ Resource res = Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource(ResourceType.PNG); PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); assertNotNull(crd.getJson().get("@context")); }); } - - @Test + + @Test void testJwtExtract() { assertDoesNotThrow(()->{ Resource res = Samples.OB30.JWT.SIMPLE_JWT.asFileResource(ResourceType.JWT); PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); assertNotNull(crd.getJson().get("@context")); }); } - - @Test + + @Test void testJsonExtract() { assertDoesNotThrow(()->{ Resource res = Samples.OB30.JSON.SIMPLE_JSON.asFileResource(ResourceType.JSON); PayloadParser ext = PayloadParserFactory.of(res); - assertNotNull(ext); - Credential crd = ext.parse(res, mockOB30Context(res)); + assertNotNull(ext); + AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); assertNotNull(crd.getJson().get("@context")); }); } - + private RunContext mockOB30Context(Resource res) { ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); From e56739370fde06722625deb73ebfb1b030ccfcb7 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 22 Nov 2022 18:04:29 +0100 Subject: [PATCH 04/93] Set new context keys and use the new credentials builder --- .../oneedtech/inspect/vc/OB30Inspector.java | 131 ++++++++++-------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 96e4236..c90a864 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -5,7 +5,7 @@ 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.code.Defensives.*; 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.AbstractBaseCredential.CREDENTIAL_KEY; import static org.oneedtech.inspect.vc.Credential.ProofType.EXTERNAL; import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; @@ -35,6 +35,8 @@ import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.util.resource.context.ResourceContext; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.payload.PngParser; +import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe; @@ -57,111 +59,118 @@ import com.google.common.collect.ImmutableList; */ public class OB30Inspector extends VCInspector implements SubInspector { protected final List> userProbes; - - protected OB30Inspector(OB30Inspector.Builder builder) { - super(builder); - this.userProbes = ImmutableList.copyOf(builder.probes); + + protected OB30Inspector(OB30Inspector.Builder builder) { + super(builder); + this.userProbes = ImmutableList.copyOf(builder.probes); } - + //https://docs.google.com/document/d/1_imUl2K-5tMib0AUxwA9CWb0Ap1b3qif0sXydih68J0/edit# //https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#verificaton-and-validation - + /* * This inspector supports both standalone openbadge verification, as well as verification of - * AchievementCredentials embedded in e.g. CLR. - * + * AchievementCredentials embedded in e.g. CLR. + * * When verifying a standalone AchievementCredential, call the run(Resource) method. When verifying - * an embedded AchievementCredential, call the run(Resource, Map) method. + * an embedded AchievementCredential, call the run(Resource, Map) method. */ - + @Override - public Report run(Resource resource) { + public Report run(Resource resource) { super.check(resource); //TODO because URIs, this should be a fetch and cache - + if(getBehavior(RESET_CACHES_ON_RUN) == TRUE) { JsonSchemaCache.reset(); CachingDocumentLoader.reset(); } - + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); - + RunContext ctx = new RunContext.Builder() .put(this) .put(resource) .put(Key.JACKSON_OBJECTMAPPER, mapper) .put(Key.JSONPATH_EVALUATOR, jsonPath) - .build(); - + .put(Key.GENERATED_OBJECT_BUILDER, new Credential.Builder()) + .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) + .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) + .build(); + List accumulator = new ArrayList<>(); int probeCount = 0; - - try { + + try { //detect type (png, svg, json, jwt) and extract json data probeCount++; - accumulator.add(new CredentialParseProbe().run(resource, ctx)); + accumulator.add(new CredentialParseProbe().run(resource, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - - //we expect the above to place a generated object in the context + + //we expect the above to place a generated object in the context Credential ob = ctx.getGeneratedObject(Credential.ID); - + //call the subinspector method of this Report subReport = this.run(resource, Map.of(Credential.CREDENTIAL_KEY, ob)); probeCount += subReport.getSummary().getTotalRun(); accumulator.add(subReport); - + //finally, run any user-added probes for(Probe probe : userProbes) { probeCount++; accumulator.add(probe.run(ob, ctx)); } - + } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); } - - return new Report(ctx, new ReportItems(accumulator), probeCount); + + return new Report(ctx, new ReportItems(accumulator), probeCount); } - + @Override public Report run(Resource resource, Map parentObjects) { - + Credential ob = checkNotNull((Credential)parentObjects.get(CREDENTIAL_KEY)); - + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + Credential.Builder credentialBuilder = new Credential.Builder(); RunContext ctx = new RunContext.Builder() .put(this) .put(resource) .put(Key.JACKSON_OBJECTMAPPER, mapper) .put(Key.JSONPATH_EVALUATOR, jsonPath) + .put(Key.GENERATED_OBJECT_BUILDER, credentialBuilder) + .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) + .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) .build(); - + List accumulator = new ArrayList<>(); int probeCount = 0; - + try { - + //context and type properties Credential.Type type = Type.OpenBadgeCredential; - for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { probeCount++; accumulator.add(probe.run(ob.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - + //canonical schema and inline schemata SchemaKey schema = ob.getSchemaKey().orElseThrow(); - for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { + for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { probeCount++; accumulator.add(probe.run(ob.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - - //credentialSubject + + //credentialSubject probeCount++; accumulator.add(new CredentialSubjectProbe().run(ob.getJson(), ctx)); - + //signatures, proofs probeCount++; if(ob.getProofType() == EXTERNAL){ @@ -169,57 +178,57 @@ public class OB30Inspector extends VCInspector implements SubInspector { accumulator.add(new ExternalProofProbe().run(ob, ctx)); } else { //The credential not contained in a jwt, must have an internal proof. - accumulator.add(new EmbeddedProofProbe().run(ob, ctx)); + accumulator.add(new EmbeddedProofProbe().run(ob, ctx)); } if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - + //check refresh service if we are not already refreshed probeCount++; if(resource.getContext().get(REFRESHED) != TRUE) { - Optional newID = checkRefreshService(ob, ctx); - if(newID.isPresent()) { + Optional newID = checkRefreshService(ob, ctx); + if(newID.isPresent()) { return this.run( new UriResource(new URI(newID.get())) .setContext(new ResourceContext(REFRESHED, TRUE))); } } - + //revocation, expiration and issuance - for(Probe probe : List.of(new RevocationListProbe(), - new ExpirationProbe(), new IssuanceProbe())) { + for(Probe probe : List.of(new RevocationListProbe(), + new ExpirationProbe(), new IssuanceProbe())) { probeCount++; accumulator.add(probe.run(ob, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - - //embedded endorsements - EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); - + + //embedded endorsements + EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); + List endorsements = asNodeList(ob.getJson(), "$..endorsement", jsonPath); for(JsonNode node : endorsements) { probeCount++; - Credential endorsement = new Credential(resource, node); + Credential endorsement = credentialBuilder.resource(resource).jsonData(node).build(); accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); - } - - //embedded jwt endorsements + } + + //embedded jwt endorsements endorsements = asNodeList(ob.getJson(), "$..endorsementJwt", jsonPath); for(JsonNode node : endorsements) { probeCount++; String jwt = node.asText(); JsonNode vcNode = fromJwt(jwt, ctx); - Credential endorsement = new Credential(resource, vcNode, jwt); + Credential endorsement = credentialBuilder.resource(resource).jsonData(node).jwt(jwt).build(); accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); } - + } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); } - - return new Report(ctx, new ReportItems(accumulator), probeCount); - + + return new Report(ctx, new ReportItems(accumulator), probeCount); + } - + public static class Builder extends VCInspector.Builder { @SuppressWarnings("unchecked") @Override @@ -228,5 +237,5 @@ public class OB30Inspector extends VCInspector implements SubInspector { set(ResourceType.OPENBADGE); return new OB30Inspector(this); } - } + } } \ No newline at end of file From 0e077c7256cedacb2782bd86728b99eda6e6cbc0 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 22 Nov 2022 18:04:42 +0100 Subject: [PATCH 05/93] First empty inspector for OB20 --- .../oneedtech/inspect/vc/OB20Inspector.java | 93 +++++++++++++++++++ .../org/oneedtech/inspect/vc/OB20Tests.java | 33 +++++++ 2 files changed, 126 insertions(+) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java create mode 100644 inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java 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 new file mode 100644 index 0000000..997260e --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java @@ -0,0 +1,93 @@ +package org.oneedtech.inspect.vc; + +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.oneedtech.inspect.core.Inspector; +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.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.core.report.ReportItems; +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.spec.Specification; +import org.oneedtech.inspect.vc.payload.PngParser; +import org.oneedtech.inspect.vc.payload.SvgParser; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * A verifier for Open Badges 2.0. + * @author xaracil + */ +public class OB20Inspector extends Inspector { + + protected OB20Inspector(OB20Inspector.Builder builder) { + super(builder); + } + + @Override + public Report run(Resource resource) { + super.check(resource); + + if(getBehavior(RESET_CACHES_ON_RUN) == TRUE) { + JsonSchemaCache.reset(); + CachingDocumentLoader.reset(); + } + + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + RunContext ctx = new RunContext.Builder() + .put(this) + .put(resource) + .put(Key.JACKSON_OBJECTMAPPER, mapper) + .put(Key.JSONPATH_EVALUATOR, jsonPath) + .put(Key.GENERATED_OBJECT_BUILDER, new Assertion.Builder()) + .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB20) + .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) + .build(); + + List accumulator = new ArrayList<>(); + int probeCount = 0; + + try { + //detect type (png, svg, json, jwt) and extract json data + probeCount++; + // accumulator.add(new CredentialParseProbe().run(resource, ctx)); + // if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + + // //we expect the above to place a generated object in the context + // VerifiableCredential ob = ctx.getGeneratedObject(VerifiableCredential.ID); + + + + + } catch (Exception e) { + accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); + } + + return new Report(ctx, new ReportItems(accumulator), probeCount); + } + + public static class Builder extends Inspector.Builder { + @SuppressWarnings("unchecked") + @Override + public OB20Inspector build() { + set(Specification.OB20); + set(ResourceType.OPENBADGE); + return new OB20Inspector(this); + } + } +} 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 new file mode 100644 index 0000000..34505fd --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB20Tests.java @@ -0,0 +1,33 @@ +package org.oneedtech.inspect.vc; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.oneedtech.inspect.test.Assertions.assertValid; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.oneedtech.inspect.core.Inspector.Behavior; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.test.PrintHelper; + +public class OB20Tests { + private static OB20Inspector validator; + private static boolean verbose = true; + + @BeforeAll + static void setup() { + validator = new OB20Inspector.Builder() + .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.VALIDATOR_FAIL_FAST, true) + .build(); + } + + @Test + void testSimpleJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + +} From 3ab9c5230041c650112980f305ede5251a5c8148 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 23 Nov 2022 09:07:49 +0100 Subject: [PATCH 06/93] Credential renamed to VerifiableCredential --- .../inspect/vc/EndorsementInspector.java | 52 +++++++++--------- .../oneedtech/inspect/vc/OB30Inspector.java | 26 ++++----- .../org/oneedtech/inspect/vc/VCInspector.java | 34 ++++++------ ...dential.java => VerifiableCredential.java} | 28 +++++----- .../inspect/vc/payload/PayloadParser.java | 2 +- .../vc/probe/ContextPropertyProbe.java | 16 +++--- .../vc/probe/CredentialParseProbe.java | 4 +- .../inspect/vc/probe/EmbeddedProofProbe.java | 13 +++-- .../inspect/vc/probe/ExpirationProbe.java | 28 +++++----- .../inspect/vc/probe/ExternalProofProbe.java | 24 ++++----- .../vc/probe/InlineJsonSchemaProbe.java | 44 +++++++-------- .../inspect/vc/probe/IssuanceProbe.java | 28 +++++----- .../inspect/vc/probe/RevocationListProbe.java | 54 +++++++++---------- .../inspect/vc/probe/TypePropertyProbe.java | 16 +++--- 14 files changed, 184 insertions(+), 185 deletions(-) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/{Credential.java => VerifiableCredential.java} (71%) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index 2eb643c..b7d29fa 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -5,8 +5,8 @@ import static org.oneedtech.inspect.core.probe.RunContext.Key.*; 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 static org.oneedtech.inspect.vc.Credential.ProofType.EXTERNAL; +import static org.oneedtech.inspect.vc.AbstractBaseCredential.CREDENTIAL_KEY; +import static org.oneedtech.inspect.vc.VerifiableCredential.ProofType.EXTERNAL; import java.net.URI; import java.util.ArrayList; @@ -25,7 +25,7 @@ import org.oneedtech.inspect.util.json.ObjectMapperCache; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.util.resource.context.ResourceContext; -import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.VerifiableCredential.Type; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; @@ -39,32 +39,32 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** - * An inspector for EndorsementCredential objects. + * An inspector for EndorsementCredential objects. * @author mgylling */ public class EndorsementInspector extends VCInspector implements SubInspector { protected > EndorsementInspector(B builder) { super(builder); - } + } @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. - * + * what we get here is a verbatim json node. + * */ - - Credential endorsement = (Credential) checkNotNull(parentObjects.get(CREDENTIAL_KEY)); - + + VerifiableCredential endorsement = (VerifiableCredential) checkNotNull(parentObjects.get(CREDENTIAL_KEY)); + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); - + RunContext ctx = new RunContext.Builder() .put(this) .put(JACKSON_OBJECTMAPPER, mapper) @@ -74,18 +74,18 @@ public class EndorsementInspector extends VCInspector implements SubInspector { List accumulator = new ArrayList<>(); int probeCount = 0; try { - + //context and type properties - Credential.Type type = Type.EndorsementCredential; - for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { + VerifiableCredential.Type type = Type.EndorsementCredential; + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { probeCount++; accumulator.add(probe.run(endorsement.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - + //inline schema (parent inspector has already validated against canonical) accumulator.add(new InlineJsonSchemaProbe().run(endorsement.getJson(), ctx)); - + //signatures, proofs probeCount++; if(endorsement.getProofType() == EXTERNAL){ @@ -93,16 +93,16 @@ public class EndorsementInspector extends VCInspector implements SubInspector { accumulator.add(new ExternalProofProbe().run(endorsement, ctx)); } else { //The credential not contained in a jwt, must have an internal proof. - accumulator.add(new EmbeddedProofProbe().run(endorsement, ctx)); - + accumulator.add(new EmbeddedProofProbe().run(endorsement, ctx)); + } if(broken(accumulator)) return abort(ctx, accumulator, probeCount); //check refresh service if we are not already refreshed (check just like in external CLR) probeCount++; if(resource.getContext().get(REFRESHED) != TRUE) { - Optional newID = checkRefreshService(endorsement, ctx); - if(newID.isPresent()) { + Optional newID = checkRefreshService(endorsement, ctx); + if(newID.isPresent()) { //TODO resource.type return this.run( new UriResource(new URI(newID.get())) @@ -111,8 +111,8 @@ public class EndorsementInspector extends VCInspector implements SubInspector { } //revocation, expiration and issuance - for(Probe probe : List.of(new RevocationListProbe(), - new ExpirationProbe(), new IssuanceProbe())) { + for(Probe probe : List.of(new RevocationListProbe(), + new ExpirationProbe(), new IssuanceProbe())) { probeCount++; accumulator.add(probe.run(endorsement, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); @@ -129,7 +129,7 @@ public class EndorsementInspector extends VCInspector implements SubInspector { public Report run(R resource) { throw new IllegalStateException("must use #run(resource, map)"); } - + public static class Builder extends VCInspector.Builder { @SuppressWarnings("unchecked") @Override @@ -137,5 +137,5 @@ public class EndorsementInspector extends VCInspector implements SubInspector { return new EndorsementInspector(this); } } - + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index c90a864..e60b8e2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -6,7 +6,7 @@ import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; import static org.oneedtech.inspect.util.code.Defensives.*; import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; import static org.oneedtech.inspect.vc.AbstractBaseCredential.CREDENTIAL_KEY; -import static org.oneedtech.inspect.vc.Credential.ProofType.EXTERNAL; +import static org.oneedtech.inspect.vc.VerifiableCredential.ProofType.EXTERNAL; import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; @@ -34,7 +34,7 @@ import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.util.resource.context.ResourceContext; import org.oneedtech.inspect.util.spec.Specification; -import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.VerifiableCredential.Type; import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; @@ -58,7 +58,7 @@ import com.google.common.collect.ImmutableList; * @author mgylling */ public class OB30Inspector extends VCInspector implements SubInspector { - protected final List> userProbes; + protected final List> userProbes; protected OB30Inspector(OB30Inspector.Builder builder) { super(builder); @@ -93,7 +93,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { .put(resource) .put(Key.JACKSON_OBJECTMAPPER, mapper) .put(Key.JSONPATH_EVALUATOR, jsonPath) - .put(Key.GENERATED_OBJECT_BUILDER, new Credential.Builder()) + .put(Key.GENERATED_OBJECT_BUILDER, new VerifiableCredential.Builder()) .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) .build(); @@ -108,15 +108,15 @@ public class OB30Inspector extends VCInspector implements SubInspector { if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); //we expect the above to place a generated object in the context - Credential ob = ctx.getGeneratedObject(Credential.ID); + VerifiableCredential ob = ctx.getGeneratedObject(VerifiableCredential.ID); //call the subinspector method of this - Report subReport = this.run(resource, Map.of(Credential.CREDENTIAL_KEY, ob)); + Report subReport = this.run(resource, Map.of(VerifiableCredential.CREDENTIAL_KEY, ob)); probeCount += subReport.getSummary().getTotalRun(); accumulator.add(subReport); //finally, run any user-added probes - for(Probe probe : userProbes) { + for(Probe probe : userProbes) { probeCount++; accumulator.add(probe.run(ob, ctx)); } @@ -131,11 +131,11 @@ public class OB30Inspector extends VCInspector implements SubInspector { @Override public Report run(Resource resource, Map parentObjects) { - Credential ob = checkNotNull((Credential)parentObjects.get(CREDENTIAL_KEY)); + VerifiableCredential ob = checkNotNull((VerifiableCredential)parentObjects.get(CREDENTIAL_KEY)); ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); - Credential.Builder credentialBuilder = new Credential.Builder(); + VerifiableCredential.Builder credentialBuilder = new VerifiableCredential.Builder(); RunContext ctx = new RunContext.Builder() .put(this) .put(resource) @@ -152,7 +152,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { try { //context and type properties - Credential.Type type = Type.OpenBadgeCredential; + VerifiableCredential.Type type = Type.OpenBadgeCredential; for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { probeCount++; accumulator.add(probe.run(ob.getJson(), ctx)); @@ -194,7 +194,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { } //revocation, expiration and issuance - for(Probe probe : List.of(new RevocationListProbe(), + for(Probe probe : List.of(new RevocationListProbe(), new ExpirationProbe(), new IssuanceProbe())) { probeCount++; accumulator.add(probe.run(ob, ctx)); @@ -207,7 +207,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { List endorsements = asNodeList(ob.getJson(), "$..endorsement", jsonPath); for(JsonNode node : endorsements) { probeCount++; - Credential endorsement = credentialBuilder.resource(resource).jsonData(node).build(); + VerifiableCredential endorsement = credentialBuilder.resource(resource).jsonData(node).build(); accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); } @@ -217,7 +217,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { probeCount++; String jwt = node.asText(); JsonNode vcNode = fromJwt(jwt, ctx); - Credential endorsement = credentialBuilder.resource(resource).jsonData(node).jwt(jwt).build(); + VerifiableCredential endorsement = credentialBuilder.resource(resource).jsonData(node).jwt(jwt).build(); accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); } 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 a4a90dc..19a89bc 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 @@ -18,19 +18,19 @@ import com.fasterxml.jackson.databind.JsonNode; * @author mgylling */ public abstract class VCInspector extends Inspector { - + protected > VCInspector(B builder) { - super(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; @@ -40,15 +40,15 @@ public abstract class VCInspector extends Inspector { } return false; } - + /** - * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the - * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL - * provided, then start the verification process over using the response as input. If the request fails, + * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the + * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL + * provided, then start the verification process over using the response as input. If the request fails, * the credential is invalid. */ - protected Optional checkRefreshService(Credential crd, RunContext ctx) { - JsonNode refreshServiceNode = crd.getJson().get("refreshService"); + protected Optional checkRefreshService(VerifiableCredential crd, RunContext ctx) { + JsonNode refreshServiceNode = crd.getJson().get("refreshService"); if(refreshServiceNode != null) { JsonNode serviceTypeNode = refreshServiceNode.get("type"); if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { @@ -56,22 +56,22 @@ public abstract class VCInspector extends Inspector { if(serviceURINode != null) { return Optional.of(serviceURINode.asText()); } - } - } + } + } return Optional.empty(); } protected static final String REFRESHED = "is.refreshed.credential"; - + public abstract static class Builder> extends Inspector.Builder { - final List> probes; + final List> probes; public Builder() { super(); this.probes = new ArrayList<>(); } - - public VCInspector.Builder add(Probe probe) { + + public VCInspector.Builder add(Probe probe) { probes.add(probe); return this; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java similarity index 71% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index ecd1722..7daf258 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -1,9 +1,9 @@ package org.oneedtech.inspect.vc; -import static org.oneedtech.inspect.vc.Credential.Type.AchievementCredential; -import static org.oneedtech.inspect.vc.Credential.Type.ClrCredential; -import static org.oneedtech.inspect.vc.Credential.Type.EndorsementCredential; -import static org.oneedtech.inspect.vc.Credential.Type.VerifiablePresentation; +import static org.oneedtech.inspect.vc.VerifiableCredential.Type.AchievementCredential; +import static org.oneedtech.inspect.vc.VerifiableCredential.Type.ClrCredential; +import static org.oneedtech.inspect.vc.VerifiableCredential.Type.EndorsementCredential; +import static org.oneedtech.inspect.vc.VerifiableCredential.Type.VerifiablePresentation; import java.util.Iterator; import java.util.Map; @@ -23,14 +23,14 @@ import com.google.common.collect.ImmutableMap; * and the extracted JSON data plus any other stuff Probes need. * @author mgylling */ -public class Credential extends AbstractBaseCredential { - final Credential.Type credentialType; +public class VerifiableCredential extends AbstractBaseCredential { + final VerifiableCredential.Type credentialType; - protected Credential(Resource resource, JsonNode data, String jwt, Map schemas) { + protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { super(ID, resource, data, jwt, schemas); ArrayNode typeNode = (ArrayNode)jsonData.get("type"); - this.credentialType = Credential.Type.valueOf(typeNode); + this.credentialType = VerifiableCredential.Type.valueOf(typeNode); } public String getCredentialType() { @@ -41,7 +41,7 @@ public class Credential extends AbstractBaseCredential { return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; } - private static final Map schemas = new ImmutableMap.Builder() + private static final Map schemas = new ImmutableMap.Builder() .put(AchievementCredential, Catalog.OB_30_ACHIEVEMENTCREDENTIAL_JSON) .put(ClrCredential, Catalog.CLR_20_CLRCREDENTIAL_JSON) .put(VerifiablePresentation, Catalog.CLR_20_CLRCREDENTIAL_JSON) @@ -58,7 +58,7 @@ public class Credential extends AbstractBaseCredential { VerifiableCredential, //this is an underspecifier in our context Unknown; - public static Credential.Type valueOf (ArrayNode typeArray) { + public static VerifiableCredential.Type valueOf (ArrayNode typeArray) { if(typeArray != null) { Iterator iter = typeArray.iterator(); while(iter.hasNext()) { @@ -91,18 +91,18 @@ public class Credential extends AbstractBaseCredential { .toString(); } - public static class Builder extends AbstractBaseCredential.Builder { + public static class Builder extends AbstractBaseCredential.Builder { @Override - public Credential build() { + public VerifiableCredential build() { // transform key of schemas map to string because the type of the key in the base map is generic // and our specific key is an Enum - return new Credential(getResource(), getJsonData(), getJwt(), + return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas.entrySet().stream().collect(Collectors.toMap( entry -> entry.getKey().toString(), entry -> entry.getValue()))); } } - public static final String ID = Credential.class.getCanonicalName(); + public static final String ID = VerifiableCredential.class.getCanonicalName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java index 8a18c88..dc9307f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java @@ -8,7 +8,7 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.vc.AbstractBaseCredential; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index 87a1999..d3fd35d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -1,6 +1,6 @@ package org.oneedtech.inspect.vc.probe; -import static org.oneedtech.inspect.vc.Credential.Type.*; +import static org.oneedtech.inspect.vc.VerifiableCredential.Type.*; import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; @@ -11,7 +11,7 @@ import java.util.Set; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -21,13 +21,13 @@ import com.google.common.collect.ImmutableMap; /** * A Probe that verifies a credential's context property. - * + * * @author mgylling */ public class ContextPropertyProbe extends Probe { - private final Credential.Type type; + private final VerifiableCredential.Type type; - public ContextPropertyProbe(Credential.Type type) { + public ContextPropertyProbe(VerifiableCredential.Type type) { super(ID); this.type = checkNotNull(type); } @@ -45,7 +45,7 @@ public class ContextPropertyProbe extends Probe { .filter(s->s.contains(type)) .findFirst() .orElseThrow(()-> new IllegalArgumentException(type.name() + " not recognized"))); - + List given = JsonNodeUtil.asStringList(contextNode); int pos = 0; for (String uri : expected) { @@ -58,7 +58,7 @@ public class ContextPropertyProbe extends Probe { return success(ctx); } - private final static Map, List> values = new ImmutableMap.Builder, List>() + private final static Map, List> values = new ImmutableMap.Builder, List>() .put(Set.of(OpenBadgeCredential, AchievementCredential, EndorsementCredential), List.of("https://www.w3.org/2018/credentials/v1", //"https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy @@ -69,7 +69,7 @@ public class ContextPropertyProbe extends Probe { // "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy "https://purl.imsglobal.org/spec/clr/v2p0/context.json", "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) - + .build(); public static final String ID = ContextPropertyProbe.class.getSimpleName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java index 3827e90..93cc98d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java @@ -9,7 +9,7 @@ import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.resource.detect.TypeDetector; import org.oneedtech.inspect.vc.AbstractBaseCredential; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.payload.PayloadParserFactory; /** @@ -39,7 +39,7 @@ public class CredentialParseProbe extends Probe { } } - if(!Credential.RECOGNIZED_PAYLOAD_TYPES.contains(type.get())) { + if(!VerifiableCredential.RECOGNIZED_PAYLOAD_TYPES.contains(type.get())) { return fatal("Payload type not supported: " + type.get().getName(), context); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index b3fe3b4..b985fee 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -7,7 +7,7 @@ import java.util.Optional; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.apicatalog.jsonld.StringUtils; import com.apicatalog.jsonld.document.Document; @@ -15,7 +15,6 @@ import com.apicatalog.jsonld.loader.DocumentLoaderOptions; import com.apicatalog.multibase.Multibase; import com.apicatalog.multicodec.Multicodec; import com.apicatalog.multicodec.Multicodec.Codec; -import com.danubetech.verifiablecredentials.VerifiableCredential; import foundation.identity.jsonld.ConfigurableDocumentLoader; import info.weboftrust.ldsignatures.LdProof; @@ -28,7 +27,7 @@ import jakarta.json.JsonStructure; * * @author mgylling */ -public class EmbeddedProofProbe extends Probe { +public class EmbeddedProofProbe extends Probe { public EmbeddedProofProbe() { super(ID); @@ -39,11 +38,11 @@ public class EmbeddedProofProbe extends Probe { * (https://github.com/danubetech/verifiable-credentials-java) */ @Override - public ReportItems run(Credential crd, RunContext ctx) throws Exception { + public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { // TODO: What there are multiple proofs? - VerifiableCredential vc = VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); + com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader(); documentLoader.setEnableHttp(true); documentLoader.setEnableHttps(true); @@ -97,7 +96,7 @@ public class EmbeddedProofProbe extends Probe { if (keyStructure.isEmpty()) { return error("Key document not found at " + method, ctx); } - + // First look for a Ed25519VerificationKey2020 document controller = keyStructure.get().asJsonObject().getString("controller"); if (StringUtils.isBlank(controller)) { @@ -113,7 +112,7 @@ public class EmbeddedProofProbe extends Probe { } else { publicKeyMultibase = keyStructure.get().asJsonObject().getString("publicKeyMultibase"); } - + } catch (Exception e) { return error("Invalid verification key URL: " + e.getMessage(), ctx); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java index fcebd0c..0c9092c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java @@ -5,39 +5,39 @@ import java.time.ZonedDateTime; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; /** - * A Probe that verifies a credential's expiration status + * A Probe that verifies a credential's expiration status * @author mgylling */ -public class ExpirationProbe extends Probe { - +public class ExpirationProbe extends Probe { + public ExpirationProbe() { super(ID); } - + @Override - public ReportItems run(Credential crd, RunContext ctx) throws Exception { - /* - * If the AchievementCredential or EndorsementCredential has an “expirationDate” property - * and the expiration date is prior to the current date, the credential has expired. - */ + public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { + /* + * If the AchievementCredential or EndorsementCredential has an “expirationDate” property + * and the expiration date is prior to the current date, the credential has expired. + */ JsonNode node = crd.getJson().get("expirationDate"); if(node != null) { try { ZonedDateTime expirationDate = ZonedDateTime.parse(node.textValue()); if (ZonedDateTime.now().isAfter(expirationDate)) { return fatal("The credential has expired (expiration date was " + node.asText() + ").", ctx); - } + } } catch (Exception e) { - return exception("Error while checking expirationDate: " + e.getMessage(), ctx.getResource()); + return exception("Error while checking expirationDate: " + e.getMessage(), ctx.getResource()); } } return success(ctx); } - - public static final String ID = ExpirationProbe.class.getSimpleName(); + + public static final String ID = ExpirationProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java index b4bccb0..b14ce3c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java @@ -21,7 +21,7 @@ import org.apache.http.util.EntityUtils; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; @@ -36,32 +36,32 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Splitter; /** - * A Probe that verifies credential external proof (jwt) + * A Probe that verifies credential external proof (jwt) * @author mlyon */ -public class ExternalProofProbe extends Probe { - +public class ExternalProofProbe extends Probe { + public ExternalProofProbe() { super(ID); } - + @Override - public ReportItems run(Credential crd, RunContext ctx) throws Exception { + public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { try { verifySignature(crd, ctx); } catch (Exception e) { return fatal("Error verifying jwt signature: " + e.getMessage(), ctx); - } + } return success(ctx); } - private void verifySignature(Credential crd, RunContext ctx) throws Exception { + private void verifySignature(VerifiableCredential crd, RunContext ctx) throws Exception { checkTrue(crd.getJwt().isPresent(), "no jwt supplied"); checkTrue(crd.getJwt().get().length() > 0, "no jwt supplied"); - + DecodedJWT decodedJwt = null; String jwt = crd.getJwt().get(); - + List parts = Splitter.on('.').splitToList(jwt); if(parts.size() != 3) throw new IllegalArgumentException("invalid jwt"); @@ -85,7 +85,7 @@ public class ExternalProofProbe extends Probe { if(jwk == null && kid == null) { throw new Exception("Key must present in either jwk or kid value."); } if(kid != null){ - //Load jwk JsonNode from url and do the rest the same below. + //Load jwk JsonNode from url and do the rest the same below. //TODO Consider additional testing. String kidUrl = kid.textValue(); String jwkResponse = fetchJwk(kidUrl); @@ -145,7 +145,7 @@ public class ExternalProofProbe extends Probe { return responseString; } - + public static final String ID = ExternalProofProbe.class.getSimpleName(); } \ No newline at end of file diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java index 7f3f86e..30c4324 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java @@ -11,64 +11,64 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.schema.SchemaKey; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; /** - * Detect inline schemas in a credential and run them. + * Detect inline schemas in a credential and run them. * @author mgylling */ public class InlineJsonSchemaProbe extends Probe { private static final Set types = Set.of("1EdTechJsonSchemaValidator2019"); private SchemaKey skip; - + public InlineJsonSchemaProbe() { super(ID); } - + public InlineJsonSchemaProbe(SchemaKey skip) { super(ID); this.skip = skip; } - + @Override public ReportItems run(JsonNode root, RunContext ctx) throws Exception { List accumulator = new ArrayList<>(); - Set ioErrors = new HashSet<>(); + Set ioErrors = new HashSet<>(); - //note - we don't get deep nested ones in e.g. EndorsementCredential + //note - we don't get deep nested ones in e.g. EndorsementCredential JsonNode credentialSchemaNode = root.get("credentialSchema"); if(credentialSchemaNode == null) return success(ctx); - + ArrayNode schemas = (ArrayNode) credentialSchemaNode; //TODO guard this cast - + for(JsonNode schemaNode : schemas) { JsonNode typeNode = schemaNode.get("type"); - if(typeNode == null || !types.contains(typeNode.asText())) continue; - JsonNode idNode = schemaNode.get("id"); + if(typeNode == null || !types.contains(typeNode.asText())) continue; + JsonNode idNode = schemaNode.get("id"); if(idNode == null) continue; - String id = idNode.asText().strip(); - if(ioErrors.contains(id)) continue; - if(equals(skip, id)) continue; - try { + String id = idNode.asText().strip(); + if(ioErrors.contains(id)) continue; + if(equals(skip, id)) continue; + try { accumulator.add(new JsonSchemaProbe(id).run(root, ctx)); - } catch (Exception e) { + } catch (Exception e) { if(!ioErrors.contains(id)) { ioErrors.add(id); accumulator.add(error("Could not read schema resource " + id, ctx)); - } - } + } + } } - + return new ReportItems(accumulator); } - + private boolean equals(SchemaKey key, String id) { if(key == null) return false; - return key.getCanonicalURI().equals(id); + return key.getCanonicalURI().equals(id); } - + public static final String ID = InlineJsonSchemaProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java index 04c5d6b..7b693ff 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java @@ -5,39 +5,39 @@ import java.time.ZonedDateTime; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; /** - * A Probe that verifies a credential's issuance status + * A Probe that verifies a credential's issuance status * @author mgylling */ -public class IssuanceProbe extends Probe { - +public class IssuanceProbe extends Probe { + public IssuanceProbe() { super(ID); } - + @Override - public ReportItems run(Credential crd, RunContext ctx) throws Exception { - /* - * If the AchievementCredential or EndorsementCredential “issuanceDate” + public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { + /* + * If the AchievementCredential or EndorsementCredential “issuanceDate” * property after the current date, the credential is not yet valid. */ - JsonNode node = crd.getJson().get("issuanceDate"); + JsonNode node = crd.getJson().get("issuanceDate"); if(node != null) { try { - ZonedDateTime issuanceDate = ZonedDateTime.parse(node.textValue()); + ZonedDateTime issuanceDate = ZonedDateTime.parse(node.textValue()); if (issuanceDate.isAfter(ZonedDateTime.now())) { return fatal("The credential is not yet issued (issuance date is " + node.asText() + ").", ctx); - } + } } catch (Exception e) { return exception("Error while checking issuanceDate: " + e.getMessage(), ctx.getResource()); } } return success(ctx); - } - - public static final String ID = IssuanceProbe.class.getSimpleName(); + } + + public static final String ID = IssuanceProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java index ca11b0f..576c6e4 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java @@ -10,7 +10,7 @@ import java.util.List; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -20,24 +20,24 @@ import com.fasterxml.jackson.databind.ObjectMapper; * A Probe that verifies a credential's revocation status. * @author mgylling */ -public class RevocationListProbe extends Probe { - +public class RevocationListProbe extends Probe { + public RevocationListProbe() { super(ID); } - + @Override - public ReportItems run(Credential crd, RunContext ctx) throws Exception { + public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { /* - * If the AchievementCredential or EndorsementCredential has a “credentialStatus” property - * and the type of the CredentialStatus object is “1EdTechRevocationList”, fetch the - * credential status from the URL provided. If the request is unsuccessful, - * report a warning, not an error. + * If the AchievementCredential or EndorsementCredential has a “credentialStatus” property + * and the type of the CredentialStatus object is “1EdTechRevocationList”, fetch the + * credential status from the URL provided. If the request is unsuccessful, + * report a warning, not an error. */ - + JsonNode credentialStatus = crd.getJson().get("credentialStatus"); - if(credentialStatus != null) { + if(credentialStatus != null) { JsonNode type = credentialStatus.get("type"); if(type != null && type.asText().strip().equals("1EdTechRevocationList")) { JsonNode listID = credentialStatus.get("id"); @@ -46,34 +46,34 @@ public class RevocationListProbe extends Probe { URL url = new URI(listID.asText().strip()).toURL(); try (InputStream is = url.openStream()) { JsonNode revocList = ((ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER)).readTree(is.readAllBytes()); - - /* To check if a credential has been revoked, the verifier issues a GET request - * to the URL of the issuer's 1EdTech Revocation List Status Method. If the - * credential's id is in the list of revokedCredentials and the value of + + /* To check if a credential has been revoked, the verifier issues a GET request + * to the URL of the issuer's 1EdTech Revocation List Status Method. If the + * credential's id is in the list of revokedCredentials and the value of * revoked is true or ommitted, the issuer has revoked the credential. */ - + JsonNode crdID = crd.getJson().get("id"); //TODO these != checks sb removed (trigger warning) if(crdID != null) { List list = JsonNodeUtil.asNodeList(revocList.get("revokedCredentials")); if(list != null) { for(JsonNode item : list) { JsonNode revID = item.get("id"); - JsonNode revoked = item.get("revoked"); + JsonNode revoked = item.get("revoked"); if(revID != null && revID.equals(crdID) && (revoked == null || revoked.asBoolean())) { - return fatal("Credential has been revoked", ctx); + return fatal("Credential has been revoked", ctx); } - } - } + } + } } - } + } } catch (Exception e) { return warning("Error when fetching credentialStatus resource " + e.getMessage(), ctx); - } - } - } + } + } + } } return success(ctx); - } - - public static final String ID = RevocationListProbe.class.getSimpleName(); + } + + public static final String ID = RevocationListProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index 804380e..ed67b8b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -7,8 +7,8 @@ import java.util.List; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.Credential; -import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.VerifiableCredential.Type; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -16,13 +16,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode; /** * A Probe that verifies a credential's type property. - * + * * @author mgylling */ public class TypePropertyProbe extends Probe { - private final Credential.Type expected; + private final VerifiableCredential.Type expected; - public TypePropertyProbe(Credential.Type expected) { + public TypePropertyProbe(VerifiableCredential.Type expected) { super(ID); this.expected = checkNotNull(expected); } @@ -40,15 +40,15 @@ public class TypePropertyProbe extends Probe { return fatal("The type property does not contain the entry 'VerifiableCredential'", ctx); } - if (expected == Credential.Type.OpenBadgeCredential) { + if (expected == VerifiableCredential.Type.OpenBadgeCredential) { if (!values.contains("OpenBadgeCredential") && !values.contains("AchievementCredential")) { return fatal("The type property does not contain one of 'OpenBadgeCredential' or 'AchievementCredential'", ctx); } - } else if (expected == Credential.Type.ClrCredential) { + } else if (expected == VerifiableCredential.Type.ClrCredential) { if (!values.contains("ClrCredential")) { return fatal("The type property does not contain the entry 'ClrCredential'", ctx); } - } else if (expected == Credential.Type.EndorsementCredential) { + } else if (expected == VerifiableCredential.Type.EndorsementCredential) { if (!values.contains("EndorsementCredential")) { return fatal("The type property does not contain the entry 'EndorsementCredential'", ctx); } From 7beb69f87be5acd98c5ca5297fe40463f6216c21 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 23 Nov 2022 09:08:42 +0100 Subject: [PATCH 07/93] AbstractBaseCredential renamed to Credential --- .../java/org/oneedtech/inspect/vc/Assertion.java | 4 ++-- ...AbstractBaseCredential.java => Credential.java} | 6 +++--- .../oneedtech/inspect/vc/EndorsementInspector.java | 2 +- .../org/oneedtech/inspect/vc/OB30Inspector.java | 2 +- .../oneedtech/inspect/vc/VerifiableCredential.java | 4 ++-- .../oneedtech/inspect/vc/payload/JsonParser.java | 4 ++-- .../oneedtech/inspect/vc/payload/JwtParser.java | 4 ++-- .../inspect/vc/payload/PayloadParser.java | 8 ++++---- .../oneedtech/inspect/vc/payload/PngParser.java | 4 ++-- .../oneedtech/inspect/vc/payload/SvgParser.java | 4 ++-- .../inspect/vc/probe/CredentialParseProbe.java | 4 ++-- .../inspect/vc/credential/PayloadParserTests.java | 14 +++++++------- 12 files changed, 30 insertions(+), 30 deletions(-) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/{AbstractBaseCredential.java => Credential.java} (90%) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 8cec78b..e571efd 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableMap; * and the extracted JSON data plus any other stuff Probes need. * @author xaracil */ -public class Assertion extends AbstractBaseCredential { +public class Assertion extends Credential { final Assertion.Type assertionType; @@ -46,7 +46,7 @@ public class Assertion extends AbstractBaseCredential { .put(Type.Assertion, Catalog.OB_21_ASSERTION_JSON) .build(); - public static class Builder extends AbstractBaseCredential.Builder { + public static class Builder extends Credential.Builder { @Override public Assertion build() { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java similarity index 90% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 1262919..82ba4e2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/AbstractBaseCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -22,13 +22,13 @@ import com.google.common.base.MoreObjects; * This contains e.g. the origin resource and the extracted JSON data. * @author xaracil */ -public abstract class AbstractBaseCredential extends GeneratedObject { +public abstract class Credential extends GeneratedObject { final Resource resource; final JsonNode jsonData; final String jwt; final Map schemas; - protected AbstractBaseCredential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { + protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { super(id, GeneratedObject.Type.INTERNAL); this.resource = checkNotNull(resource); this.jsonData = checkNotNull(data); @@ -72,7 +72,7 @@ public abstract class AbstractBaseCredential extends GeneratedObject { public static final List RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT); public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY"; - public abstract static class Builder { + public abstract static class Builder { private Resource resource; private JsonNode jsonData; private String jwt; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index b7d29fa..6879e79 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -5,7 +5,7 @@ import static org.oneedtech.inspect.core.probe.RunContext.Key.*; 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.AbstractBaseCredential.CREDENTIAL_KEY; +import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY; import static org.oneedtech.inspect.vc.VerifiableCredential.ProofType.EXTERNAL; import java.net.URI; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index e60b8e2..51b48c6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -5,7 +5,7 @@ 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.code.Defensives.*; import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; -import static org.oneedtech.inspect.vc.AbstractBaseCredential.CREDENTIAL_KEY; +import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY; import static org.oneedtech.inspect.vc.VerifiableCredential.ProofType.EXTERNAL; import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index 7daf258..b388379 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableMap; * and the extracted JSON data plus any other stuff Probes need. * @author mgylling */ -public class VerifiableCredential extends AbstractBaseCredential { +public class VerifiableCredential extends Credential { final VerifiableCredential.Type credentialType; protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { @@ -91,7 +91,7 @@ public class VerifiableCredential extends AbstractBaseCredential { .toString(); } - public static class Builder extends AbstractBaseCredential.Builder { + public static class Builder extends Credential.Builder { @Override public VerifiableCredential build() { // transform key of schemas map to string because the type of the key in the base map is generic diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java index c500743..8c16ed3 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java @@ -7,7 +7,7 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.RunContext.Key; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; /** @@ -22,7 +22,7 @@ public final class JsonParser extends PayloadParser { } @Override - public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + public Credential parse(Resource resource, RunContext ctx) throws Exception { checkTrue(resource.getType() == ResourceType.JSON); String json = resource.asByteSource().asCharSource(UTF_8).read(); JsonNode node = fromString(json, ctx); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java index eece82b..1769d07 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java @@ -6,7 +6,7 @@ import static org.oneedtech.inspect.util.code.Defensives.checkTrue; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; @@ -22,7 +22,7 @@ public final class JwtParser extends PayloadParser { } @Override - public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + public Credential parse(Resource resource, RunContext ctx) throws Exception { checkTrue(resource.getType() == ResourceType.JWT); String jwt = resource.asByteSource().asCharSource(UTF_8).read(); JsonNode node = fromJwt(jwt, ctx); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java index dc9307f..78bc293 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java @@ -7,7 +7,7 @@ import java.util.Base64.Decoder; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; @@ -22,11 +22,11 @@ public abstract class PayloadParser { public abstract boolean supports(ResourceType type); - public abstract AbstractBaseCredential parse(Resource source, RunContext ctx) throws Exception; + public abstract Credential parse(Resource source, RunContext ctx) throws Exception; @SuppressWarnings("rawtypes") - public static AbstractBaseCredential.Builder getBuilder(RunContext context) { - return ((AbstractBaseCredential.Builder) context.get(RunContext.Key.GENERATED_OBJECT_BUILDER)); + public static Credential.Builder getBuilder(RunContext context) { + return ((Credential.Builder) context.get(RunContext.Key.GENERATED_OBJECT_BUILDER)); } protected static JsonNode fromString(String json, RunContext context) throws Exception { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java index 31ab22a..492bab2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java @@ -11,7 +11,7 @@ import javax.imageio.metadata.IIOMetadata; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -29,7 +29,7 @@ public final class PngParser extends PayloadParser { } @Override - public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + public Credential parse(Resource resource, RunContext ctx) throws Exception { checkTrue(resource.getType() == ResourceType.PNG); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java index 34f81fa..8d9a659 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java @@ -14,7 +14,7 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.xml.XMLInputFactoryCache; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; @@ -30,7 +30,7 @@ public final class SvgParser extends PayloadParser { } @Override - public AbstractBaseCredential parse(Resource resource, RunContext ctx) throws Exception { + public Credential parse(Resource resource, RunContext ctx) throws Exception { final QNames qNames = (QNames) ctx.get(RunContext.Key.SVG_CREDENTIAL_QNAME); checkTrue(resource.getType() == ResourceType.SVG); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java index 93cc98d..1444c50 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java @@ -8,7 +8,7 @@ import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.resource.detect.TypeDetector; -import org.oneedtech.inspect.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.payload.PayloadParserFactory; @@ -43,7 +43,7 @@ public class CredentialParseProbe extends Probe { return fatal("Payload type not supported: " + type.get().getName(), context); } - AbstractBaseCredential crd = PayloadParserFactory.of(resource).parse(resource, context); + Credential crd = PayloadParserFactory.of(resource).parse(resource, context); context.addGeneratedObject(crd); return success(this, context); diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java index 1bf3f88..506b451 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java @@ -11,7 +11,7 @@ import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; 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.vc.AbstractBaseCredential; +import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.OB30Inspector; import org.oneedtech.inspect.vc.Samples; import org.oneedtech.inspect.vc.payload.PayloadParser; @@ -27,7 +27,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource(ResourceType.SVG); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -41,7 +41,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource(ResourceType.SVG); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -55,7 +55,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource(ResourceType.PNG); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -69,7 +69,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource(ResourceType.PNG); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -83,7 +83,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.JWT.SIMPLE_JWT.asFileResource(ResourceType.JWT); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); @@ -97,7 +97,7 @@ public class PayloadParserTests { Resource res = Samples.OB30.JSON.SIMPLE_JSON.asFileResource(ResourceType.JSON); PayloadParser ext = PayloadParserFactory.of(res); assertNotNull(ext); - AbstractBaseCredential crd = ext.parse(res, mockOB30Context(res)); + Credential crd = ext.parse(res, mockOB30Context(res)); //System.out.println(crd.getJson().toPrettyString()); assertNotNull(crd); assertNotNull(crd.getJson()); From 19c97f29376ce2a60e0f7e9321d4abef33dffd10 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 23 Nov 2022 17:18:02 +0100 Subject: [PATCH 08/93] Adap CachingDocumentLoader to ConfigurableDocumentLoader --- .../vc/util/CachingDocumentLoader.java | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 6db88fc..dee682d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -21,21 +21,43 @@ import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; +import foundation.identity.jsonld.ConfigurableDocumentLoader; + /** * A com.apicatalog DocumentLoader with a threadsafe static cache. * * @author mgylling */ -public class CachingDocumentLoader implements DocumentLoader { +public class CachingDocumentLoader extends ConfigurableDocumentLoader { + + + public CachingDocumentLoader() { + super(); + setEnableHttp(true); + setEnableHttps(true); + setDefaultHttpLoader(new HttpLoader()); + } @Override public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { - Tuple tpl = new Tuple<>(url.toASCIIString(), options); - try { - return documentCache.get(tpl); - } catch (Exception e) { + Document document = super.loadDocument(url, options); + if (document == null) { logger.error("documentCache not able to load {}", url); - throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, e.getMessage()); + throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT); + } + return document; + } + + public class HttpLoader implements DocumentLoader { + @Override + public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { + Tuple tpl = new Tuple<>(url.toASCIIString(), options); + try { + return documentCache.get(tpl); + } catch (Exception e) { + logger.error("documentCache not able to load {}", url); + throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, e.getMessage()); + } } } @@ -62,7 +84,7 @@ public class CachingDocumentLoader implements DocumentLoader { .initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24)) .build(new CacheLoader, Document>() { public Document load(final Tuple id) throws Exception { - try (InputStream is = bundled.keySet().contains(id.t1) + try (InputStream is = bundled.containsKey(id.t1) ? bundled.get(id.t1).openStream() : new URI(id.t1).toURL().openStream();) { return JsonDocument.of(is); From b7e7aa2c5dbeb358f32110238fa3c48d25530774 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 23 Nov 2022 17:18:16 +0100 Subject: [PATCH 09/93] Back to CachedDocumentLoader --- .../org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index b985fee..b70d1c3 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -8,6 +8,7 @@ import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.StringUtils; import com.apicatalog.jsonld.document.Document; @@ -16,7 +17,6 @@ import com.apicatalog.multibase.Multibase; import com.apicatalog.multicodec.Multicodec; import com.apicatalog.multicodec.Multicodec.Codec; -import foundation.identity.jsonld.ConfigurableDocumentLoader; import info.weboftrust.ldsignatures.LdProof; import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier; import jakarta.json.JsonObject; @@ -43,10 +43,7 @@ public class EmbeddedProofProbe extends Probe { // TODO: What there are multiple proofs? com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); - ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader(); - documentLoader.setEnableHttp(true); - documentLoader.setEnableHttps(true); - vc.setDocumentLoader(documentLoader); + vc.setDocumentLoader(new CachingDocumentLoader()); LdProof proof = vc.getLdProof(); if (proof == null) { From 904c97acedb50a11b6526e5a68726c9dc3907718 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 23 Nov 2022 17:22:53 +0100 Subject: [PATCH 10/93] Set additional context keys for testing --- .../oneedtech/inspect/vc/credential/PayloadParserTests.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java index 506b451..975721c 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java @@ -14,8 +14,11 @@ import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.OB30Inspector; import org.oneedtech.inspect.vc.Samples; +import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.payload.PayloadParser; import org.oneedtech.inspect.vc.payload.PayloadParserFactory; +import org.oneedtech.inspect.vc.payload.PngParser; +import org.oneedtech.inspect.vc.payload.SvgParser; import com.fasterxml.jackson.databind.ObjectMapper; @@ -113,6 +116,9 @@ public class PayloadParserTests { .put(res) .put(Key.JACKSON_OBJECTMAPPER, mapper) .put(Key.JSONPATH_EVALUATOR, jsonPath) + .put(Key.GENERATED_OBJECT_BUILDER, new VerifiableCredential.Builder()) + .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) + .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) .build(); } } From c47a04aa8c6b96558fc201f21316319f452f5fe6 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 09:11:22 +0100 Subject: [PATCH 11/93] type property is an string node, not an array --- .../org/oneedtech/inspect/vc/Assertion.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index e571efd..b9d67e0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -25,7 +25,7 @@ public class Assertion extends Credential { protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { super(ID, resource, data, jwt, schemas); - ArrayNode typeNode = (ArrayNode)jsonData.get("type"); + JsonNode typeNode = jsonData.get("type"); this.assertionType = Assertion.Type.valueOf(typeNode); } @@ -63,15 +63,12 @@ public class Assertion extends Credential { Assertion, Unknown; - public static Assertion.Type valueOf (ArrayNode typeArray) { - if(typeArray != null) { - Iterator iter = typeArray.iterator(); - while(iter.hasNext()) { - String value = iter.next().asText(); - if(value.equals("Assertion")) { - return Assertion; - } - } + public static Assertion.Type valueOf (JsonNode typeNode) { + if(typeNode != null) { + String value = typeNode.asText(); + if(value.equals("Assertion")) { + return Assertion; + } } return Unknown; } From 677caf6f219c65b7f4c7dd287b6b26a9cc2637b1 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 11:15:53 +0100 Subject: [PATCH 12/93] Updated samples --- .../org/oneedtech/inspect/vc/Samples.java | 4 +- .../resources/ob20/assets/altbadgeurl.json | 10 + .../ob20/assets/beths-robot-badge.png | Bin 0 -> 789 bytes .../ob20/assets/openbadges_context.json | 203 ++++++++++++++++++ .../resources/ob20/assets/organization.json | 8 + .../resources/ob20/assets/robotics-badge.json | 10 + .../resources/ob20/assets/robotics-badge.png | Bin 0 -> 789 bytes .../test/resources/ob20/basic-assertion.json | 34 +-- .../test/resources/ob20/basic-badgeclass.json | 10 - .../src/test/resources/ob20/basic-issuer.json | 8 - .../ob20/redirected-validation-subject.json | 18 ++ .../ob20/warning-with-redirection.json | 18 ++ 12 files changed, 286 insertions(+), 37 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/beths-robot-badge.png create mode 100644 inspector-vc/src/test/resources/ob20/assets/openbadges_context.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/organization.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/robotics-badge.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/robotics-badge.png delete mode 100644 inspector-vc/src/test/resources/ob20/basic-badgeclass.json delete mode 100644 inspector-vc/src/test/resources/ob20/basic-issuer.json create mode 100644 inspector-vc/src/test/resources/ob20/redirected-validation-subject.json create mode 100644 inspector-vc/src/test/resources/ob20/warning-with-redirection.json 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 35b7bf6..3fd3811 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 @@ -44,8 +44,8 @@ public class Samples { public static final class OB20 { public static final class JSON { public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); - public final static Sample SIMPLE_BADGECLASS_JSON = new Sample("ob20/basic-badgeclass.json", true); - public final static Sample SIMPLE_ISSUER_JSON = new Sample("ob20/basic-issuer.json", true); + // public final static Sample SIMPLE_BADGECLASS_JSON = new Sample("ob20/basic-badgeclass.json", true); + // public final static Sample SIMPLE_ISSUER_JSON = new Sample("ob20/basic-issuer.json", true); } } } diff --git a/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json b/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json new file mode 100644 index 0000000..3554627 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/robotics-badge.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/robotics-badge.html", + "issuer": "https://example.org/organization.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/beths-robot-badge.png b/inspector-vc/src/test/resources/ob20/assets/beths-robot-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..abdc4b64511d3d13762695ed85169c2ed5e4015d GIT binary patch literal 789 zcmV+w1M2*VP)O? z4TavM^geB8=D3g=z*dp|SCct!e(%W)!G>6`zv-gd_pV-PX zpL&jcC5qJO>h8*Zn+TYUZOutnI^rkyhZo8-r2G)TU`OT8#;MHDtXZOAAGv+H6FQ0xDkppkeUd`QPZrbEif^pO;Q}omM2) zGBLR%*~`l~fe$Ms$y{&OUl)tapGxQaOjer9=al1qSliuJ#cYJjX1d1p-x(qRFxt6a z@isbG?0Z-OAfR%~02-^D$<8A_Dm$!bx_Ot9*FN7f0ptw8W*Be)J4~h3gk0Snu0ny$ z(VQ&xXb54ofB+!nMOSGx2}#B6r6tBxwO|`lBn8D-oKl%cB~g|L0Hs!w5(>x+v;mqM zaVX#j3DzQN>&-b%E%oS&gvmvw;$HD1oLII%b!@_RH zFs7ySIe?Y_(FhQ>Ta07W<(Th0e(1vbyI(PYOt-fW=y`sZD9gPJaBjnl*er(8Vlubt z4g~rLPy_(FVI%9Dn;Qrh#T9|$h5>5% T+OyS`00000NkvXXu0mjf?b2Oz literal 0 HcmV?d00001 diff --git a/inspector-vc/src/test/resources/ob20/assets/openbadges_context.json b/inspector-vc/src/test/resources/ob20/assets/openbadges_context.json new file mode 100644 index 0000000..7c1caf1 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/openbadges_context.json @@ -0,0 +1,203 @@ +https://w3id.org/openbadges/v2 + +{ + "@context": { + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "AlignmentObject": "schema:AlignmentObject", + "uid": { + "@id": "obi:uid" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "sec": "https://w3id.org/security#", + "Criteria": "obi:Criteria", + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "targetName": { + "@id": "schema:targetName" + }, + "id": "@id", + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "Profile": "obi:Profile", + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "FrameValidation": "obi:FrameValidation", + "validationFrame": "obi:validationFrame", + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "validationSchema": "obi:validationSchema", + "validatesType": "obi:validatesType", + "version": { + "@id": "schema:version" + }, + "BadgeClass": "obi:BadgeClass", + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "RevocationList": "obi:RevocationList", + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "type": "@type", + "email": { + "@id": "schema:email" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "schema": "http://schema.org/", + "targetUrl": { + "@id": "schema:targetUrl" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "description": { + "@id": "schema:description" + }, + "Extension": "obi:Extension", + "tags": { + "@id": "schema:keywords" + }, + "CryptographicKey": "sec:Key", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "hosted": "obi:HostedBadge", + "dc": "http://purl.org/dc/terms/", + "telephone": { + "@id": "schema:telephone" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "genre": { + "@id": "schema:genre" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "HostedBadge": "obi:HostedBadge", + "identity": { + "@id": "obi:identityHash" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "verify": "verification", + "VerificationObject": "obi:VerificationObject", + "name": { + "@id": "schema:name" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "obi": "https://w3id.org/openbadges#", + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "cred": "https://w3id.org/credentials#", + "Image": "obi:Image", + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "IdentityObject": "obi:IdentityObject", + "signed": "obi:SignedBadge", + "Evidence": "obi:Evidence", + "narrative": { + "@id": "obi:narrative" + }, + "caption": { + "@id": "schema:caption" + }, + "audience": { + "@id": "obi:audience" + }, + "extensions": "https://w3id.org/openbadges/extensions#", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "xsd": "http://www.w3.org/2001/XMLSchema#", + "TypeValidation": "obi:TypeValidation", + "revokedAssertions": { + "@id": "obi:revoked" + }, + "SignedBadge": "obi:SignedBadge", + "validation": "obi:validation", + "salt": { + "@id": "obi:salt" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "Issuer": "obi:Issuer" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/organization.json b/inspector-vc/src/test/resources/ob20/assets/organization.json new file mode 100644 index 0000000..559a120 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/organization.json @@ -0,0 +1,8 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization.json", + "name": "An Example Badge Issuer", + "url": "https://example.org", + "email": "contact@example.org" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json b/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json new file mode 100644 index 0000000..3554627 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/robotics-badge.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/robotics-badge.html", + "issuer": "https://example.org/organization.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/robotics-badge.png b/inspector-vc/src/test/resources/ob20/assets/robotics-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..abdc4b64511d3d13762695ed85169c2ed5e4015d GIT binary patch literal 789 zcmV+w1M2*VP)O? z4TavM^geB8=D3g=z*dp|SCct!e(%W)!G>6`zv-gd_pV-PX zpL&jcC5qJO>h8*Zn+TYUZOutnI^rkyhZo8-r2G)TU`OT8#;MHDtXZOAAGv+H6FQ0xDkppkeUd`QPZrbEif^pO;Q}omM2) zGBLR%*~`l~fe$Ms$y{&OUl)tapGxQaOjer9=al1qSliuJ#cYJjX1d1p-x(qRFxt6a z@isbG?0Z-OAfR%~02-^D$<8A_Dm$!bx_Ot9*FN7f0ptw8W*Be)J4~h3gk0Snu0ny$ z(VQ&xXb54ofB+!nMOSGx2}#B6r6tBxwO|`lBn8D-oKl%cB~g|L0Hs!w5(>x+v;mqM zaVX#j3DzQN>&-b%E%oS&gvmvw;$HD1oLII%b!@_RH zFs7ySIe?Y_(FhQ>Ta07W<(Th0e(1vbyI(PYOt-fW=y`sZD9gPJaBjnl*er(8Vlubt z4g~rLPy_(FVI%9Dn;Qrh#T9|$h5>5% T+OyS`00000NkvXXu0mjf?b2Oz literal 0 HcmV?d00001 diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion.json b/inspector-vc/src/test/resources/ob20/basic-assertion.json index 4bc982b..69963d6 100644 --- a/inspector-vc/src/test/resources/ob20/basic-assertion.json +++ b/inspector-vc/src/test/resources/ob20/basic-assertion.json @@ -1,18 +1,18 @@ { - "@context": "https://w3id.org/openbadges/v2", - "type": "Assertion", - "id": "https://example.org/beths-robotics-badge.json", - "recipient": { - "type": "email", - "hashed": true, - "salt": "deadsea", - "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" - }, - "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", - "verification": { - "type": "hosted" - } - } \ No newline at end of file + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-badgeclass.json b/inspector-vc/src/test/resources/ob20/basic-badgeclass.json deleted file mode 100644 index b53f3cc..0000000 --- a/inspector-vc/src/test/resources/ob20/basic-badgeclass.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@context": "https://w3id.org/openbadges/v2", - "type": "BadgeClass", - "id": "https://example.org/robotics-badge.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/robotics-badge.html", - "issuer": "https://example.org/organization.json" -} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-issuer.json b/inspector-vc/src/test/resources/ob20/basic-issuer.json deleted file mode 100644 index 151dcc5..0000000 --- a/inspector-vc/src/test/resources/ob20/basic-issuer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@context": "https://w3id.org/openbadges/v2", - "type": "Issuer", - "id": "https://example.org/organization.json", - "name": "An Example Badge Issuer", - "url": "https://example.org", - "email": "contact@example.org" -} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/redirected-validation-subject.json b/inspector-vc/src/test/resources/ob20/redirected-validation-subject.json new file mode 100644 index 0000000..924a704 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/redirected-validation-subject.json @@ -0,0 +1,18 @@ +{ // both http://example.org/altbadgeurl and https://example.org/beths-robotics-badge.json return this json + "@context": "https://w3id.org/openbadges/v2", // openbadges_context.json + "type": "Assertion", + "id": "http://example.org/altbadgeurl", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", // image + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2016-12-31T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + } + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/warning-with-redirection.json b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json new file mode 100644 index 0000000..a350449 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json @@ -0,0 +1,18 @@ +{ // test_graph:test_verify_with_redirection + "@context": "https://w3id.org/openbadges/v2", // openbadges_context.json + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", // robotics-badge.json + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", // image + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2016-12-31T23:59:59Z", + "badge": "http://example.org/altbadgeurl", // altbadgeurl.json + "verification": { + "type": "hosted" + } +} \ No newline at end of file From 579e19a9297d209848a7bb9d1422bba0dfe43170 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 11:55:58 +0100 Subject: [PATCH 13/93] Added new method for getting context uris --- .../org/oneedtech/inspect/vc/Assertion.java | 8 ++++-- .../org/oneedtech/inspect/vc/Credential.java | 2 ++ .../inspect/vc/VerifiableCredential.java | 25 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index b9d67e0..193a3f0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -1,6 +1,6 @@ package org.oneedtech.inspect.vc; -import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -9,7 +9,6 @@ import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; @@ -34,6 +33,11 @@ public class Assertion extends Credential { return assertionType.toString(); } + @Override + public List getContext() { + return List.of("https://w3id.org/openbadges/v2"); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 82ba4e2..3d74a87 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -59,6 +59,8 @@ public abstract class Credential extends GeneratedObject { public abstract String getCredentialType(); + public abstract List getContext(); + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index b388379..60f48a0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -6,7 +6,9 @@ import static org.oneedtech.inspect.vc.VerifiableCredential.Type.EndorsementCred import static org.oneedtech.inspect.vc.VerifiableCredential.Type.VerifiablePresentation; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.oneedtech.inspect.schema.Catalog; @@ -37,6 +39,15 @@ public class VerifiableCredential extends Credential { return credentialType.toString(); } + @Override + public List getContext() { + return values.get(values.keySet() + .stream() + .filter(s->s.contains(credentialType)) + .findFirst() + .orElseThrow(()-> new IllegalArgumentException(credentialType.name() + " not recognized"))); + } + public ProofType getProofType() { return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; } @@ -48,6 +59,19 @@ public class VerifiableCredential extends Credential { .put(EndorsementCredential, Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON) .build(); + private static final Map, List> values = new ImmutableMap.Builder, List>() + .put(Set.of(Type.OpenBadgeCredential, AchievementCredential, EndorsementCredential), + List.of("https://www.w3.org/2018/credentials/v1", + //"https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy + "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) + .put(Set.of(ClrCredential), + List.of("https://www.w3.org/2018/credentials/v1", + // "https://dc.imsglobal.org/draft/clr/v2p0/context", //dev legacy + // "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy + "https://purl.imsglobal.org/spec/clr/v2p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) + + .build(); public enum Type { AchievementCredential, @@ -104,5 +128,4 @@ public class VerifiableCredential extends Credential { } public static final String ID = VerifiableCredential.class.getCanonicalName(); - } From 7ee4475917841d7ade8cc1a4213753b086d08905 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 11:58:07 +0100 Subject: [PATCH 14/93] Added OB 20 context file --- .../vc/util/CachingDocumentLoader.java | 1 + .../src/main/resources/contexts/ob-v2p0.json | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 inspector-vc/src/main/resources/contexts/ob-v2p0.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index dee682d..42dc5cc 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -77,6 +77,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .put("https://w3id.org/security/suites/ed25519-2018/v1", Resources.getResource("contexts/suites-ed25519-2018.jsonld")) .put("https://w3id.org/security/suites/x25519-2019/v1", Resources.getResource("contexts/suites-x25519-2019.jsonld")) .put("https://w3id.org/security/suites/jws-2020/v1", Resources.getResource("contexts/suites-jws-2020.jsonld")) + .put("https://openbadgespec.org/v2/context.json", Resources.getResource("contexts/ob-v2p0.json")) .build(); diff --git a/inspector-vc/src/main/resources/contexts/ob-v2p0.json b/inspector-vc/src/main/resources/contexts/ob-v2p0.json new file mode 100644 index 0000000..84c3f37 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/ob-v2p0.json @@ -0,0 +1,91 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + + "author": { "@id": "schema:author", "@type": "@id" }, + "caption": { "@id": "schema:caption" }, + "claim": {"@id": "cred:claim", "@type": "@id"}, + "created": { "@id": "dc:created", "@type": "xsd:dateTime" }, + "creator": { "@id": "dc:creator", "@type": "@id" }, + "description": { "@id": "schema:description" }, + "email": { "@id": "schema:email" }, + "endorsement": {"@id": "cred:credential", "@type": "@id"}, + "expires": { "@id": "sec:expiration", "@type": "xsd:dateTime" }, + "genre": { "@id": "schema:genre" }, + "image": { "@id": "schema:image", "@type": "@id" }, + "name": { "@id": "schema:name" }, + "owner": {"@id": "sec:owner", "@type": "@id"}, + "publicKey": { "@id": "sec:publicKey", "@type": "@id" }, + "publicKeyPem": { "@id": "sec:publicKeyPem" }, + "related": { "@id": "dc:relation", "@type": "@id" }, + "startsWith": { "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" }, + "tags": { "@id": "schema:keywords" }, + "targetDescription": { "@id": "schema:targetDescription" }, + "targetFramework": { "@id": "schema:targetFramework" }, + "targetName": { "@id": "schema:targetName" }, + "targetUrl": { "@id": "schema:targetUrl" }, + "telephone": { "@id": "schema:telephone" }, + "url": { "@id": "schema:url", "@type": "@id" }, + "version": { "@id": "schema:version" }, + + "alignment": { "@id": "obi:alignment", "@type": "@id" }, + "allowedOrigins": { "@id": "obi:allowedOrigins" }, + "audience": { "@id": "obi:audience" }, + "badge": { "@id": "obi:badge", "@type": "@id" }, + "criteria": { "@id": "obi:criteria", "@type": "@id" }, + "endorsementComment": { "@id": "obi:endorsementComment" }, + "evidence": { "@id": "obi:evidence", "@type": "@id" }, + "hashed": { "@id": "obi:hashed", "@type": "xsd:boolean" }, + "identity": { "@id": "obi:identityHash" }, + "issuedOn": { "@id": "obi:issueDate", "@type": "xsd:dateTime" }, + "issuer": { "@id": "obi:issuer", "@type": "@id" }, + "narrative": { "@id": "obi:narrative" }, + "recipient": { "@id": "obi:recipient", "@type": "@id" }, + "revocationList": { "@id": "obi:revocationList", "@type": "@id" }, + "revocationReason": { "@id": "obi:revocationReason" }, + "revoked": { "@id": "obi:revoked", "@type": "xsd:boolean" }, + "revokedAssertions": { "@id": "obi:revoked" }, + "salt": { "@id": "obi:salt" }, + "targetCode": { "@id": "obi:targetCode" }, + "uid": { "@id": "obi:uid" }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { "@id": "obi:verify", "@type": "@id" }, + "verificationProperty": { "@id": "obi:verificationProperty" }, + "verify": "verification" + } +} From 2d6d93041fe14f17319d2dda8ebc555a8fc86daf Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 11:58:48 +0100 Subject: [PATCH 15/93] Allow redirection of url to local resources --- .../vc/util/CachingDocumentLoader.java | 35 +++++++++++++++++-- .../vc/util/CachingDocumentLoaderTests.java | 30 +++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 42dc5cc..27d7b36 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -2,8 +2,10 @@ package org.oneedtech.inspect.vc.util; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; +import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,10 +34,14 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { public CachingDocumentLoader() { + this(null); + } + + public CachingDocumentLoader(Map localDomains) { super(); setEnableHttp(true); setEnableHttps(true); - setDefaultHttpLoader(new HttpLoader()); + setDefaultHttpLoader(new HttpLoader(localDomains)); } @Override @@ -49,16 +55,41 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { } public class HttpLoader implements DocumentLoader { + final Map localDomains; + + public HttpLoader(Map localDomains) { + this.localDomains = localDomains; + } + @Override public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { - Tuple tpl = new Tuple<>(url.toASCIIString(), options); try { + // resolve url + URI resolvedUrl = resolve(url); + + Tuple tpl = new Tuple<>(resolvedUrl.toASCIIString(), options); + return documentCache.get(tpl); } catch (Exception e) { logger.error("documentCache not able to load {}", url); throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, e.getMessage()); } } + + /** + * Resolved given url. If the url is from one of local domain, a URL of the relative resource will be returned + * @throws URISyntaxException + */ + private URI resolve(URI url) throws URISyntaxException { + if (localDomains != null) { + URI base = url.resolve("/"); + if (localDomains.containsKey(base)) { + URL resource = Resources.getResource(localDomains.get(base) + "/" + base.relativize(url).toString()); + return resource.toURI(); + } + } + return url; + } } static final ImmutableMap bundled = ImmutableMap.builder() diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java index b56542f..198e6ee 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java @@ -1,27 +1,47 @@ package org.oneedtech.inspect.vc.util; import java.net.URI; +import java.net.URL; +import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; import com.apicatalog.jsonld.loader.DocumentLoader; import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.google.common.io.Resources; public class CachingDocumentLoaderTests { @Test void testStaticCachedDocumentBundled() { Assertions.assertDoesNotThrow(()->{ - DocumentLoader loader = new CachingDocumentLoader(); + DocumentLoader loader = new CachingDocumentLoader(); for(String id : CachingDocumentLoader.bundled.keySet()) { Document doc = loader.loadDocument(new URI(id), new DocumentLoaderOptions()); - Assertions.assertNotNull(doc); - } + Assertions.assertNotNull(doc); + } }); } - + + @Test + void testLocalDomainCachedDocument() { + Assertions.assertDoesNotThrow(()->{ + Map localDomains = Map.of(new URI("http://example.org/"), "ob20"); + DocumentLoader loader = new CachingDocumentLoader(localDomains); + URI uri = new URI("http://example.org/basic-assertion.json"); + Document doc = loader.loadDocument(uri, new DocumentLoaderOptions()); + Assertions.assertNotNull(doc); + + // assert the returned document is the same as the local resource + URL resource = Resources.getResource("ob20/basic-assertion.json"); + JsonDocument resourceDocument = JsonDocument.of(resource.openStream()); + Assertions.assertEquals(resourceDocument.getJsonContent().toString(), doc.getJsonContent().toString()); + }); + } + @Test void testStaticCachedDocumentKey() { Assertions.assertDoesNotThrow(()->{ @@ -30,5 +50,5 @@ public class CachingDocumentLoaderTests { Document doc = loader.loadDocument(uri, new DocumentLoaderOptions()); Assertions.assertNotNull(doc); }); - } + } } \ No newline at end of file From f97b01da82b5c1eb41361f5b025b1e477067855a Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 11:59:52 +0100 Subject: [PATCH 16/93] New probe for compacting JSONLD documents --- .../vc/probe/JsonLDCompactionProve.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java new file mode 100644 index 0000000..771fc82 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java @@ -0,0 +1,63 @@ +package org.oneedtech.inspect.vc.probe; + +import java.io.StringReader; +import java.net.URI; +import java.util.Map; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.apicatalog.jsonld.JsonLd; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.api.CompactionApi; +import com.apicatalog.jsonld.document.JsonDocument; + +import jakarta.json.JsonObject; + + +public class JsonLDCompactionProve extends Probe { + private final String context; + private final Map localDomains; + + public JsonLDCompactionProve(String context) { + this(context, null); + } + + public JsonLDCompactionProve(String context, Map localDomains) { + super(ID); + this.context = context; + this.localDomains = localDomains; + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + try { + // compact JSON + JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); + CompactionApi compactApi = JsonLd.compact(jsonDocument, context); + compactApi.options(new JsonLdOptions(new CachingDocumentLoader(localDomains))); + JsonObject compactedObject = compactApi.get(); + + // TODO: Handle mismatch between URL node source and declared ID. + if (compactedObject.get("id") != null && crd.getResource().getID() != null + && !compactedObject.get("id").toString().equals(crd.getResource().getID())) { + + // TODO: warning!! + // report_message( + // "Node fetched from source {} declared its id as {}".format( + // task_meta['node_id'], node_id), MESSAGE_LEVEL_WARNING, success=False + // ), + + } + return success(this, ctx); + + } catch (Exception e) { + return fatal("Error while parsing credential: " + e.getMessage(), ctx); + } + } + + public static final String ID = JsonLDCompactionProve.class.getSimpleName(); +} From af48747b571ac7caa2df4f2555a0824453c4f39a Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 12:01:05 +0100 Subject: [PATCH 17/93] Added a test OB20 inspector, with local redirections --- .../inspect/vc/util/TestOB20Inspector.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java new file mode 100644 index 0000000..e3c51cf --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java @@ -0,0 +1,57 @@ +package org.oneedtech.inspect.vc.util; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.util.spec.Specification; +import org.oneedtech.inspect.vc.Assertion; +import org.oneedtech.inspect.vc.OB20Inspector; +import org.oneedtech.inspect.vc.probe.JsonLDCompactionProve; + +/** + * OpenBadges 2.0 Test inspector. + * It's a subclass of main OB2.0 inspector, setting redirection of urls to local resources for testing + */ +public class TestOB20Inspector extends OB20Inspector { + protected final Map localDomains; + + protected TestOB20Inspector(TestBuilder builder) { + super(builder); + if (getBehavior(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION) == Boolean.TRUE) { + this.localDomains = builder.localDomains; + } else { + this.localDomains = Collections.emptyMap(); + } + } + + @Override + protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { + return new JsonLDCompactionProve(assertion.getContext().get(0), localDomains); + } + + public static class TestBuilder extends OB20Inspector.Builder { + final Map localDomains; + + public TestBuilder() { + super(); + // don't allow local redirections by default + super.behaviors.put(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION, true); + this.localDomains = new HashMap<>(); + } + + public TestBuilder add(URI localDomain, String resourcePath) { + localDomains.put(localDomain, resourcePath); + return this; + } + + @Override + public TestOB20Inspector build() { + set(Specification.OB20); + set(ResourceType.OPENBADGE); + return new TestOB20Inspector(this); + } + } +} From 6f7b1f710a212d397fac5d5db1905575c8261d87 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 12:01:35 +0100 Subject: [PATCH 18/93] Added parse and compaction probes --- .../oneedtech/inspect/vc/OB20Inspector.java | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) 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 997260e..c487768 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 @@ -7,9 +7,9 @@ import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.oneedtech.inspect.core.Inspector; +import org.oneedtech.inspect.core.probe.Outcome; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.RunContext.Key; @@ -23,6 +23,8 @@ import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; +import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,6 +39,25 @@ public class OB20Inspector extends Inspector { 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; + } + + @Override public Report run(Resource resource) { super.check(resource); @@ -65,13 +86,15 @@ public class OB20Inspector extends Inspector { try { //detect type (png, svg, json, jwt) and extract json data probeCount++; - // accumulator.add(new CredentialParseProbe().run(resource, ctx)); - // if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + accumulator.add(new CredentialParseProbe().run(resource, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); // //we expect the above to place a generated object in the context - // VerifiableCredential ob = ctx.getGeneratedObject(VerifiableCredential.ID); - + Assertion assertion = ctx.getGeneratedObject(Assertion.ID); + // let's compact and validate + accumulator.add(getCompactionProbe(assertion).run(assertion, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); } catch (Exception e) { @@ -81,7 +104,18 @@ public class OB20Inspector extends Inspector { return new Report(ctx, new ReportItems(accumulator), probeCount); } + protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { + return new JsonLDCompactionProve(assertion.getContext().get(0)); + } + public static class Builder extends Inspector.Builder { + + public Builder() { + super(); + // don't allow local redirections by default + super.behaviors.put(Behavior.ALLOW_LOCAL_REDIRECTION, false); + } + @SuppressWarnings("unchecked") @Override public OB20Inspector build() { @@ -90,4 +124,12 @@ public class OB20Inspector extends Inspector { return new OB20Inspector(this); } } + + public static class Behavior extends Inspector.Behavior { + /** + * Whether to support local redirection of uris + */ + public static final String ALLOW_LOCAL_REDIRECTION = "ALLOW_LOCAL_REDIRECTION"; + } + } From ca8050d79045ebf0691dc832c05e2d2a918deeb8 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 12:01:53 +0100 Subject: [PATCH 19/93] Added basic test, using test inspector --- .../org/oneedtech/inspect/vc/OB20Tests.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 34505fd..8e1df53 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 @@ -3,22 +3,29 @@ package org.oneedtech.inspect.vc; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.oneedtech.inspect.test.Assertions.assertValid; +import java.net.URI; +import java.net.URISyntaxException; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.oneedtech.inspect.core.Inspector.Behavior; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.test.PrintHelper; +import org.oneedtech.inspect.vc.util.TestOB20Inspector.TestBuilder; public class OB20Tests { private static OB20Inspector validator; private static boolean verbose = true; @BeforeAll - static void setup() { - validator = new OB20Inspector.Builder() - .set(Behavior.TEST_INCLUDE_SUCCESS, true) - .set(Behavior.VALIDATOR_FAIL_FAST, true) - .build(); + static void setup() throws URISyntaxException { + validator = new TestBuilder() + .add(new URI("https://www.example.org/"), "ob20/assets") + .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.TEST_INCLUDE_WARNINGS, true) + .set(Behavior.VALIDATOR_FAIL_FAST, true) + .set(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION, true) + .build(); } @Test @@ -29,5 +36,4 @@ public class OB20Tests { assertValid(report); }); } - } From 0ba347d8f0eaee1470cb80598d96c173ebdb9e7c Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 13:50:48 +0100 Subject: [PATCH 20/93] Added redirection sample --- .../test/java/org/oneedtech/inspect/vc/Samples.java | 5 +++-- .../test/resources/ob20/warning-with-redirection.json | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) 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 3fd3811..c972edd 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 @@ -43,9 +43,10 @@ public class Samples { public static final class OB20 { public static final class JSON { + // original: test_verify:test_verify_function public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); - // public final static Sample SIMPLE_BADGECLASS_JSON = new Sample("ob20/basic-badgeclass.json", true); - // public final static Sample SIMPLE_ISSUER_JSON = new Sample("ob20/basic-issuer.json", true); + // original: test_graph:test_verify_with_redirection + public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); } } } diff --git a/inspector-vc/src/test/resources/ob20/warning-with-redirection.json b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json index a350449..01c02ad 100644 --- a/inspector-vc/src/test/resources/ob20/warning-with-redirection.json +++ b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json @@ -1,17 +1,17 @@ -{ // test_graph:test_verify_with_redirection - "@context": "https://w3id.org/openbadges/v2", // openbadges_context.json +{ + "@context": "https://w3id.org/openbadges/v2", "type": "Assertion", - "id": "https://example.org/beths-robotics-badge.json", // robotics-badge.json + "id": "https://example.org/beths-robotics-badge.json", "recipient": { "type": "email", "hashed": true, "salt": "deadsea", "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" }, - "image": "https://example.org/beths-robot-badge.png", // image + "image": "https://example.org/beths-robot-badge.png", "evidence": "https://example.org/beths-robot-work.html", "issuedOn": "2016-12-31T23:59:59Z", - "badge": "http://example.org/altbadgeurl", // altbadgeurl.json + "badge": "http://example.org/altbadgeurl", "verification": { "type": "hosted" } From a629fc83723515474869852cbdf2bc35c7bc68f2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 13:51:04 +0100 Subject: [PATCH 21/93] Redirect warning in compaction --- .../vc/probe/JsonLDCompactionProve.java | 37 ++++++++----------- .../org/oneedtech/inspect/vc/OB20Tests.java | 27 +++++++++++++- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java index 771fc82..3cabea1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java @@ -34,29 +34,22 @@ public class JsonLDCompactionProve extends Probe { @Override public ReportItems run(Credential crd, RunContext ctx) throws Exception { - try { - // compact JSON - JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); - CompactionApi compactApi = JsonLd.compact(jsonDocument, context); - compactApi.options(new JsonLdOptions(new CachingDocumentLoader(localDomains))); - JsonObject compactedObject = compactApi.get(); + try { + // compact JSON + JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); + CompactionApi compactApi = JsonLd.compact(jsonDocument, context); + compactApi.options(new JsonLdOptions(new CachingDocumentLoader(localDomains))); + JsonObject compactedObject = compactApi.get(); - // TODO: Handle mismatch between URL node source and declared ID. - if (compactedObject.get("id") != null && crd.getResource().getID() != null - && !compactedObject.get("id").toString().equals(crd.getResource().getID())) { - - // TODO: warning!! - // report_message( - // "Node fetched from source {} declared its id as {}".format( - // task_meta['node_id'], node_id), MESSAGE_LEVEL_WARNING, success=False - // ), - - } - return success(this, ctx); - - } catch (Exception e) { - return fatal("Error while parsing credential: " + e.getMessage(), ctx); - } + // Handle mismatch between URL node source and declared ID. + if (compactedObject.get("id") != null && crd.getResource().getID() != null + && !compactedObject.get("id").toString().equals(crd.getResource().getID())) { + return warning("Node fetched from source " + crd.getResource().getID() + " declared its id as " + compactedObject.get("id").toString(), ctx); + } + return success(this, ctx); + } catch (Exception e) { + return fatal("Error while parsing credential: " + e.getMessage(), ctx); + } } public static final String ID = JsonLDCompactionProve.class.getSimpleName(); 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 8e1df53..bfbc0e9 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 @@ -2,11 +2,13 @@ package org.oneedtech.inspect.vc; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.oneedtech.inspect.test.Assertions.assertValid; +import static org.oneedtech.inspect.test.Assertions.assertWarning; import java.net.URI; import java.net.URISyntaxException; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.oneedtech.inspect.core.Inspector.Behavior; import org.oneedtech.inspect.core.report.Report; @@ -22,7 +24,7 @@ public class OB20Tests { validator = new TestBuilder() .add(new URI("https://www.example.org/"), "ob20/assets") .set(Behavior.TEST_INCLUDE_SUCCESS, true) - .set(Behavior.TEST_INCLUDE_WARNINGS, true) + .set(Behavior.TEST_INCLUDE_WARNINGS, false) .set(Behavior.VALIDATOR_FAIL_FAST, true) .set(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION, true) .build(); @@ -36,4 +38,27 @@ public class OB20Tests { assertValid(report); }); } + + @Nested + static class WarningTests { + @BeforeAll + static void setup() throws URISyntaxException { + validator = new TestBuilder() + .add(new URI("https://www.example.org/"), "ob20/assets") + .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.TEST_INCLUDE_WARNINGS, true) + .set(Behavior.VALIDATOR_FAIL_FAST, true) + .set(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION, true) + .build(); + } + + @Test + void testWarningRedirectionJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.WARNING_REDIRECTION_ASSERTION_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertWarning(report); + }); + } + } } From 24a2e02fe6b2c1e022d5f5cef8201fb342b12b9f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 14:37:48 +0100 Subject: [PATCH 22/93] Added simple png test --- .../java/org/oneedtech/inspect/vc/OB20Tests.java | 9 +++++++++ .../java/org/oneedtech/inspect/vc/Samples.java | 4 ++++ .../src/test/resources/ob20/simple-badge.png | Bin 0 -> 1302 bytes 3 files changed, 13 insertions(+) create mode 100644 inspector-vc/src/test/resources/ob20/simple-badge.png 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 bfbc0e9..6cb29be 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 @@ -39,6 +39,15 @@ public class OB20Tests { }); } + @Test + void testSimplePNGPlainValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.PNG.SIMPLE_JSON_PNG.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(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 c972edd..904369a 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 @@ -48,5 +48,9 @@ public class Samples { // original: test_graph:test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); } + + public static final class PNG { + public final static Sample SIMPLE_JSON_PNG = new Sample("ob20/simple-badge.png", true); + } } } diff --git a/inspector-vc/src/test/resources/ob20/simple-badge.png b/inspector-vc/src/test/resources/ob20/simple-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..ea3669368b6926975ae5804b4a6957f851655965 GIT binary patch literal 1302 zcmeAS@N?(olHy`uVBq!ia0vp^qChOn!3HGj{oJ}47#P21hD4O)7o_GTC8ngO76T<1 z7^;;VlJoOQQY%W7tdufJN(zdt^!3Y)GgI{Pi_-NGiuKEklysCzDhpD9>Ku!UQ;SM6 z^Yeg0nJGvbQ!5g43vyDydXrL1GKzJJ@{{sQGLwsS!FK9p6@xVur6y+Qjfr7;uiAJVoDyhk7 zrX~iKX~t4kCdmdyiK%Jk2F6ClrY6Sbrm5xz$!R7j7O5u2X=aIr zDMpqSmWGxlMoP6nU*smHgM0=H0UREI`l28&9q5wOvdom!ykugumFE{_>t&SWg8Y|R zTwI!(;-3e!&B(yeOxMsz*Vr(`$k@u%5{ROJD!@U7!!g*yvn;hJGc7YY5gaFoAkD}x zE&&E*ZEdq_=4N1Ivn6@EyDx`7I;Jg69Ts|2s1Lwnj^u$z_iuV#WBR< z^wNvo{=$J0$38xv^U^1}%5(je0Fg_Rqv~hPm$nly|?6xU2gib9TTJugiZVxKG`zA?)^!ggKZJ(_rF}L zq4P;$Pr!2JU1IFs$FGI$<)44!l=1wkzFO6;D=%KXJ5@KEV@6NzSk%0%YSvkUly{^nKv#Z;s|0<7*E2@{X$~0xKqBpne+*>&= zeN0+$e-|WvxcBq^i{r)1-JIr6_p6v2rgF{5aHq=6)2A&O`L$I~#^hg*7wTJr>^0drktKf z-!4@>7P2)#I#`AEXvCbLnI5Wv(>NHGhD{dYI>EDrah7zU#sdz{Yf4$K&szHFIfkq2 z2r*P#c@}E3r>$Xv0K;{@GotC;n-#Z+96$In(}{7x>%)igju|Xl`o*$g>i>%z0^6c{ zlCM0S@!qEH#i3uj;|&{pwr}TuQCT-#w;fm79>0;v@a^n^hpA<{vu_z27Z++rh{m?RY&mb9 zp?YLe`Wqbv>4qIwlPUszIz7*SnSGi?VD+CpuM8b*-+ku2D!k~laoLR=Ll!Sd&5Ks1 zH^X1HiSEyA?QLCr@WEA8*Hg2SJK&I<9~ T+I)2ys8sWG^>bP0l+XkK^->1{ literal 0 HcmV?d00001 From f17f8913bcddacefa32e1efb48da709d9359c878 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 17:06:55 +0100 Subject: [PATCH 23/93] Add simple badgeclass sample --- .../org/oneedtech/inspect/vc/Samples.java | 7 +- .../resources/ob20/assets/badgeclass1.json | 208 ++++++++++++++++++ .../test/resources/ob20/assets/issuer1.json | 206 +++++++++++++++++ 3 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass1.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer1.json 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 904369a..0492c38 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 @@ -43,13 +43,16 @@ public class Samples { public static final class OB20 { public static final class JSON { - // original: test_verify:test_verify_function + // original: test_verify: test_verify_function public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); - // original: test_graph:test_verify_with_redirection + // original: test_graph: test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); + // original: test_validation: test_can_input_badgeclass + public final static Sample SIMPLE_BADGECLASS = new Sample("ob20/assets/badgeclass1.json", true); } public static final class PNG { + // original: test_verify: test_verify_of_baked_image public final static Sample SIMPLE_JSON_PNG = new Sample("ob20/simple-badge.png", true); } } diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json new file mode 100644 index 0000000..55b5b83 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass1", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.org/criteria", + "issuer": "http://example.org/issuer1", + "image": "http://example.org/robotics-badge.png" +} \ 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 new file mode 100644 index 0000000..2df69c2 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer1.json @@ -0,0 +1,206 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer1", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org" +} \ No newline at end of file From 20ca615e468de08b60593918837a74b47014afee Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 17:07:10 +0100 Subject: [PATCH 24/93] Added simple badge class test --- .../test/java/org/oneedtech/inspect/vc/OB20Tests.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 6cb29be..84245cb 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 @@ -48,6 +48,17 @@ public class OB20Tests { }); } + @Test + void testSimpleBadgeClassJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + + @Nested static class WarningTests { @BeforeAll From b948a6fa2b5054f3be542e89c1ab4efd6f88d59d Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 17:07:23 +0100 Subject: [PATCH 25/93] Add JSON-LD Validation probe to OB20 --- .../inspect/vc/JsonLdGeneratedObject.java | 18 ++++++++++ .../oneedtech/inspect/vc/OB20Inspector.java | 30 ++++++++++++----- .../vc/probe/JsonLDCompactionProve.java | 5 +++ .../vc/probe/JsonLDValidationProbe.java | 33 +++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java new file mode 100644 index 0000000..5a529bd --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java @@ -0,0 +1,18 @@ +package org.oneedtech.inspect.vc; + +import org.oneedtech.inspect.core.probe.GeneratedObject; + +public class JsonLdGeneratedObject extends GeneratedObject { + private String json; + + public JsonLdGeneratedObject(String json) { + super(ID, GeneratedObject.Type.INTERNAL); + this.json = json; + } + + public String getJson() { + return json; + } + + public static final String ID = JsonLdGeneratedObject.class.getCanonicalName(); +} 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 c487768..dd1f6b7 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 @@ -25,6 +25,7 @@ import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.fasterxml.jackson.databind.ObjectMapper; @@ -84,19 +85,30 @@ public class OB20Inspector extends Inspector { int probeCount = 0; try { - //detect type (png, svg, json, jwt) and extract json data - probeCount++; - accumulator.add(new CredentialParseProbe().run(resource, ctx)); - if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + //detect type (png, svg, json, jwt) and extract json data + probeCount++; + accumulator.add(new CredentialParseProbe().run(resource, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - // //we expect the above to place a generated object in the context - Assertion assertion = ctx.getGeneratedObject(Assertion.ID); + // we expect the above to place a generated object in the context + Assertion assertion = ctx.getGeneratedObject(Assertion.ID); - // let's compact and validate - accumulator.add(getCompactionProbe(assertion).run(assertion, ctx)); - if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + // let's compact + accumulator.add(getCompactionProbe(assertion).run(assertion, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + // validate JSON LD + JsonLdGeneratedObject jsonLdGeneratedObject = ctx.getGeneratedObject(JsonLdGeneratedObject.ID); + accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + //canonical schema and inline schemata + // SchemaKey schema = assertion.getSchemaKey().orElseThrow(); + // for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { + // probeCount++; + // accumulator.add(probe.run(assertion.getJson(), ctx)); + // if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + // } } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java index 3cabea1..e849395 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java @@ -8,6 +8,7 @@ import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.JsonLd; @@ -39,13 +40,17 @@ public class JsonLDCompactionProve extends Probe { JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); CompactionApi compactApi = JsonLd.compact(jsonDocument, context); compactApi.options(new JsonLdOptions(new CachingDocumentLoader(localDomains))); + JsonObject compactedObject = compactApi.get(); + ctx.addGeneratedObject(new JsonLdGeneratedObject(compactedObject.toString())); // Handle mismatch between URL node source and declared ID. if (compactedObject.get("id") != null && crd.getResource().getID() != null && !compactedObject.get("id").toString().equals(crd.getResource().getID())) { + // TODO: a new fetch of the JSON document at id is required return warning("Node fetched from source " + crd.getResource().getID() + " declared its id as " + compactedObject.get("id").toString(), ctx); } + return success(this, ctx); } catch (Exception e) { return fatal("Error while parsing credential: " + e.getMessage(), ctx); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java new file mode 100644 index 0000000..23bc2cd --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java @@ -0,0 +1,33 @@ +package org.oneedtech.inspect.vc.probe; + +import java.io.StringReader; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.JsonLdGeneratedObject; + +import foundation.identity.jsonld.JsonLDObject; +import foundation.identity.jsonld.validation.Validation; + +public class JsonLDValidationProbe extends Probe { + private final JsonLdGeneratedObject jsonLdObject; + + public JsonLDValidationProbe(JsonLdGeneratedObject jsonLdObject) { + super(); + this.jsonLdObject = jsonLdObject; + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + JsonLDObject jsonLd = JsonLDObject.fromJson(new StringReader(jsonLdObject.getJson())); + try { + Validation.validate(jsonLd); + return success(this, ctx); + } catch (Exception e) { + return fatal("Error while validation JSON LD object: " + e.getMessage(), ctx); + } + } + +} From 543a2c5bdb74e114fa1c3344137e32ddad1c400f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 24 Nov 2022 17:09:21 +0100 Subject: [PATCH 26/93] Refactored out to a new package --- .../main/java/org/oneedtech/inspect/vc/OB20Inspector.java | 5 +++-- .../inspect/vc/{ => jsonld}/JsonLdGeneratedObject.java | 2 +- .../inspect/vc/{ => jsonld}/probe/JsonLDCompactionProve.java | 4 ++-- .../inspect/vc/{ => jsonld}/probe/JsonLDValidationProbe.java | 4 ++-- .../org/oneedtech/inspect/vc/util/TestOB20Inspector.java | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/{ => jsonld}/JsonLdGeneratedObject.java (91%) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/{ => jsonld}/probe/JsonLDCompactionProve.java (95%) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/{ => jsonld}/probe/JsonLDValidationProbe.java (90%) 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 dd1f6b7..ef28c04 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 @@ -21,11 +21,12 @@ 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.spec.Specification; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; -import org.oneedtech.inspect.vc.probe.JsonLDCompactionProve; -import org.oneedtech.inspect.vc.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java similarity index 91% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java index 5a529bd..9fdd752 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/JsonLdGeneratedObject.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc; +package org.oneedtech.inspect.vc.jsonld; import org.oneedtech.inspect.core.probe.GeneratedObject; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java similarity index 95% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index e849395..284f44b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.jsonld.probe; import java.io.StringReader; import java.net.URI; @@ -8,7 +8,7 @@ import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.Credential; -import org.oneedtech.inspect.vc.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.JsonLd; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDValidationProbe.java similarity index 90% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDValidationProbe.java index 23bc2cd..9a2b1a4 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/JsonLDValidationProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDValidationProbe.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.jsonld.probe; import java.io.StringReader; @@ -6,7 +6,7 @@ import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.Credential; -import org.oneedtech.inspect.vc.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import foundation.identity.jsonld.JsonLDObject; import foundation.identity.jsonld.validation.Validation; diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java index e3c51cf..1e80e40 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java @@ -9,7 +9,7 @@ import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.Assertion; import org.oneedtech.inspect.vc.OB20Inspector; -import org.oneedtech.inspect.vc.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; /** * OpenBadges 2.0 Test inspector. From 40b34cce13b3d9b75c26e04d19405bc49c4bdb13 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 08:28:02 +0100 Subject: [PATCH 27/93] Use JsonNodeUtils to manage array nodes --- .../java/org/oneedtech/inspect/vc/Assertion.java | 13 ++++++++----- .../inspect/vc/VerifiableCredential.java | 16 +++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 193a3f0..9bfab9a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.MoreObjects; @@ -25,7 +26,7 @@ public class Assertion extends Credential { super(ID, resource, data, jwt, schemas); JsonNode typeNode = jsonData.get("type"); - this.assertionType = Assertion.Type.valueOf(typeNode); + this.assertionType = Assertion.Type.valueOf(typeNode); } @Override @@ -69,13 +70,15 @@ public class Assertion extends Credential { public static Assertion.Type valueOf (JsonNode typeNode) { if(typeNode != null) { - String value = typeNode.asText(); - if(value.equals("Assertion")) { - return Assertion; + List values = JsonNodeUtil.asStringList(typeNode); + for (String value : values) { + if(value.equals("Assertion")) { + return Assertion; + } } } return Unknown; - } + } } public static final String ID = Assertion.class.getCanonicalName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index 60f48a0..b9443c6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -5,7 +5,6 @@ import static org.oneedtech.inspect.vc.VerifiableCredential.Type.ClrCredential; import static org.oneedtech.inspect.vc.VerifiableCredential.Type.EndorsementCredential; import static org.oneedtech.inspect.vc.VerifiableCredential.Type.VerifiablePresentation; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -14,9 +13,9 @@ import java.util.stream.Collectors; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; @@ -31,7 +30,7 @@ public class VerifiableCredential extends Credential { protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { super(ID, resource, data, jwt, schemas); - ArrayNode typeNode = (ArrayNode)jsonData.get("type"); + JsonNode typeNode = jsonData.get("type"); this.credentialType = VerifiableCredential.Type.valueOf(typeNode); } @@ -82,11 +81,10 @@ public class VerifiableCredential extends Credential { VerifiableCredential, //this is an underspecifier in our context Unknown; - public static VerifiableCredential.Type valueOf (ArrayNode typeArray) { - if(typeArray != null) { - Iterator iter = typeArray.iterator(); - while(iter.hasNext()) { - String value = iter.next().asText(); + public static VerifiableCredential.Type valueOf (JsonNode typeNode) { + if(typeNode != null) { + List values = JsonNodeUtil.asStringList(typeNode); + for (String value : values) { if(value.equals("AchievementCredential") || value.equals("OpenBadgeCredential")) { return AchievementCredential; } else if(value.equals("ClrCredential")) { @@ -99,7 +97,7 @@ public class VerifiableCredential extends Credential { } } return Unknown; - } + } } public enum ProofType { From a46fcbff9a70578454a817e84873f98b044c9bde Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 08:45:38 +0100 Subject: [PATCH 28/93] Refactor probes, extracted out to a generic class --- .../vc/probe/ContextPropertyProbe.java | 20 ++++----- .../inspect/vc/probe/PropertyProbe.java | 45 +++++++++++++++++++ .../inspect/vc/probe/TypePropertyProbe.java | 21 ++------- 3 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index d3fd35d..67774f2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -7,6 +7,7 @@ import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; @@ -24,32 +25,29 @@ import com.google.common.collect.ImmutableMap; * * @author mgylling */ -public class ContextPropertyProbe extends Probe { +public class ContextPropertyProbe extends PropertyProbe { private final VerifiableCredential.Type type; public ContextPropertyProbe(VerifiableCredential.Type type) { - super(ID); + super(ID, "@context"); this.type = checkNotNull(type); + setValidations(this::validate); } @Override - public ReportItems run(JsonNode root, RunContext ctx) throws Exception { - - ArrayNode contextNode = (ArrayNode) root.get("@context"); - if (contextNode == null) { - return notRun("No @context property", ctx); - } + protected ReportItems reportForNonExistentProperty(RunContext ctx) { + return notRun("No @context property", ctx); + } + public ReportItems validate(List nodeValues, RunContext ctx) { List expected = values.get(values.keySet() .stream() .filter(s->s.contains(type)) .findFirst() .orElseThrow(()-> new IllegalArgumentException(type.name() + " not recognized"))); - - List given = JsonNodeUtil.asStringList(contextNode); int pos = 0; for (String uri : expected) { - if ((given.size() < pos + 1) || !given.get(pos).equals(uri)) { + if ((nodeValues.size() < pos + 1) || !nodeValues.get(pos).equals(uri)) { return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx); } pos++; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java new file mode 100644 index 0000000..fb4a108 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -0,0 +1,45 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; + +public class PropertyProbe extends Probe { + private final String propertyName; + private BiFunction, RunContext, ReportItems> validations; + + public PropertyProbe(String id, String propertyName) { + super(id); + this.propertyName = propertyName; + this.validations = this::defaultValidation; + } + + public void setValidations(BiFunction, RunContext, ReportItems> validations) { + this.validations = validations; + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + JsonNode propertyNode = root.get(propertyName); + if (propertyNode == null) { + return reportForNonExistentProperty(ctx); + } + List values = JsonNodeUtil.asStringList(propertyNode); + return validations.apply(values, ctx); + } + protected ReportItems reportForNonExistentProperty(RunContext ctx) { + return fatal("No " + propertyName + " property", ctx); + } + + private ReportItems defaultValidation(List nodeValues, RunContext ctx) { + return notRun("Not additional validations run", ctx); + } +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index ed67b8b..c8e03f0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -4,38 +4,25 @@ import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.util.List; -import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.VerifiableCredential; -import org.oneedtech.inspect.vc.VerifiableCredential.Type; -import org.oneedtech.inspect.vc.util.JsonNodeUtil; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; /** * A Probe that verifies a credential's type property. * * @author mgylling */ -public class TypePropertyProbe extends Probe { +public class TypePropertyProbe extends PropertyProbe { private final VerifiableCredential.Type expected; public TypePropertyProbe(VerifiableCredential.Type expected) { - super(ID); + super(ID, "type"); this.expected = checkNotNull(expected); + this.setValidations(this::validate); } - @Override - public ReportItems run(JsonNode root, RunContext ctx) throws Exception { - - ArrayNode typeNode = (ArrayNode) root.get("type"); - if (typeNode == null) - return fatal("No type property", ctx); - - List values = JsonNodeUtil.asStringList(typeNode); - + public ReportItems validate(List values, RunContext ctx) { if (!values.contains("VerifiableCredential")) { return fatal("The type property does not contain the entry 'VerifiableCredential'", ctx); } From cc33dd40680fb28c44baf0dfb3f9e09d4afd9a42 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 12:48:11 +0100 Subject: [PATCH 29/93] Generic TypePropertyProbe --- .../org/oneedtech/inspect/vc/Assertion.java | 27 +++++++++-- .../org/oneedtech/inspect/vc/Credential.java | 5 ++ .../inspect/vc/VerifiableCredential.java | 33 +++++++++---- .../inspect/vc/probe/TypePropertyProbe.java | 47 ++++++++++++------- 4 files changed, 83 insertions(+), 29 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 9bfab9a..3ba06f8 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -1,5 +1,6 @@ package org.oneedtech.inspect.vc; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -64,9 +65,16 @@ public class Assertion extends Credential { } } - public enum Type { - Assertion, - Unknown; + public enum Type implements CredentialEnum { + Assertion(List.of("Assertion")), + BadgeClass(List.of("BadgeClass")), + Unknown(Collections.emptyList()); + + private final List allowedTypeValues; + + Type(List typeValues) { + this.allowedTypeValues = typeValues; + } public static Assertion.Type valueOf (JsonNode typeNode) { if(typeNode != null) { @@ -75,10 +83,23 @@ public class Assertion extends Credential { if(value.equals("Assertion")) { return Assertion; } + if(value.equals("BadgeClass")) { + return BadgeClass; + } } } return Unknown; } + + @Override + public List getRequiredTypeValues() { + return Collections.emptyList(); + } + + @Override + public List getAllowedTypeValues() { + return allowedTypeValues; + } } public static final String ID = Assertion.class.getCanonicalName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 3d74a87..e180ef5 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -74,6 +74,11 @@ public abstract class Credential extends GeneratedObject { public static final List RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT); public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY"; + public interface CredentialEnum { + List getRequiredTypeValues(); + List getAllowedTypeValues(); + } + public abstract static class Builder { private Resource resource; private JsonNode jsonData; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index b9443c6..c892844 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -5,6 +5,7 @@ import static org.oneedtech.inspect.vc.VerifiableCredential.Type.ClrCredential; import static org.oneedtech.inspect.vc.VerifiableCredential.Type.EndorsementCredential; import static org.oneedtech.inspect.vc.VerifiableCredential.Type.VerifiablePresentation; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -72,14 +73,20 @@ public class VerifiableCredential extends Credential { .build(); - public enum Type { - AchievementCredential, - OpenBadgeCredential, //treated as an alias of AchievementCredential - ClrCredential, - EndorsementCredential, - VerifiablePresentation, - VerifiableCredential, //this is an underspecifier in our context - Unknown; + public enum Type implements CredentialEnum { + AchievementCredential(Collections.emptyList()), + OpenBadgeCredential(List.of("OpenBadgeCredential", "AchievementCredential")), //treated as an alias of AchievementCredential + ClrCredential(List.of("ClrCredential")), + EndorsementCredential(List.of("EndorsementCredential")), + VerifiablePresentation(Collections.emptyList()), + VerifiableCredential(List.of("VerifiableCredential")), //this is an underspecifier in our context + Unknown(Collections.emptyList()); + + private final List allowedTypeValues; + + Type(List allowedTypeValues) { + this.allowedTypeValues = allowedTypeValues; + } public static VerifiableCredential.Type valueOf (JsonNode typeNode) { if(typeNode != null) { @@ -98,6 +105,16 @@ public class VerifiableCredential extends Credential { } return Unknown; } + + @Override + public List getRequiredTypeValues() { + return List.of("VerifiableCredential"); + } + + @Override + public List getAllowedTypeValues() { + return allowedTypeValues; + } } public enum ProofType { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index c8e03f0..2c25d7d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -3,10 +3,11 @@ package org.oneedtech.inspect.vc.probe; import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.util.List; +import java.util.stream.Collectors; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.Credential.CredentialEnum; /** * A Probe that verifies a credential's type property. @@ -14,38 +15,48 @@ import org.oneedtech.inspect.vc.VerifiableCredential; * @author mgylling */ public class TypePropertyProbe extends PropertyProbe { - private final VerifiableCredential.Type expected; + private final CredentialEnum expected; - public TypePropertyProbe(VerifiableCredential.Type expected) { + public TypePropertyProbe(CredentialEnum expected) { super(ID, "type"); this.expected = checkNotNull(expected); this.setValidations(this::validate); } public ReportItems validate(List values, RunContext ctx) { - if (!values.contains("VerifiableCredential")) { - return fatal("The type property does not contain the entry 'VerifiableCredential'", ctx); + List requiredTypeValues = expected.getRequiredTypeValues(); + if (!requiredTypeValues.isEmpty()) { + if (!requiredTypeValues.stream().allMatch(requiredValue -> values.contains(requiredValue))) { + return fatal(formatMessage(requiredTypeValues), ctx); + } } - if (expected == VerifiableCredential.Type.OpenBadgeCredential) { - if (!values.contains("OpenBadgeCredential") && !values.contains("AchievementCredential")) { - return fatal("The type property does not contain one of 'OpenBadgeCredential' or 'AchievementCredential'", ctx); - } - } else if (expected == VerifiableCredential.Type.ClrCredential) { - if (!values.contains("ClrCredential")) { - return fatal("The type property does not contain the entry 'ClrCredential'", ctx); - } - } else if (expected == VerifiableCredential.Type.EndorsementCredential) { - if (!values.contains("EndorsementCredential")) { - return fatal("The type property does not contain the entry 'EndorsementCredential'", ctx); - } - } else { + List allowedValues = expected.getAllowedTypeValues(); + if (allowedValues.isEmpty()) { // TODO implement throw new IllegalStateException(); } + if (!values.stream().anyMatch(v -> allowedValues.contains(v))) { + return fatal(formatMessage(values), ctx); + } return success(ctx); } + private String formatMessage(List values) { + StringBuffer buffer = new StringBuffer("The type property does not contain "); + if (values.size() > 1) { + buffer.append("one of"); + buffer.append(values.stream() + .map(value -> "'" + value + "'") + .collect(Collectors.joining(" or ")) + ); + + } else { + buffer.append("the entry '" + values.get(0) + "'"); + } + return buffer.toString(); + } + public static final String ID = TypePropertyProbe.class.getSimpleName(); } From bf3f93e59e34abb266cb226f39e8b311f95b7cb5 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 12:54:17 +0100 Subject: [PATCH 30/93] credentialType now is of the interface type --- .../java/org/oneedtech/inspect/vc/Assertion.java | 13 +++++-------- .../java/org/oneedtech/inspect/vc/Credential.java | 15 +++++++++------ .../inspect/vc/VerifiableCredential.java | 15 +++++---------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 3ba06f8..cfa452e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -23,7 +23,7 @@ public class Assertion extends Credential { final Assertion.Type assertionType; - protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { + protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { super(ID, resource, data, jwt, schemas); JsonNode typeNode = jsonData.get("type"); @@ -31,8 +31,8 @@ public class Assertion extends Credential { } @Override - public String getCredentialType() { - return assertionType.toString(); + public CredentialEnum getCredentialType() { + return assertionType; } @Override @@ -48,7 +48,7 @@ public class Assertion extends Credential { .toString(); } - private static final Map schemas = new ImmutableMap.Builder() + private static final Map schemas = new ImmutableMap.Builder() .put(Type.Assertion, Catalog.OB_21_ASSERTION_JSON) .build(); @@ -58,10 +58,7 @@ public class Assertion extends Credential { public Assertion build() { // transform key of schemas map to string because the type of the key in the base map is generic // and our specific key is an Enum - return new Assertion(getResource(), getJsonData(), getJwt(), - schemas.entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey().toString(), - entry -> entry.getValue()))); + return new Assertion(getResource(), getJsonData(), getJwt(), schemas); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index e180ef5..7600c6d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -1,7 +1,11 @@ package org.oneedtech.inspect.vc; -import static org.oneedtech.inspect.util.code.Defensives.*; -import static org.oneedtech.inspect.util.resource.ResourceType.*; +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; +import static org.oneedtech.inspect.util.resource.ResourceType.JSON; +import static org.oneedtech.inspect.util.resource.ResourceType.JWT; +import static org.oneedtech.inspect.util.resource.ResourceType.PNG; +import static org.oneedtech.inspect.util.resource.ResourceType.SVG; import java.util.List; import java.util.Map; @@ -13,7 +17,6 @@ import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.base.MoreObjects; @@ -26,9 +29,9 @@ public abstract class Credential extends GeneratedObject { final Resource resource; final JsonNode jsonData; final String jwt; - final Map schemas; + final Map schemas; - protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { + protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { super(id, GeneratedObject.Type.INTERNAL); this.resource = checkNotNull(resource); this.jsonData = checkNotNull(data); @@ -57,7 +60,7 @@ public abstract class Credential extends GeneratedObject { return Optional.ofNullable(schemas.get(getCredentialType())); } - public abstract String getCredentialType(); + public abstract CredentialEnum getCredentialType(); public abstract List getContext(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index c892844..ba1d045 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -28,15 +28,15 @@ import com.google.common.collect.ImmutableMap; public class VerifiableCredential extends Credential { final VerifiableCredential.Type credentialType; - protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { + protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { super(ID, resource, data, jwt, schemas); JsonNode typeNode = jsonData.get("type"); this.credentialType = VerifiableCredential.Type.valueOf(typeNode); } - public String getCredentialType() { - return credentialType.toString(); + public CredentialEnum getCredentialType() { + return credentialType; } @Override @@ -52,7 +52,7 @@ public class VerifiableCredential extends Credential { return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; } - private static final Map schemas = new ImmutableMap.Builder() + private static final Map schemas = new ImmutableMap.Builder() .put(AchievementCredential, Catalog.OB_30_ACHIEVEMENTCREDENTIAL_JSON) .put(ClrCredential, Catalog.CLR_20_CLRCREDENTIAL_JSON) .put(VerifiablePresentation, Catalog.CLR_20_CLRCREDENTIAL_JSON) @@ -133,12 +133,7 @@ public class VerifiableCredential extends Credential { public static class Builder extends Credential.Builder { @Override public VerifiableCredential build() { - // transform key of schemas map to string because the type of the key in the base map is generic - // and our specific key is an Enum - return new VerifiableCredential(getResource(), getJsonData(), getJwt(), - schemas.entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey().toString(), - entry -> entry.getValue()))); + return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas); } } From 82ab70eaf96b879795a2525b742a3d4ef55fd780 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 12:54:37 +0100 Subject: [PATCH 31/93] Added TypePropertyProbe --- .../java/org/oneedtech/inspect/vc/OB20Inspector.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 ef28c04..7599944 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 @@ -21,14 +21,18 @@ 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.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.JsonLDCompactionProve; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -103,6 +107,14 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + //context and type properties + CredentialEnum type = assertion.getCredentialType(); + for(Probe probe : List.of(/*new ContextPropertyProbe(type), */new TypePropertyProbe(type))) { + probeCount++; + accumulator.add(probe.run(assertion.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + //canonical schema and inline schemata // SchemaKey schema = assertion.getSchemaKey().orElseThrow(); // for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { From f4f44f61fff968950b37f232ab47a57252d93bd8 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 13:55:08 +0100 Subject: [PATCH 32/93] Return empty list when node is an object --- .../inspect/vc/util/JsonNodeUtil.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java index aaf6083..630e43a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java @@ -17,11 +17,11 @@ import com.fasterxml.jackson.databind.node.ArrayNode; public class JsonNodeUtil { public static List asNodeList(JsonNode root, String jsonPath, JsonPathEvaluator evaluator) { - List list = new ArrayList<>(); - ArrayNode array = evaluator.eval(jsonPath, root); + List list = new ArrayList<>(); + ArrayNode array = evaluator.eval(jsonPath, root); for(JsonNode node : array) { if(!(node instanceof ArrayNode)) { - list.add(node); + list.add(node); } else { ArrayNode values = (ArrayNode) node; for(JsonNode value : values) { @@ -31,28 +31,31 @@ public class JsonNodeUtil { } return list; } - + public static List asStringList(JsonNode node) { if(!(node instanceof ArrayNode)) { + if (node.isObject()) { + return List.of(); + } return List.of(node.asText()); } else { - ArrayNode arrayNode = (ArrayNode)node; + ArrayNode arrayNode = (ArrayNode)node; return StreamSupport .stream(arrayNode.spliterator(), false) .map(n->n.asText().strip()) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } } - + public static List asNodeList(JsonNode node) { if(node == null) return null; if(!(node instanceof ArrayNode)) { return List.of(node); } else { - ArrayNode arrayNode = (ArrayNode)node; + ArrayNode arrayNode = (ArrayNode)node; return StreamSupport .stream(arrayNode.spliterator(), false) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } } } From c88f7765528fef657bd13c28891c60443e423975 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 13:55:55 +0100 Subject: [PATCH 33/93] Removed unused imports --- .../main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java index fb4a108..9c0ba22 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -2,8 +2,6 @@ package org.oneedtech.inspect.vc.probe; import java.util.List; import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; From 256028142045dbb77205f15f1ad55b26069635e0 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 13:56:19 +0100 Subject: [PATCH 34/93] Moved getContext to CredentialEnum --- .../org/oneedtech/inspect/vc/Assertion.java | 10 +++++----- .../org/oneedtech/inspect/vc/Credential.java | 3 +-- .../inspect/vc/VerifiableCredential.java | 20 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index cfa452e..5650e2f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -35,11 +35,6 @@ public class Assertion extends Credential { return assertionType; } - @Override - public List getContext() { - return List.of("https://w3id.org/openbadges/v2"); - } - @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -97,6 +92,11 @@ public class Assertion extends Credential { public List getAllowedTypeValues() { return allowedTypeValues; } + + @Override + public List getContextUris() { + return List.of("https://w3id.org/openbadges/v2") ; + } } public static final String ID = Assertion.class.getCanonicalName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 7600c6d..83c19b8 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -62,8 +62,6 @@ public abstract class Credential extends GeneratedObject { public abstract CredentialEnum getCredentialType(); - public abstract List getContext(); - @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -80,6 +78,7 @@ public abstract class Credential extends GeneratedObject { public interface CredentialEnum { List getRequiredTypeValues(); List getAllowedTypeValues(); + List getContextUris(); } public abstract static class Builder { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index ba1d045..e062432 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -39,15 +39,6 @@ public class VerifiableCredential extends Credential { return credentialType; } - @Override - public List getContext() { - return values.get(values.keySet() - .stream() - .filter(s->s.contains(credentialType)) - .findFirst() - .orElseThrow(()-> new IllegalArgumentException(credentialType.name() + " not recognized"))); - } - public ProofType getProofType() { return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; } @@ -59,7 +50,7 @@ public class VerifiableCredential extends Credential { .put(EndorsementCredential, Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON) .build(); - private static final Map, List> values = new ImmutableMap.Builder, List>() + private static final Map, List> contextMap = new ImmutableMap.Builder, List>() .put(Set.of(Type.OpenBadgeCredential, AchievementCredential, EndorsementCredential), List.of("https://www.w3.org/2018/credentials/v1", //"https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy @@ -115,6 +106,15 @@ public class VerifiableCredential extends Credential { public List getAllowedTypeValues() { return allowedTypeValues; } + + @Override + public List getContextUris() { + return contextMap.get(contextMap.keySet() + .stream() + .filter(s->s.contains(this)) + .findFirst() + .orElseThrow(()-> new IllegalArgumentException(this.name() + " not recognized"))); + } } public enum ProofType { From c72d1de7f4dc714aa50274d6d87ddab68b3760ce Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 13:56:39 +0100 Subject: [PATCH 35/93] Generic ContextPropertyProbe --- .../oneedtech/inspect/vc/OB20Inspector.java | 5 +- .../vc/probe/ContextPropertyProbe.java | 52 +++++-------------- .../inspect/vc/util/TestOB20Inspector.java | 2 +- 3 files changed, 17 insertions(+), 42 deletions(-) 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 7599944..c2a4f38 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 @@ -28,6 +28,7 @@ import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; +import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; @@ -109,7 +110,7 @@ public class OB20Inspector extends Inspector { //context and type properties CredentialEnum type = assertion.getCredentialType(); - for(Probe probe : List.of(/*new ContextPropertyProbe(type), */new TypePropertyProbe(type))) { + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { probeCount++; accumulator.add(probe.run(assertion.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); @@ -130,7 +131,7 @@ public class OB20Inspector extends Inspector { } protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { - return new JsonLDCompactionProve(assertion.getContext().get(0)); + return new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0)); } public static class Builder extends Inspector.Builder { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index 67774f2..1580221 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -1,24 +1,12 @@ package org.oneedtech.inspect.vc.probe; -import static org.oneedtech.inspect.vc.VerifiableCredential.Type.*; - import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.VerifiableCredential; - -import org.oneedtech.inspect.vc.util.JsonNodeUtil; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.google.common.collect.ImmutableMap; +import org.oneedtech.inspect.vc.Credential.CredentialEnum; /** * A Probe that verifies a credential's context property. @@ -26,9 +14,9 @@ import com.google.common.collect.ImmutableMap; * @author mgylling */ public class ContextPropertyProbe extends PropertyProbe { - private final VerifiableCredential.Type type; + private final CredentialEnum type; - public ContextPropertyProbe(VerifiableCredential.Type type) { + public ContextPropertyProbe(CredentialEnum type) { super(ID, "@context"); this.type = checkNotNull(type); setValidations(this::validate); @@ -40,35 +28,21 @@ public class ContextPropertyProbe extends PropertyProbe { } public ReportItems validate(List nodeValues, RunContext ctx) { - List expected = values.get(values.keySet() - .stream() - .filter(s->s.contains(type)) - .findFirst() - .orElseThrow(()-> new IllegalArgumentException(type.name() + " not recognized"))); - int pos = 0; - for (String uri : expected) { - if ((nodeValues.size() < pos + 1) || !nodeValues.get(pos).equals(uri)) { - return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx); + if (!nodeValues.isEmpty()) { // empty context uri node: inline context + List contextUris = type.getContextUris(); + checkNotNull(contextUris); + + int pos = 0; + for (String uri : contextUris) { + if ((nodeValues.size() < pos + 1) || !nodeValues.get(pos).equals(uri)) { + return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx); + } + pos++; } - pos++; } return success(ctx); } - private final static Map, List> values = new ImmutableMap.Builder, List>() - .put(Set.of(OpenBadgeCredential, AchievementCredential, EndorsementCredential), - List.of("https://www.w3.org/2018/credentials/v1", - //"https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy - "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) - .put(Set.of(ClrCredential), - List.of("https://www.w3.org/2018/credentials/v1", -// "https://dc.imsglobal.org/draft/clr/v2p0/context", //dev legacy -// "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy - "https://purl.imsglobal.org/spec/clr/v2p0/context.json", - "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) - - .build(); - public static final String ID = ContextPropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java index 1e80e40..323ec88 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java @@ -29,7 +29,7 @@ public class TestOB20Inspector extends OB20Inspector { @Override protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { - return new JsonLDCompactionProve(assertion.getContext().get(0), localDomains); + return new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0), localDomains); } public static class TestBuilder extends OB20Inspector.Builder { From 5cb232ea5b575303635ea47667a467adae583708 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 14:09:17 +0100 Subject: [PATCH 36/93] Added invalid context test --- .../oneedtech/inspect/vc/OB20Inspector.java | 16 ++++++++-------- .../org/oneedtech/inspect/vc/OB20Tests.java | 15 +++++++++++++++ .../java/org/oneedtech/inspect/vc/Samples.java | 1 + .../ob20/basic-assertion-invalid-context.json | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-invalid-context.json 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 c2a4f38..0f6c636 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 @@ -99,6 +99,14 @@ public class OB20Inspector extends Inspector { // we expect the above to place a generated object in the context Assertion assertion = ctx.getGeneratedObject(Assertion.ID); + //context and type properties + CredentialEnum type = assertion.getCredentialType(); + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { + probeCount++; + accumulator.add(probe.run(assertion.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + // let's compact accumulator.add(getCompactionProbe(assertion).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); @@ -108,14 +116,6 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - //context and type properties - CredentialEnum type = assertion.getCredentialType(); - for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { - probeCount++; - accumulator.add(probe.run(assertion.getJson(), ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - } - //canonical schema and inline schemata // SchemaKey schema = assertion.getSchemaKey().orElseThrow(); // for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { 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 84245cb..4325443 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 @@ -1,6 +1,9 @@ package org.oneedtech.inspect.vc; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.oneedtech.inspect.test.Assertions.assertFatalCount; +import static org.oneedtech.inspect.test.Assertions.assertHasProbeID; +import static org.oneedtech.inspect.test.Assertions.assertInvalid; import static org.oneedtech.inspect.test.Assertions.assertValid; import static org.oneedtech.inspect.test.Assertions.assertWarning; @@ -13,6 +16,7 @@ import org.junit.jupiter.api.Test; import org.oneedtech.inspect.core.Inspector.Behavior; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.test.PrintHelper; +import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.util.TestOB20Inspector.TestBuilder; public class OB20Tests { @@ -39,6 +43,17 @@ public class OB20Tests { }); } + @Test + void testSimpleJsonInvalidContext() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_INVALID_CONTEXT_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertFatalCount(report, 1); + assertHasProbeID(report, ContextPropertyProbe.ID, true); + }); + } + @Test void testSimplePNGPlainValid() { assertDoesNotThrow(()->{ 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 0492c38..c628249 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 @@ -45,6 +45,7 @@ public class Samples { public static final class JSON { // original: test_verify: test_verify_function public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); + public final static Sample SIMPLE_ASSERTION_INVALID_CONTEXT_JSON = new Sample("ob20/basic-assertion-invalid-context.json", true); // original: test_graph: test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); // original: test_validation: test_can_input_badgeclass diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-context.json b/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-context.json new file mode 100644 index 0000000..b1de0b9 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-context.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/invalid", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From 521cce5dd41f6e188e2adbc4a12e30acdca98a87 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 25 Nov 2022 14:27:44 +0100 Subject: [PATCH 37/93] Added invalid type test --- .../inspect/vc/probe/TypePropertyProbe.java | 3 +- .../org/oneedtech/inspect/vc/OB20Tests.java | 34 +++++++++++++------ .../org/oneedtech/inspect/vc/Samples.java | 1 + .../ob20/basic-assertion-invalid-type.json | 18 ++++++++++ 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-invalid-type.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index 2c25d7d..7896617 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -33,8 +33,7 @@ public class TypePropertyProbe extends PropertyProbe { List allowedValues = expected.getAllowedTypeValues(); if (allowedValues.isEmpty()) { - // TODO implement - throw new IllegalStateException(); + return fatal("The type property is invalid", ctx); } if (!values.stream().anyMatch(v -> allowedValues.contains(v))) { return fatal(formatMessage(values), ctx); 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 4325443..05f678f 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 @@ -17,6 +17,7 @@ import org.oneedtech.inspect.core.Inspector.Behavior; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.test.PrintHelper; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; +import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.util.TestOB20Inspector.TestBuilder; public class OB20Tests { @@ -43,17 +44,6 @@ public class OB20Tests { }); } - @Test - void testSimpleJsonInvalidContext() { - assertDoesNotThrow(()->{ - Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_INVALID_CONTEXT_JSON.asFileResource()); - if(verbose) PrintHelper.print(report, true); - assertInvalid(report); - assertFatalCount(report, 1); - assertHasProbeID(report, ContextPropertyProbe.ID, true); - }); - } - @Test void testSimplePNGPlainValid() { assertDoesNotThrow(()->{ @@ -72,6 +62,28 @@ public class OB20Tests { }); } + @Test + void testSimpleJsonInvalidContext() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_INVALID_CONTEXT_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertFatalCount(report, 1); + assertHasProbeID(report, ContextPropertyProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidType() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_INVALID_TYPE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertFatalCount(report, 1); + assertHasProbeID(report, TypePropertyProbe.ID, true); + }); + } + @Nested 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 c628249..6f3f861 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 @@ -46,6 +46,7 @@ public class Samples { // original: test_verify: test_verify_function public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); public final static Sample SIMPLE_ASSERTION_INVALID_CONTEXT_JSON = new Sample("ob20/basic-assertion-invalid-context.json", true); + public final static Sample SIMPLE_ASSERTION_INVALID_TYPE_JSON = new Sample("ob20/basic-assertion-invalid-type.json", true); // original: test_graph: test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); // original: test_validation: test_can_input_badgeclass diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-type.json b/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-type.json new file mode 100644 index 0000000..527cd67 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-invalid-type.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "OtherAssertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From 7cd91030016a1ed61d9ac6adc36da2e45299cf2f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 28 Nov 2022 14:17:10 +0100 Subject: [PATCH 38/93] Two type of generic property probes, depending of the type of validations --- .../vc/probe/ContextPropertyProbe.java | 4 +-- .../inspect/vc/probe/PropertyProbe.java | 10 +++--- .../vc/probe/StringValuePropertyProbe.java | 35 +++++++++++++++++++ .../inspect/vc/probe/TypePropertyProbe.java | 4 +-- 4 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index 1580221..8a21518 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -13,13 +13,13 @@ import org.oneedtech.inspect.vc.Credential.CredentialEnum; * * @author mgylling */ -public class ContextPropertyProbe extends PropertyProbe { +public class ContextPropertyProbe extends StringValuePropertyProbe { private final CredentialEnum type; public ContextPropertyProbe(CredentialEnum type) { super(ID, "@context"); this.type = checkNotNull(type); - setValidations(this::validate); + setValueValidations(this::validate); } @Override diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java index 9c0ba22..ca64f9e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.JsonNode; public class PropertyProbe extends Probe { private final String propertyName; - private BiFunction, RunContext, ReportItems> validations; + private BiFunction validations; public PropertyProbe(String id, String propertyName) { super(id); @@ -20,7 +20,7 @@ public class PropertyProbe extends Probe { this.validations = this::defaultValidation; } - public void setValidations(BiFunction, RunContext, ReportItems> validations) { + public void setValidations(BiFunction validations) { this.validations = validations; } @@ -30,14 +30,16 @@ public class PropertyProbe extends Probe { if (propertyNode == null) { return reportForNonExistentProperty(ctx); } + List values = JsonNodeUtil.asStringList(propertyNode); - return validations.apply(values, ctx); + return validations.apply(propertyNode, ctx); } + protected ReportItems reportForNonExistentProperty(RunContext ctx) { return fatal("No " + propertyName + " property", ctx); } - private ReportItems defaultValidation(List nodeValues, RunContext ctx) { + private ReportItems defaultValidation(JsonNode node, RunContext ctx) { return notRun("Not additional validations run", ctx); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java new file mode 100644 index 0000000..5c792d0 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java @@ -0,0 +1,35 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.function.BiFunction; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; + +public class StringValuePropertyProbe extends PropertyProbe { + private BiFunction, RunContext, ReportItems> valueValidations; + + public StringValuePropertyProbe(String id, String propertyName) { + super(id, propertyName); + this.valueValidations = this::defaultValidation; + super.setValidations(this::nodeValidation); + } + + public void setValueValidations(BiFunction, RunContext, ReportItems> validations) { + this.valueValidations = validations; + } + + private ReportItems nodeValidation(JsonNode node, RunContext ctx) { + List values = JsonNodeUtil.asStringList(node); + return valueValidations.apply(values, ctx); +} + + private ReportItems defaultValidation(List nodeValues, RunContext ctx) { + return notRun("Not additional validations run", ctx); + } + + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index 7896617..972427c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -14,13 +14,13 @@ import org.oneedtech.inspect.vc.Credential.CredentialEnum; * * @author mgylling */ -public class TypePropertyProbe extends PropertyProbe { +public class TypePropertyProbe extends StringValuePropertyProbe { private final CredentialEnum expected; public TypePropertyProbe(CredentialEnum expected) { super(ID, "type"); this.expected = checkNotNull(expected); - this.setValidations(this::validate); + this.setValueValidations(this::validate); } public ReportItems validate(List values, RunContext ctx) { From 4267423273b83813ed54ffc5791daf3036252187 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 28 Nov 2022 14:22:15 +0100 Subject: [PATCH 39/93] Added validations by type --- .../org/oneedtech/inspect/vc/Assertion.java | 190 +++++++++++++++++- .../org/oneedtech/inspect/vc/Validation.java | 142 +++++++++++++ 2 files changed, 326 insertions(+), 6 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 5650e2f..2c50b67 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -1,9 +1,10 @@ package org.oneedtech.inspect.vc; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; @@ -43,6 +44,10 @@ public class Assertion extends Credential { .toString(); } + public List getValidations() { + return validationMap.get(assertionType); + } + private static final Map schemas = new ImmutableMap.Builder() .put(Type.Assertion, Catalog.OB_21_ASSERTION_JSON) .build(); @@ -60,8 +65,27 @@ public class Assertion extends Credential { public enum Type implements CredentialEnum { Assertion(List.of("Assertion")), BadgeClass(List.of("BadgeClass")), + + AlignmentObject(List.of("AlignmentObject")), + Criteria(List.of("Criteria")), + CryptographicKey(List.of("CryptographicKey")), + Endorsement(List.of("Endorsement")), + EndorsementClaim(List.of("EndorsementClaim")), + Evidence(List.of("Evidence")), + ExpectedRecipientProfile(List.of("ExpectedRecipientProfile")), + Extension(List.of("Extension")), + IdentityObject(List.of("IdentityObject")), + Image(List.of("Image")), + Issuer(List.of("Issuer")), + Profile(List.of("Profile")), + RevocationList(List.of("RevocationList")), + VerificationObject(List.of("VerificationObject")), + VerificationObjectAssertion(List.of("VerificationObjectAssertion")), + VerificationObjectIssuer(List.of("VerificationObjectIssuer")), Unknown(Collections.emptyList()); + public static List primaryObjects = List.of(Assertion, BadgeClass, Issuer, Profile, Endorsement); + private final List allowedTypeValues; Type(List typeValues) { @@ -72,11 +96,12 @@ public class Assertion extends Credential { if(typeNode != null) { List values = JsonNodeUtil.asStringList(typeNode); for (String value : values) { - if(value.equals("Assertion")) { - return Assertion; - } - if(value.equals("BadgeClass")) { - return BadgeClass; + Type found = Arrays.stream(Type.values()) + .filter(type -> value.equals(type.toString())) + .findFirst() + .orElse(Unknown); + if (found != Unknown) { + return found; } } } @@ -99,5 +124,158 @@ public class Assertion extends Credential { } } + public enum ValueType { + BOOLEAN, + COMPACT_IRI, + DATA_URI, + DATA_URI_OR_URL, + DATETIME, + EMAIL, + ID, + IDENTITY_HASH, + IRI, + LANGUAGE, + MARKDOWN_TEXT, + RDF_TYPE, + TELEPHONE, + TEXT, + TEXT_OR_NUMBER, + URL, + URL_AUTHORITY; + + public static List primitives = List.of(BOOLEAN, DATA_URI_OR_URL, DATETIME, ID, IDENTITY_HASH, IRI, LANGUAGE, MARKDOWN_TEXT, + TELEPHONE, TEXT, TEXT_OR_NUMBER, URL, URL_AUTHORITY); + } + + public static Map> validationMap = new ImmutableMap.Builder>() + .put(Type.Assertion, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Assertion)).build(), + new Validation.Builder().name("recipient").type(ValueType.ID).expectedType(Type.IdentityObject).required(true).build(), + new Validation.Builder().name("badge").type(ValueType.ID).prerequisite("ASN_FLATTEN_BC").expectedType(Type.BadgeClass).fetch(true).required(true).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedTypes(List.of(Type.VerificationObjectAssertion)).required(true).build(), + new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), + new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), + new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), + new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build() + )) + .put(Type.BadgeClass, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.BadgeClass)).build(), + new Validation.Builder().name("issuer").type(ValueType.ID).prerequisite("BC_FLATTEN_ISS").expectedType(Type.Profile).fetch(true).required(true).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(true).build(), + new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("criteria").type(ValueType.ID).expectedType(Type.Criteria).fetch(false).required(true).allowRemoteUrl(true).build(), + new Validation.Builder().name("alignment").type(ValueType.ID).expectedType(Type.AlignmentObject).many(true).fetch(false).required(false).build(), + new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).required(false).build() + )) + .put(Type.AlignmentObject, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.AlignmentObject).build(), + new Validation.Builder().name("targetName").type(ValueType.TEXT).required(true).build(), + new Validation.Builder().name("targetUrl").type(ValueType.URL).required(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("targetFramework").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("targetCode").type(ValueType.TEXT).required(false).build() + )) + .put(Type.Criteria, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.Criteria).build(), + new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build() + )) + .put(Type.CryptographicKey, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.CryptographicKey).build(), + new Validation.Builder().name("owner").type(ValueType.IRI).required(false).fetch(true).build(), + new Validation.Builder().name("publicKeyPem").type(ValueType.TEXT).required(false).build() + )) + .put(Type.Endorsement, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Endorsement)).build(), + new Validation.Builder().name("claim").type(ValueType.ID).required(true).allowRemoteUrl(false).fetch(false).allowDataUri(false).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).fullValidate(false).build(), + new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), + new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).build(), + new Validation.Builder().name("verification").build() + )) + .put(Type.EndorsementClaim, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("endorsementComment").type(ValueType.MARKDOWN_TEXT).required(false).build() + )) + .put(Type.Evidence, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.Evidence).build(), + new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("genre").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("audience").type(ValueType.TEXT).required(false).build() + )) + .put(Type.ExpectedRecipientProfile, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).defaultType(Type.Profile).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("image").type(ValueType.ID).required(false).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("url").type(ValueType.URL).required(false).many(true).build(), + new Validation.Builder().name("email").type(ValueType.EMAIL).required(false).many(true).build(), + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).many(true).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).many(true).expectedType(Type.CryptographicKey).fetch(false).required(false).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + )) + .put(Type.Extension, List.of()) + .put(Type.IdentityObject, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(false).mustContainOne(List.of("id", "email", "url", "telephone")).build(), + new Validation.Builder().name("identity").type(ValueType.IDENTITY_HASH).required(true).build(), + new Validation.Builder().name("hashed").type(ValueType.BOOLEAN).required(true).build(), + new Validation.Builder().name("salt").type(ValueType.TEXT).required(false).build() + )) + .put(Type.Image, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType("schema:ImageObject").build(), + new Validation.Builder().name("id").type(ValueType.DATA_URI_OR_URL).required(true).build(), + new Validation.Builder().name("caption").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("author").type(ValueType.IRI).required(false).build() + )) + .put(Type.Issuer, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("url").type(ValueType.URL).required(true).build(), + new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + )) + .put(Type.Profile, List.of( + new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), + new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("url").type(ValueType.URL).required(true).build(), + new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + )) + .put(Type.RevocationList, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.RevocationList)).build(), + new Validation.Builder().name("id").type(ValueType.IRI).required(false).build() + )) + .put(Type.VerificationObject, List.of()) + .put(Type.VerificationObjectAssertion, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(false).mustContainOne(List.of("HostedBadge", "SignedBadge")).build(), + new Validation.Builder().name("creator").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).prerequisite("ASSERTION_VERIFICATION_DEPENDENCIES").build() + )) + .put(Type.VerificationObjectIssuer, List.of( + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.VerificationObject).build(), + new Validation.Builder().name("verificationProperty").type(ValueType.COMPACT_IRI).required(false).build(), + new Validation.Builder().name("startsWith").type(ValueType.URL).required(false).build(), + new Validation.Builder().name("allowedOrigins").type(ValueType.URL_AUTHORITY).required(false).many(true).build() + )) + .build(); + public static final String ID = Assertion.class.getCanonicalName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java new file mode 100644 index 0000000..509665b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java @@ -0,0 +1,142 @@ +package org.oneedtech.inspect.vc; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Validation class for Open Badges 2.0 types + */ +public class Validation { + private final String name; + private final Assertion.ValueType type; + private final boolean required; + private final boolean many; + private final List mustContainOne; + private final List prerequisites; + private final List expectedTypes; + private final boolean allowRemoteUrl; + private final boolean allowDataUri; + private final boolean fetch; + private final String defaultType; + private final boolean fullValidate; + + public Validation(Builder builder) { + this.name = builder.name; + this.type = builder.type; + this.required = builder.required; + this.many = builder.many; + this.mustContainOne = builder.mustContainOne; + this.prerequisites = builder.prerequisites; + this.expectedTypes = builder.expectedTypes; + this.allowRemoteUrl = builder.allowRemoteUrl; + this.allowDataUri = builder.allowDataUri; + this.fetch = builder.fetch; + this.defaultType = builder.defaultType; + this.fullValidate = builder.fullValidate; + } + + public static class Builder { + private String name; + private Assertion.ValueType type; + private boolean required; + private boolean many; + private List mustContainOne; + private List prerequisites; + private List expectedTypes; + private boolean allowRemoteUrl; + private boolean allowDataUri; + private boolean fetch; + private String defaultType; + private boolean fullValidate; + + public Builder() { + this.mustContainOne = new ArrayList<>(); + this.prerequisites = new ArrayList<>(); + this.expectedTypes = new ArrayList<>(); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder type(Assertion.ValueType type) { + this.type = type; + return this; + } + + public Builder required(boolean required) { + this.required = required; + return this; + } + + public Builder many(boolean many) { + this.many = many; + return this; + } + + public Builder mustContainOne(List elems) { + this.mustContainOne = elems; + return this; + } + + public Builder mustContainOneType(List types) { + this.mustContainOne = types.stream().map(Assertion.Type::toString).collect(Collectors.toList()); + return this; + } + + public Builder prerequisites(List elems) { + this.prerequisites = elems; + return this; + } + + public Builder prerequisite(String elem) { + this.prerequisites = List.of(elem); + return this; + } + + public Builder expectedTypes(List elems) { + this.expectedTypes = elems; + return this; + } + + public Builder expectedType(Assertion.Type type) { + this.expectedTypes = List.of(type); + return this; + } + + public Builder allowRemoteUrl(boolean allowRemoteUrl) { + this.allowRemoteUrl = allowRemoteUrl; + return this; + } + + public Builder allowDataUri(boolean allowDataUri) { + this.allowDataUri = allowDataUri; + return this; + } + + public Builder fetch(boolean fetch) { + this.fetch = fetch; + return this; + } + + public Builder defaultType(Assertion.Type defaultType) { + return defaultType(defaultType.toString()); + } + + public Builder defaultType(String defaultType) { + this.defaultType = defaultType; + return this; + } + + public Builder fullValidate(boolean fullValidate) { + this.fullValidate = fullValidate; + return this; + } + + public Validation build() { + return new Validation(this); + } + } +} From 2f11941ebd66230f9be452fc2b59bbe4a1e6450b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 28 Nov 2022 15:03:21 +0100 Subject: [PATCH 40/93] Pass node to reportForNonExistentProperty --- .../oneedtech/inspect/vc/probe/ContextPropertyProbe.java | 4 +++- .../java/org/oneedtech/inspect/vc/probe/PropertyProbe.java | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index 8a21518..c6f9192 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -8,6 +8,8 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.Credential.CredentialEnum; +import com.fasterxml.jackson.databind.JsonNode; + /** * A Probe that verifies a credential's context property. * @@ -23,7 +25,7 @@ public class ContextPropertyProbe extends StringValuePropertyProbe { } @Override - protected ReportItems reportForNonExistentProperty(RunContext ctx) { + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { return notRun("No @context property", ctx); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java index ca64f9e..ed21f18 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -1,12 +1,10 @@ package org.oneedtech.inspect.vc.probe; -import java.util.List; import java.util.function.BiFunction; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -28,14 +26,13 @@ public class PropertyProbe extends Probe { public ReportItems run(JsonNode root, RunContext ctx) throws Exception { JsonNode propertyNode = root.get(propertyName); if (propertyNode == null) { - return reportForNonExistentProperty(ctx); + return reportForNonExistentProperty(root, ctx); } - List values = JsonNodeUtil.asStringList(propertyNode); return validations.apply(propertyNode, ctx); } - protected ReportItems reportForNonExistentProperty(RunContext ctx) { + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { return fatal("No " + propertyName + " property", ctx); } From 1cc64d2ae91eafb74fcd1184642a80136529b00d Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 29 Nov 2022 11:24:32 +0100 Subject: [PATCH 41/93] Basic validation of nodes which are not Id --- .../org/oneedtech/inspect/vc/Assertion.java | 46 +++-- .../oneedtech/inspect/vc/OB20Inspector.java | 17 +- .../org/oneedtech/inspect/vc/Validation.java | 49 +++++ .../vc/probe/ValidationPropertyProbe.java | 135 ++++++++++++++ .../vc/util/PrimitiveValueValidator.java | 173 ++++++++++++++++++ 5 files changed, 402 insertions(+), 18 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 2c50b67..205240f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -5,11 +5,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.vc.util.JsonNodeUtil; +import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.MoreObjects; @@ -125,23 +127,33 @@ public class Assertion extends Credential { } public enum ValueType { - BOOLEAN, - COMPACT_IRI, - DATA_URI, - DATA_URI_OR_URL, - DATETIME, - EMAIL, - ID, - IDENTITY_HASH, - IRI, - LANGUAGE, - MARKDOWN_TEXT, - RDF_TYPE, - TELEPHONE, - TEXT, - TEXT_OR_NUMBER, - URL, - URL_AUTHORITY; + BOOLEAN(PrimitiveValueValidator::validateBoolean), + COMPACT_IRI(PrimitiveValueValidator::validateCompactIri), + DATA_URI(PrimitiveValueValidator::validateDataUri), + DATA_URI_OR_URL(PrimitiveValueValidator::validateDataUriOrUrl), + DATETIME(PrimitiveValueValidator::validateDatetime), + EMAIL(PrimitiveValueValidator::validateEmail), + ID(null), + IDENTITY_HASH(PrimitiveValueValidator::validateIdentityHash), + IRI(PrimitiveValueValidator::validateIri), + LANGUAGE(PrimitiveValueValidator::validateLanguage), + MARKDOWN_TEXT(PrimitiveValueValidator::validateMarkdown), + RDF_TYPE(PrimitiveValueValidator::validateRdfType), + TELEPHONE(PrimitiveValueValidator::validateTelephone), + TEXT(PrimitiveValueValidator::validateText), + TEXT_OR_NUMBER(PrimitiveValueValidator::validateTextOrNumber), + URL(PrimitiveValueValidator::validateUrl), + URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority); + + private final Function validationFunction; + + private ValueType(Function validationFunction) { + this.validationFunction = validationFunction; + } + + public Function getValidationFunction() { + return validationFunction; + } public static List primitives = List.of(BOOLEAN, DATA_URI_OR_URL, DATETIME, ID, IDENTITY_HASH, IRI, LANGUAGE, MARKDOWN_TEXT, TELEPHONE, TEXT, TEXT_OR_NUMBER, URL, URL_AUTHORITY); 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 0f6c636..bf1eb24 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 @@ -21,7 +21,6 @@ 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.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.JsonLDCompactionProve; @@ -31,6 +30,7 @@ import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; +import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.fasterxml.jackson.databind.JsonNode; @@ -116,6 +116,21 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + // Validates the Open Badge + List validations = assertion.getValidations(); + for (Validation validation : validations) { + probeCount++; + accumulator.add(new ValidationPropertyProbe(validation).run(assertion.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + // Each Badge Object contains all required properties for its class + // This could be done validating with the schema, but seems that there are some error on that file + // So, we do a manual Probe for the nodes + + // accumulator.add(new RequiredFieldsProbe(jsonLdGeneratedObject).run(assertion, ctx)); + // if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + //canonical schema and inline schemata // SchemaKey schema = assertion.getSchemaKey().orElseThrow(); // for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java index 509665b..8ecd22c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java @@ -36,6 +36,55 @@ public class Validation { this.fullValidate = builder.fullValidate; } + + public String getName() { + return name; + } + + public Assertion.ValueType getType() { + return type; + } + + public boolean isRequired() { + return required; + } + + public boolean isMany() { + return many; + } + + public List getMustContainOne() { + return mustContainOne; + } + + public List getPrerequisites() { + return prerequisites; + } + + public List getExpectedTypes() { + return expectedTypes; + } + + public boolean isAllowRemoteUrl() { + return allowRemoteUrl; + } + + public boolean isAllowDataUri() { + return allowDataUri; + } + + public boolean isFetch() { + return fetch; + } + + public String getDefaultType() { + return defaultType; + } + + public boolean isFullValidate() { + return fullValidate; + } + public static class Builder { private String name; private Assertion.ValueType type; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java new file mode 100644 index 0000000..a64b0aa --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java @@ -0,0 +1,135 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.function.Function; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.Assertion.ValueType; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; + + +public class ValidationPropertyProbe extends PropertyProbe { + private final Validation validation; + + public ValidationPropertyProbe(Validation validation) { + super(ID + "<" + validation.getName() + ">", validation.getName()); + this.validation = validation; + setValidations(this::validate); + } + + + @Override + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { + if (validation.isRequired()) { + return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx); + } else { + // optional property + return success(ctx); + } + } + + /** + * Validates presence and data type of a single property that is + * expected to be one of the Open Badges Primitive data types or an ID. + * @param node node to check data type + * @param ctx associated run context + * @return validation result + */ + private ReportItems validate(JsonNode node, RunContext ctx) { + // required property + if (validation.isRequired()) { + if (node.isObject()) { + if (!node.fieldNames().hasNext()) { + return error("Required property " + validation.getName() + " value " + node.toString() + " is not acceptable", ctx); + } + } else { + List values = JsonNodeUtil.asStringList(node); + if (values == null ||values.isEmpty()) { + return error("Required property " + validation.getName() + " value " + values + " is not acceptable", ctx); + } + } + } + + List nodeList = JsonNodeUtil.asNodeList(node); + // many property + if (!validation.isMany()) { + if (nodeList.size() > 1) { + return error("Property " + validation.getName() + "has more than the single allowed value.", ctx); + } + } + + try { + if (validation.getType() != ValueType.ID) { + Function validationFunction = validation.getType().getValidationFunction(); + for (JsonNode childNode : nodeList) { + Boolean valid = validationFunction.apply(childNode); + if (!valid) { + return error(validation.getType() + " property " + validation.getName() + " value " + childNode.toString() + " not valid", ctx); + } + } + } else { + /** + for i in range(len(values_to_test)): + val = values_to_test[i] + if isinstance(prop_value, (list, tuple,)): + value_to_test_path = [node_id, prop_name, i] + else: + value_to_test_path = [node_id, prop_name] + + if isinstance(val, dict): + actions.append( + add_task(VALIDATE_EXPECTED_NODE_CLASS, node_path=value_to_test_path, + expected_class=task_meta.get('expected_class'), + full_validate=task_meta.get('full_validate', True))) + continue + elif task_meta.get('allow_data_uri') and not PrimitiveValueValidator(ValueTypes.DATA_URI_OR_URL)(val): + raise ValidationError("ID-type property {} had value `{}` that isn't URI or DATA URI in {}.".format( + prop_name, abv(val), abv_node(node_id, node_path)) + ) + elif not task_meta.get('allow_data_uri', False) and not PrimitiveValueValidator(ValueTypes.IRI)(val): + actions.append(report_message( + "ID-type property {} had value `{}` where another scheme may have been expected {}.".format( + prop_name, abv(val), abv_node(node_id, node_path) + ), message_level=MESSAGE_LEVEL_WARNING)) + raise ValidationError( + "ID-type property {} had value `{}` not embedded node or in IRI format in {}.".format( + prop_name, abv(val), abv_node(node_id, node_path)) + ) + try: + target = get_node_by_id(state, val) + except IndexError: + if not task_meta.get('fetch', False): + if task_meta.get('allow_remote_url') and PrimitiveValueValidator(ValueTypes.URL)(val): + continue + if task_meta.get('allow_data_uri') and PrimitiveValueValidator(ValueTypes.DATA_URI)(val): + continue + raise ValidationError( + 'Node {} has {} property value `{}` that appears not to be in URI format'.format( + abv_node(node_id, node_path), prop_name, abv(val) + ) + ' or did not correspond to a known local node.') + else: + actions.append( + add_task(FETCH_HTTP_NODE, url=val, + expected_class=task_meta.get('expected_class'), + source_node_path=value_to_test_path + )) + else: + actions.append( + add_task(VALIDATE_EXPECTED_NODE_CLASS, node_id=val, + expected_class=task_meta.get('expected_class'))) + */ + } + } catch (Throwable t) { + return fatal(t.getMessage(), ctx); + } + + return success(ctx); + } + + public static final String ID = ValidationPropertyProbe.class.getSimpleName(); + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java new file mode 100644 index 0000000..0e81463 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java @@ -0,0 +1,173 @@ +package org.oneedtech.inspect.vc.util; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Locale; + +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; + +import com.apicatalog.jsonld.JsonLd; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.JsonDocument; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.google.common.io.Resources; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; + +/** + * Validator for ValueType. Translated into java from PrimitiveValueValidator in validation.py + */ +public class PrimitiveValueValidator { + + public static boolean validateBoolean(JsonNode value) { + return value.isValueNode() && value.isBoolean(); + } + + public static boolean validateCompactIri(JsonNode value) { + if (value.asText().equals("id") || validateIri(value)) { + return true; + } + + return false; + } + + public static boolean validateDataUri(JsonNode value) { + try { + URI uri = new URI(value.asText()); + return "data".equalsIgnoreCase(uri.getScheme()); + } catch (Throwable ignored) { + } + return false; + } + + public static boolean validateDataUriOrUrl(JsonNode value) { + return validateUrl(value) || validateDataUri(value); + } + + public static boolean validateDatetime(JsonNode value) { + try { + DateTimeFormatter.ISO_INSTANT.parse(value.asText()); + return true; + } catch (DateTimeParseException | NullPointerException ignored) { + } + return false; + } + + public static boolean validateEmail(JsonNode value) { + return value.asText().matches("(^[^@\\s]+@[^@\\s]+$)"); + } + + public static boolean is_hashed_identity_hash(JsonNode value) { + return value.asText().matches("md5\\$[\\da-fA-F]{32}$") || value.asText().matches("sha256\\$[\\da-fA-F]{64}$"); + } + + /** + * Validates that identity is a string. More specific rules may only be enforced at the class instance level. + * @param value + * @return + */ + public static boolean validateIdentityHash(JsonNode value) { + return validateText(value); + } + + /** + * Checks if a string matches an acceptable IRI format and scheme. For now, only accepts a few schemes, + * 'http', 'https', blank node identifiers, and 'urn:uuid' + + * @return + */ + public static boolean validateIri(JsonNode value) { + return validateUrl(value) || value.asText().matches("^_:") || value.asText().matches("^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"); + } + + public static boolean validateLanguage(JsonNode value) { + try { + return validateText(value) && new Locale.Builder().setLanguageTag(value.asText()).build() != null; + } catch (IllformedLocaleException ignored) { + // value is not a valid locale + } + return false; + } + + public static boolean validateMarkdown(JsonNode value) { + return validateText(value); + } + + public static boolean validateRdfType(JsonNode value) { + if (!validateText(value)) { + return false; + } + + ObjectMapper mapper = new ObjectMapper(); // TODO: get from RunContext + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + try { + JsonNode node = mapper.readTree(Resources.getResource("contexts/ob-v2p0.json")); + ObjectReader readerForUpdating = mapper.readerForUpdating(node); + JsonNode merged = readerForUpdating.readValue("{\"type\": \"" + value.asText() + "\"}"); + + JsonDocument jsonDocument = JsonDocument.of(new StringReader(merged.toString())); + JsonNode expanded = mapper.readTree(JsonLd.expand(jsonDocument).get().toString()); + + return validateIri(JsonNodeUtil.asNodeList(expanded, "$[0].@type[0]", jsonPath).get(0)); + + } catch (NullPointerException | IOException | JsonLdError e) { + return false; + } + } + + public static boolean validateTelephone(JsonNode value) { + return value.asText().matches("^\\+?[1-9]\\d{1,14}(;ext=\\d+)?$"); + } + + public static boolean validateText(JsonNode value) { + return value.isValueNode() && value.isTextual(); + } + + public static boolean validateTextOrNumber(JsonNode value) { + return value.isValueNode() && value.isTextual() || value.isNumber(); + } + + public static boolean validateUrl(JsonNode value) { + if (!value.isValueNode()) { + return false; + } + + try { + new URL(value.asText()); + return true; + } catch (MalformedURLException ignored) { + // value is not a valid URL + } + return false; + } + + public static boolean validateUrlAuthority(JsonNode value) { + if (!validateText(value)) { + return false; + } + + URI testUri; + try { + testUri = new URI("http://" + value.asText() + "/test"); + String host = testUri.getHost(); + if (host == null || !host.matches("(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\\.)+[a-zA-Z]{2,63}$)")) { + return false; + } + return testUri.getScheme().equals("http") && host.equals(value.asText()) && testUri.getPath().equals("/test") && testUri.getQuery() == null; + } catch (URISyntaxException e) { + return false; + } + } +} From 0d6d97cd4ffe0cd628950ebea2928ad11c332974 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 29 Nov 2022 18:06:36 +0100 Subject: [PATCH 42/93] Validate properties in Assertion --- .../org/oneedtech/inspect/vc/Assertion.java | 9 +- .../oneedtech/inspect/vc/OB20Inspector.java | 13 +- .../vc/jsonld/JsonLdGeneratedObject.java | 6 +- .../jsonld/probe/JsonLDCompactionProve.java | 19 +-- .../vc/probe/ValidationPropertyProbe.java | 156 ++++++++++++------ .../vc/util/CachingDocumentLoader.java | 2 +- .../vc/util/PrimitiveValueValidator.java | 2 +- .../org/oneedtech/inspect/vc/OB20Tests.java | 13 +- .../inspect/vc/util/TestOB20Inspector.java | 10 +- 9 files changed, 146 insertions(+), 84 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 205240f..df46f7c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -1,6 +1,5 @@ package org.oneedtech.inspect.vc; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -27,7 +26,7 @@ public class Assertion extends Credential { final Assertion.Type assertionType; protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { - super(ID, resource, data, jwt, schemas); + super(resource.getID(), resource, data, jwt, schemas); JsonNode typeNode = jsonData.get("type"); this.assertionType = Assertion.Type.valueOf(typeNode); @@ -124,6 +123,10 @@ public class Assertion extends Credential { public List getContextUris() { return List.of("https://w3id.org/openbadges/v2") ; } + + public List getValidations() { + return validationMap.get(this); + } } public enum ValueType { @@ -165,7 +168,7 @@ public class Assertion extends Credential { new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Assertion)).build(), new Validation.Builder().name("recipient").type(ValueType.ID).expectedType(Type.IdentityObject).required(true).build(), new Validation.Builder().name("badge").type(ValueType.ID).prerequisite("ASN_FLATTEN_BC").expectedType(Type.BadgeClass).fetch(true).required(true).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedTypes(List.of(Type.VerificationObjectAssertion)).required(true).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectAssertion).required(true).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), 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 bf1eb24..7d06bc7 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 @@ -33,6 +33,7 @@ import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoader; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -76,6 +77,7 @@ public class OB20Inspector extends Inspector { ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + DocumentLoader documentLoader = getDocumentLoader(); RunContext ctx = new RunContext.Builder() .put(this) @@ -85,6 +87,7 @@ public class OB20Inspector extends Inspector { .put(Key.GENERATED_OBJECT_BUILDER, new Assertion.Builder()) .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB20) .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) + .put(Key.JSON_DOCUMENT_LOADER, documentLoader) .build(); List accumulator = new ArrayList<>(); @@ -97,7 +100,7 @@ public class OB20Inspector extends Inspector { if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); // we expect the above to place a generated object in the context - Assertion assertion = ctx.getGeneratedObject(Assertion.ID); + Assertion assertion = ctx.getGeneratedObject(resource.getID()); //context and type properties CredentialEnum type = assertion.getCredentialType(); @@ -108,11 +111,11 @@ public class OB20Inspector extends Inspector { } // let's compact - accumulator.add(getCompactionProbe(assertion).run(assertion, ctx)); + accumulator.add(new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0)).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); // validate JSON LD - JsonLdGeneratedObject jsonLdGeneratedObject = ctx.getGeneratedObject(JsonLdGeneratedObject.ID); + JsonLdGeneratedObject jsonLdGeneratedObject = ctx.getGeneratedObject(JsonLDCompactionProve.getId(assertion)); accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); @@ -145,8 +148,8 @@ public class OB20Inspector extends Inspector { return new Report(ctx, new ReportItems(accumulator), probeCount); } - protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { - return new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0)); + protected DocumentLoader getDocumentLoader() { + return new CachingDocumentLoader(); } public static class Builder extends Inspector.Builder { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java index 9fdd752..fa7dcff 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java @@ -6,7 +6,11 @@ public class JsonLdGeneratedObject extends GeneratedObject { private String json; public JsonLdGeneratedObject(String json) { - super(ID, GeneratedObject.Type.INTERNAL); + this(ID, json); + } + + public JsonLdGeneratedObject(String id, String json) { + super(id, GeneratedObject.Type.INTERNAL); this.json = json; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index 284f44b..9a7bc6f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -1,36 +1,29 @@ package org.oneedtech.inspect.vc.jsonld.probe; import java.io.StringReader; -import java.net.URI; -import java.util.Map; 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.Credential; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; -import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.JsonLd; import com.apicatalog.jsonld.JsonLdOptions; import com.apicatalog.jsonld.api.CompactionApi; import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; import jakarta.json.JsonObject; public class JsonLDCompactionProve extends Probe { private final String context; - private final Map localDomains; public JsonLDCompactionProve(String context) { - this(context, null); - } - - public JsonLDCompactionProve(String context, Map localDomains) { super(ID); this.context = context; - this.localDomains = localDomains; } @Override @@ -39,10 +32,10 @@ public class JsonLDCompactionProve extends Probe { // compact JSON JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); CompactionApi compactApi = JsonLd.compact(jsonDocument, context); - compactApi.options(new JsonLdOptions(new CachingDocumentLoader(localDomains))); + compactApi.options(new JsonLdOptions((DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER))); JsonObject compactedObject = compactApi.get(); - ctx.addGeneratedObject(new JsonLdGeneratedObject(compactedObject.toString())); + ctx.addGeneratedObject(new JsonLdGeneratedObject(getId(crd), compactedObject.toString())); // Handle mismatch between URL node source and declared ID. if (compactedObject.get("id") != null && crd.getResource().getID() != null @@ -57,5 +50,9 @@ public class JsonLDCompactionProve extends Probe { } } + public static String getId(Credential crd) { + return "json-ld-compact:" + crd.getResource().getID(); + } + public static final String ID = JsonLDCompactionProve.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java index a64b0aa..f23760c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java @@ -1,27 +1,53 @@ package org.oneedtech.inspect.vc.probe; +import static org.oneedtech.inspect.vc.Assertion.ValueType.DATA_URI; +import static org.oneedtech.inspect.vc.Assertion.ValueType.DATA_URI_OR_URL; +import static org.oneedtech.inspect.vc.Assertion.ValueType.URL; + +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import java.util.function.Function; +import java.util.stream.Collectors; +import org.oneedtech.inspect.core.probe.Outcome; +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.Validation; +import org.oneedtech.inspect.core.report.ReportUtil; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.vc.Assertion; import org.oneedtech.inspect.vc.Assertion.ValueType; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; public class ValidationPropertyProbe extends PropertyProbe { private final Validation validation; + private final boolean fullValidate; // TODO: fullValidate public ValidationPropertyProbe(Validation validation) { + this(validation, false); + } + + public ValidationPropertyProbe(Validation validation, boolean fullValidate) { super(ID + "<" + validation.getName() + ">", validation.getName()); this.validation = validation; + this.fullValidate = fullValidate; setValidations(this::validate); } + @Override protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { if (validation.isRequired()) { @@ -40,6 +66,8 @@ public class ValidationPropertyProbe extends PropertyProbe { * @return validation result */ private ReportItems validate(JsonNode node, RunContext ctx) { + ReportItems result = new ReportItems(); + // required property if (validation.isRequired()) { if (node.isObject()) { @@ -72,64 +100,88 @@ public class ValidationPropertyProbe extends PropertyProbe { } } } else { - /** - for i in range(len(values_to_test)): - val = values_to_test[i] - if isinstance(prop_value, (list, tuple,)): - value_to_test_path = [node_id, prop_name, i] - else: - value_to_test_path = [node_id, prop_name] + for (JsonNode childNode : nodeList) { + if (childNode.isObject()) { + result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx))); + continue; + } else if (validation.isAllowDataUri() && !DATA_URI_OR_URL.getValidationFunction().apply(childNode)){ + return error("ID-type property " + validation.getName() + " had value `" + childNode.toString() + "` that isn't URI or DATA URI in " + node.toString(), ctx); + } else if (!validation.isAllowDataUri() && !ValueType.IRI.getValidationFunction().apply(childNode)) { + return error("ID-type property " + validation.getName() + " had value `" + childNode.toString() + "` where another scheme may have been expected " + node.toString(), ctx); + } - if isinstance(val, dict): - actions.append( - add_task(VALIDATE_EXPECTED_NODE_CLASS, node_path=value_to_test_path, - expected_class=task_meta.get('expected_class'), - full_validate=task_meta.get('full_validate', True))) - continue - elif task_meta.get('allow_data_uri') and not PrimitiveValueValidator(ValueTypes.DATA_URI_OR_URL)(val): - raise ValidationError("ID-type property {} had value `{}` that isn't URI or DATA URI in {}.".format( - prop_name, abv(val), abv_node(node_id, node_path)) - ) - elif not task_meta.get('allow_data_uri', False) and not PrimitiveValueValidator(ValueTypes.IRI)(val): - actions.append(report_message( - "ID-type property {} had value `{}` where another scheme may have been expected {}.".format( - prop_name, abv(val), abv_node(node_id, node_path) - ), message_level=MESSAGE_LEVEL_WARNING)) - raise ValidationError( - "ID-type property {} had value `{}` not embedded node or in IRI format in {}.".format( - prop_name, abv(val), abv_node(node_id, node_path)) - ) - try: - target = get_node_by_id(state, val) - except IndexError: - if not task_meta.get('fetch', False): - if task_meta.get('allow_remote_url') and PrimitiveValueValidator(ValueTypes.URL)(val): - continue - if task_meta.get('allow_data_uri') and PrimitiveValueValidator(ValueTypes.DATA_URI)(val): - continue - raise ValidationError( - 'Node {} has {} property value `{}` that appears not to be in URI format'.format( - abv_node(node_id, node_path), prop_name, abv(val) - ) + ' or did not correspond to a known local node.') - else: - actions.append( - add_task(FETCH_HTTP_NODE, url=val, - expected_class=task_meta.get('expected_class'), - source_node_path=value_to_test_path - )) - else: - actions.append( - add_task(VALIDATE_EXPECTED_NODE_CLASS, node_id=val, - expected_class=task_meta.get('expected_class'))) - */ + // get node from context + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(childNode.asText()); + if (resolved == null) { + if (!validation.isFetch()) { + if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) { + continue; + } + + if (validation.isAllowDataUri() && DATA_URI.getValidationFunction().apply(childNode)) { + continue; + } + return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); + } else { + // fetch + UriResource uriResource = resolveUriResource(ctx, childNode); + + result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + Assertion assertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); + + // compact ld + result = new ReportItems(List.of(result, new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0)).run(assertion, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + JsonLdGeneratedObject fetched = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(assertion)); + JsonNode fetchedNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(fetched.getJson()); + + // validate document + result = new ReportItems(List.of(result, validateExpectedTypes(fetchedNode, ctx))); + } + } + } + } else { + // validate expected node class + result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx))); + } + } } } catch (Throwable t) { return fatal(t.getMessage(), ctx); } - return success(ctx); + return result.size() > 0 ? result : success(ctx); } - public static final String ID = ValidationPropertyProbe.class.getSimpleName(); + private UriResource resolveUriResource(RunContext ctx, JsonNode childNode) throws URISyntaxException { + URI uri = new URI(childNode.asText()); + 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; + } + + private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) { + List results = validation.getExpectedTypes().stream() + .flatMap(type -> type.getValidations().stream()) + .map(v -> new ValidationPropertyProbe(v, validation.isFullValidate())) + .map(probe -> { + try { + return probe.run(node, ctx); + } catch (Exception e) { + return ReportUtil.onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, null, e); + } + }) + .collect(Collectors.toList()); + return new ReportItems(results); + } + public static final String ID = ValidationPropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 27d7b36..937db8b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -80,7 +80,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { * Resolved given url. If the url is from one of local domain, a URL of the relative resource will be returned * @throws URISyntaxException */ - private URI resolve(URI url) throws URISyntaxException { + public URI resolve(URI url) throws URISyntaxException { if (localDomains != null) { URI base = url.resolve("/"); if (localDomains.containsKey(base)) { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java index 0e81463..8303fe2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java @@ -110,7 +110,7 @@ public class PrimitiveValueValidator { } ObjectMapper mapper = new ObjectMapper(); // TODO: get from RunContext - JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); // TODO: get from RunContext try { JsonNode node = mapper.readTree(Resources.getResource("contexts/ob-v2p0.json")); 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 05f678f..72ab6ff 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 @@ -28,6 +28,8 @@ public class OB20Tests { static void setup() throws URISyntaxException { validator = new TestBuilder() .add(new URI("https://www.example.org/"), "ob20/assets") + .add(new URI("https://example.org/"), "ob20/assets") + .add(new URI("http://example.org/"), "ob20/assets") .set(Behavior.TEST_INCLUDE_SUCCESS, true) .set(Behavior.TEST_INCLUDE_WARNINGS, false) .set(Behavior.VALIDATOR_FAIL_FAST, true) @@ -55,11 +57,12 @@ public class OB20Tests { @Test void testSimpleBadgeClassJsonValid() { - assertDoesNotThrow(()->{ - Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource()); - if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + // TODO: commented out due to lack of prerequisite tasks yet + // assertDoesNotThrow(()->{ + // Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource()); + // if(verbose) PrintHelper.print(report, true); + // assertValid(report); + // }); } @Test diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java index 323ec88..2728550 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java @@ -7,9 +7,9 @@ import java.util.Map; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.spec.Specification; -import org.oneedtech.inspect.vc.Assertion; import org.oneedtech.inspect.vc.OB20Inspector; -import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; + +import com.apicatalog.jsonld.loader.DocumentLoader; /** * OpenBadges 2.0 Test inspector. @@ -28,9 +28,9 @@ public class TestOB20Inspector extends OB20Inspector { } @Override - protected JsonLDCompactionProve getCompactionProbe(Assertion assertion) { - return new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0), localDomains); - } + protected DocumentLoader getDocumentLoader() { + return new CachingDocumentLoader(localDomains); + } public static class TestBuilder extends OB20Inspector.Builder { final Map localDomains; From d3667e580c0a9376decbcb4f8c0cf38ded30c5fc Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 08:24:40 +0100 Subject: [PATCH 43/93] New validation probe for rdf types --- .../oneedtech/inspect/vc/OB20Inspector.java | 6 +- .../vc/probe/ValidationPropertyProbe.java | 153 +++++++++++++++++- .../probe/ValidationPropertyProbeFactory.java | 24 +++ .../probe/ValidationRdfTypePropertyProbe.java | 58 +++++++ 4 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java 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 7d06bc7..f2c70e5 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 @@ -31,6 +31,7 @@ import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe; +import org.oneedtech.inspect.vc.probe.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.loader.DocumentLoader; @@ -119,11 +120,12 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - // Validates the Open Badge + // Validates the Open Badge, from the compacted form + JsonNode assertionNode = mapper.readTree(jsonLdGeneratedObject.getJson()); List validations = assertion.getValidations(); for (Validation validation : validations) { probeCount++; - accumulator.add(new ValidationPropertyProbe(validation).run(assertion.getJson(), ctx)); + accumulator.add(ValidationPropertyProbeFactory.of(validation).run(assertionNode, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java index f23760c..b9edbd1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java @@ -32,8 +32,8 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader; public class ValidationPropertyProbe extends PropertyProbe { - private final Validation validation; - private final boolean fullValidate; // TODO: fullValidate + protected final Validation validation; + protected final boolean fullValidate; // TODO: fullValidate public ValidationPropertyProbe(Validation validation) { this(validation, false); @@ -46,8 +46,6 @@ public class ValidationPropertyProbe extends PropertyProbe { setValidations(this::validate); } - - @Override protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { if (validation.isRequired()) { @@ -65,7 +63,7 @@ public class ValidationPropertyProbe extends PropertyProbe { * @param ctx associated run context * @return validation result */ - private ReportItems validate(JsonNode node, RunContext ctx) { + protected ReportItems validate(JsonNode node, RunContext ctx) { ReportItems result = new ReportItems(); // required property @@ -172,7 +170,7 @@ public class ValidationPropertyProbe extends PropertyProbe { private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) { List results = validation.getExpectedTypes().stream() .flatMap(type -> type.getValidations().stream()) - .map(v -> new ValidationPropertyProbe(v, validation.isFullValidate())) + .map(v -> ValidationPropertyProbeFactory.of(v, validation.isFullValidate())) .map(probe -> { try { return probe.run(node, ctx); @@ -183,5 +181,148 @@ public class ValidationPropertyProbe extends PropertyProbe { .collect(Collectors.toList()); return new ReportItems(results); } + + private void flattenEmbeddedResource() { + /* + try: + node_id = task_meta['node_id'] + node = get_node_by_id(state, node_id) + prop_name = task_meta['prop_name'] + node_class = task_meta['node_class'] + except (IndexError, KeyError): + raise TaskPrerequisitesError() + + actions = [] + value = node.get(prop_name) + if value is None: + return task_result(True, "Expected property {} was missing in node {}".format(node_id)) + + if isinstance(value, six.string_types): + return task_result( + True, "Property {} referenced from {} is not embedded in need of flattening".format( + prop_name, abv_node(node_id=node_id) + )) + + if not isinstance(value, dict): + return task_result( + False, "Property {} referenced from {} is not a JSON object or string as expected".format( + prop_name, abv_node(node_id=node_id) + )) + embedded_node_id = value.get('id') + + if embedded_node_id is None: + new_node = value.copy() + embedded_node_id = '_:{}'.format(uuid.uuid4()) + new_node['id'] = embedded_node_id + new_node['@context'] = OPENBADGES_CONTEXT_V2_URI + actions.append(add_node(embedded_node_id, data=new_node)) + actions.append(patch_node(node_id, {prop_name: embedded_node_id})) + actions.append(report_message( + 'Node id missing at {}. A blank node ID has been assigned'.format( + abv_node(node_path=[node_id, prop_name], length=64) + ), message_level=MESSAGE_LEVEL_WARNING) + ) + elif not isinstance(embedded_node_id, six.string_types) or not is_iri(embedded_node_id): + return task_result(False, "Embedded JSON object at {} has no proper assigned id.".format( + abv_node(node_path=[node_id, prop_name]))) + + elif node_class == OBClasses.Assertion and not is_url(embedded_node_id): + if not re.match(URN_REGEX, embedded_node_id, re.IGNORECASE): + actions.append(report_message( + 'ID format for {} at {} not in an expected HTTP or URN:UUID scheme'.format( + embedded_node_id, abv_node(node_path=[node_id, prop_name]) + ))) + new_node = value.copy() + new_node['@context'] = OPENBADGES_CONTEXT_V2_URI + actions.append(add_node(embedded_node_id, data=value)) + actions.append(patch_node(node_id, {prop_name: embedded_node_id})) + + else: + actions.append(patch_node(node_id, {prop_name: embedded_node_id})) + if not node_match_exists(state, embedded_node_id) and not filter_tasks( + state, node_id=embedded_node_id, task_type=FETCH_HTTP_NODE): + # fetch + actions.append(add_task(FETCH_HTTP_NODE, url=embedded_node_id)) + + return task_result(True, "Embedded {} node in {} queued for storage and/or refetching as needed", actions) + + */ + } + + private void validateImage() { + /* +def validate_image(state, task_meta, **options): + try: + node_id = task_meta.get('node_id') + node_path = task_meta.get('node_path') + prop_name = task_meta.get('prop_name', 'image') + node_class = task_meta.get('node_class') + required = bool(task_meta.get('required', False)) + if node_id: + node = get_node_by_id(state, node_id) + node_path = [node_id] + else: + node = get_node_by_path(state, node_path) + + if options.get('cache_backend'): + session = requests_cache.CachedSession( + backend=options['cache_backend'], expire_after=options.get('cache_expire_after', 300)) + else: + session = requests.Session() + except (IndexError, TypeError, KeyError): + raise TaskPrerequisitesError() + + actions = [] + + image_val = node.get(prop_name) + + if image_val is None: + return task_result(not required, "Could not load and validate image in node {}".format(abv_node(node_id, node_path))) + if isinstance(image_val, six.string_types): + url = image_val + elif isinstance(image_val, dict): + url = image_val.get('id') + elif isinstance(image_val, list): + return task_result(False, "many images not allowed") + else: + raise TypeError("Could not interpret image property value {}".format( + abbreviate_value(image_val) + )) + if is_data_uri(url): + if task_meta.get('allow_data_uri', False) is False: + return task_result(False, "Image in node {} may not be a data URI.".format(abv_node(node_id, node_path))) + try: + mimetypes = re.match(r'(?P^data):(?P[^,]{0,}?)?(?Pbase64)?,(?P.*$)', url).group( + 'mimetypes') + if 'image/png' not in mimetypes and 'image/svg+xml' not in mimetypes: + raise ValueError("Disallowed filetype") + except (AttributeError, ValueError,): + return task_result( + False, "Data URI image does not declare any of the allowed PNG or SVG mime types in {}".format( + abv_node(node_id, node_path)) + ) + elif url: + existing_file = state.get('input', {}).get('original_json', {}).get(url) + if existing_file: + return task_result(True, "Image resource already stored for url {}".format(abbreviate_value(url))) + else: + try: + result = session.get( + url, headers={'Accept': 'application/ld+json, application/json, image/png, image/svg+xml'} + ) + result.raise_for_status() + content_type = result.headers['content-type'] + encoded_body = base64.b64encode(result.content) + data_uri = "data:{};base64,{}".format(content_type, encoded_body) + + except (requests.ConnectionError, requests.HTTPError, KeyError): + return task_result(False, "Could not fetch image at {}".format(url)) + else: + actions.append(store_original_resource(url, data_uri)) + + return task_result(True, "Validated image for node {}".format(abv_node(node_id, node_path)), actions) + + */ + } public static final String ID = ValidationPropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java new file mode 100644 index 0000000..0156d32 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java @@ -0,0 +1,24 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; + +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.Assertion.ValueType; + +/** + * Factory for ValidationPropertyProbes + * @author xaracil + */ +public class ValidationPropertyProbeFactory { + public static ValidationPropertyProbe of(Validation validation) { + return of(validation, false); + } + + public static ValidationPropertyProbe of(Validation validation, boolean fullValidate) { + checkNotNull(validation.getType()); + if (validation.getType() == ValueType.RDF_TYPE) { + return new ValidationRdfTypePropertyProbe(validation, fullValidate); + } + return new ValidationPropertyProbe(validation, fullValidate); + } +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java new file mode 100644 index 0000000..338820e --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java @@ -0,0 +1,58 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.stream.Collectors; + +import org.oneedtech.inspect.core.probe.Outcome; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.TextNode; + +public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe { + public ValidationRdfTypePropertyProbe(Validation validation) { + super(validation); + } + + public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) { + super(validation, fullValidate); + } + + @Override + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { + if (!validation.isRequired()) { + // check if we have a default type + if (validation.getDefaultType() != null) { + JsonNodeFactory factory = JsonNodeFactory.instance; + TextNode textNode = factory.textNode(validation.getDefaultType()); + // validate with default value + return validate(textNode, ctx); + } + } + + return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx); + } + + @Override + protected ReportItems validate(JsonNode node, RunContext ctx) { + ReportItems result = super.validate(node, ctx); + if (result.contains(Outcome.ERROR, Outcome.FATAL)) { + return result; + } + if (!validation.getMustContainOne().isEmpty()) { + List values = JsonNodeUtil.asStringList(node); + boolean valid = validation.getMustContainOne().stream().anyMatch(type -> values.contains(type)); + if (!valid) { + return new ReportItems(List.of(result, + fatal("Node " + validation.getName() + " of type " + node.asText() + + " does not have type among allowed values (" + validation.getMustContainOne().stream().collect(Collectors.joining(",")) + ")", ctx) + )); + } + } + return new ReportItems(List.of(result, success(ctx))); + } +} From 60e0187052052c07aaf10ca16ba35051d1e53150 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 10:52:38 +0100 Subject: [PATCH 44/93] Added image validation --- .../org/oneedtech/inspect/vc/Assertion.java | 7 +- .../probe/ValidationImagePropertyProbe.java | 65 +++++++++++++++ .../vc/probe/ValidationPropertyProbe.java | 83 +------------------ .../probe/ValidationPropertyProbeFactory.java | 3 + 4 files changed, 77 insertions(+), 81 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index df46f7c..74125f3 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -146,7 +146,9 @@ public class Assertion extends Credential { TEXT(PrimitiveValueValidator::validateText), TEXT_OR_NUMBER(PrimitiveValueValidator::validateTextOrNumber), URL(PrimitiveValueValidator::validateUrl), - URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority); + URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority), + + IMAGE(null); private final Function validationFunction; @@ -173,7 +175,8 @@ public class Assertion extends Credential { new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), - new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build() + new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build(), + new Validation.Builder().name("image").type(ValueType.IMAGE).required(false).many(false).allowDataUri(false).build() )) .put(Type.BadgeClass, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java new file mode 100644 index 0000000..d9781ac --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java @@ -0,0 +1,65 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.resource.MimeType; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ValidationImagePropertyProbe extends ValidationPropertyProbe { + + public ValidationImagePropertyProbe(Validation validation) { + super(validation); + } + + public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) { + super(validation, fullValidate); + } + + @Override + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { + return notRun("Could not load and validate image in node " + node.toString(), ctx); + } + + @Override + protected ReportItems validate(JsonNode node, RunContext ctx) { + if (node.isArray()) { + return error("many images not allowed", ctx); + } + String url = node.isObject() ? node.get("id").asText() : node.asText(); + if (PrimitiveValueValidator.validateDataUri(node)) { + if (!validation.isAllowDataUri()) { + return error("Image in node " + node + " may not be a data URI.", ctx); + } + + // check mime types + final Pattern pattern = Pattern.compile("(^data):([^,]{0,}?)?(base64)?,(.*$)"); + final Matcher matcher = pattern.matcher(url); + if (matcher.matches()) { + MimeType mimeType = new MimeType(matcher.toMatchResult().group(2)); + if (!allowedMimeTypes.contains(mimeType)) { + return error("Data URI image does not declare any of the allowed PNG or SVG mime types in " + node.asText(), ctx); + } + } + } else if (!url.isEmpty()) { + try { + UriResource uriResource = resolveUriResource(ctx, url); + // TODO: load resource from cache + // TODO: check accept type -> 'Accept': 'application/ld+json, application/json, image/png, image/svg+xml' + uriResource.asByteSource(); + } catch (Throwable t) { + return fatal(t.getMessage(), ctx); + } + } + return success(ctx); + } + + private static final List allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java index b9edbd1..ba242a6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java @@ -122,7 +122,7 @@ public class ValidationPropertyProbe extends PropertyProbe { return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); } else { // fetch - UriResource uriResource = resolveUriResource(ctx, childNode); + UriResource uriResource = resolveUriResource(ctx, childNode.asText()); result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { @@ -152,8 +152,8 @@ public class ValidationPropertyProbe extends PropertyProbe { return result.size() > 0 ? result : success(ctx); } - private UriResource resolveUriResource(RunContext ctx, JsonNode childNode) throws URISyntaxException { - URI uri = new URI(childNode.asText()); + protected UriResource resolveUriResource(RunContext ctx, String url) throws URISyntaxException { + URI uri = new URI(url); UriResource initialUriResource = new UriResource(uri); UriResource uriResource = initialUriResource; @@ -182,7 +182,7 @@ public class ValidationPropertyProbe extends PropertyProbe { return new ReportItems(results); } - private void flattenEmbeddedResource() { + private void flattenEmbeddedResource(JsonNode node, RunContext ctx) { /* try: node_id = task_meta['node_id'] @@ -249,80 +249,5 @@ public class ValidationPropertyProbe extends PropertyProbe { */ } - private void validateImage() { - /* -def validate_image(state, task_meta, **options): - try: - node_id = task_meta.get('node_id') - node_path = task_meta.get('node_path') - prop_name = task_meta.get('prop_name', 'image') - node_class = task_meta.get('node_class') - required = bool(task_meta.get('required', False)) - if node_id: - node = get_node_by_id(state, node_id) - node_path = [node_id] - else: - node = get_node_by_path(state, node_path) - - if options.get('cache_backend'): - session = requests_cache.CachedSession( - backend=options['cache_backend'], expire_after=options.get('cache_expire_after', 300)) - else: - session = requests.Session() - except (IndexError, TypeError, KeyError): - raise TaskPrerequisitesError() - - actions = [] - - image_val = node.get(prop_name) - - if image_val is None: - return task_result(not required, "Could not load and validate image in node {}".format(abv_node(node_id, node_path))) - if isinstance(image_val, six.string_types): - url = image_val - elif isinstance(image_val, dict): - url = image_val.get('id') - elif isinstance(image_val, list): - return task_result(False, "many images not allowed") - else: - raise TypeError("Could not interpret image property value {}".format( - abbreviate_value(image_val) - )) - if is_data_uri(url): - if task_meta.get('allow_data_uri', False) is False: - return task_result(False, "Image in node {} may not be a data URI.".format(abv_node(node_id, node_path))) - try: - mimetypes = re.match(r'(?P^data):(?P[^,]{0,}?)?(?Pbase64)?,(?P.*$)', url).group( - 'mimetypes') - if 'image/png' not in mimetypes and 'image/svg+xml' not in mimetypes: - raise ValueError("Disallowed filetype") - except (AttributeError, ValueError,): - return task_result( - False, "Data URI image does not declare any of the allowed PNG or SVG mime types in {}".format( - abv_node(node_id, node_path)) - ) - elif url: - existing_file = state.get('input', {}).get('original_json', {}).get(url) - if existing_file: - return task_result(True, "Image resource already stored for url {}".format(abbreviate_value(url))) - else: - try: - result = session.get( - url, headers={'Accept': 'application/ld+json, application/json, image/png, image/svg+xml'} - ) - result.raise_for_status() - content_type = result.headers['content-type'] - encoded_body = base64.b64encode(result.content) - data_uri = "data:{};base64,{}".format(content_type, encoded_body) - - except (requests.ConnectionError, requests.HTTPError, KeyError): - return task_result(False, "Could not fetch image at {}".format(url)) - else: - actions.append(store_original_resource(url, data_uri)) - - return task_result(True, "Validated image for node {}".format(abv_node(node_id, node_path)), actions) - - */ - } public static final String ID = ValidationPropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java index 0156d32..896adb6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java @@ -19,6 +19,9 @@ public class ValidationPropertyProbeFactory { if (validation.getType() == ValueType.RDF_TYPE) { return new ValidationRdfTypePropertyProbe(validation, fullValidate); } + if (validation.getType() == ValueType.IMAGE) { + return new ValidationImagePropertyProbe(validation); + } return new ValidationPropertyProbe(validation, fullValidate); } } From 91efffa848090dc0902577b10d1427495817eff9 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 12:45:54 +0100 Subject: [PATCH 45/93] Validation probes moved to its own package --- .../src/main/java/org/oneedtech/inspect/vc/OB20Inspector.java | 4 ++-- .../probe/{ => validation}/ValidationImagePropertyProbe.java | 2 +- .../vc/probe/{ => validation}/ValidationPropertyProbe.java | 4 +++- .../{ => validation}/ValidationPropertyProbeFactory.java | 2 +- .../{ => validation}/ValidationRdfTypePropertyProbe.java | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/{ => validation}/ValidationImagePropertyProbe.java (98%) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/{ => validation}/ValidationPropertyProbe.java (98%) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/{ => validation}/ValidationPropertyProbeFactory.java (94%) rename inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/{ => validation}/ValidationRdfTypePropertyProbe.java (97%) 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 f2c70e5..0b7090c 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 @@ -30,8 +30,8 @@ import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; -import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe; -import org.oneedtech.inspect.vc.probe.ValidationPropertyProbeFactory; +import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbe; +import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.loader.DocumentLoader; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java similarity index 98% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java index d9781ac..d2dfa30 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.probe.validation; import java.util.List; import java.util.regex.Matcher; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java similarity index 98% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index ba242a6..fb5bd1e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.probe.validation; import static org.oneedtech.inspect.vc.Assertion.ValueType.DATA_URI; import static org.oneedtech.inspect.vc.Assertion.ValueType.DATA_URI_OR_URL; @@ -22,6 +22,8 @@ import org.oneedtech.inspect.vc.Assertion.ValueType; import org.oneedtech.inspect.vc.Validation; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.probe.PropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java similarity index 94% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java index 896adb6..195a8ac 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.probe.validation; import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java similarity index 97% rename from inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java rename to inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java index 338820e..8929f79 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationRdfTypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java @@ -1,4 +1,4 @@ -package org.oneedtech.inspect.vc.probe; +package org.oneedtech.inspect.vc.probe.validation; import java.util.List; import java.util.stream.Collectors; From dc0f50177293bd9526226f67d12e609937151c59 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 18:48:17 +0100 Subject: [PATCH 46/93] Gotten the right resource from context --- .../validation/ValidationPropertyProbe.java | 72 +------------------ 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index fb5bd1e..e6aa71c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -111,7 +111,8 @@ public class ValidationPropertyProbe extends PropertyProbe { } // get node from context - JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(childNode.asText()); + UriResource uriResource = resolveUriResource(ctx, childNode.asText()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); if (resolved == null) { if (!validation.isFetch()) { if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) { @@ -124,8 +125,6 @@ public class ValidationPropertyProbe extends PropertyProbe { return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); } else { // fetch - UriResource uriResource = resolveUriResource(ctx, childNode.asText()); - result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { Assertion assertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); @@ -184,72 +183,5 @@ public class ValidationPropertyProbe extends PropertyProbe { return new ReportItems(results); } - private void flattenEmbeddedResource(JsonNode node, RunContext ctx) { - /* - try: - node_id = task_meta['node_id'] - node = get_node_by_id(state, node_id) - prop_name = task_meta['prop_name'] - node_class = task_meta['node_class'] - except (IndexError, KeyError): - raise TaskPrerequisitesError() - - actions = [] - value = node.get(prop_name) - if value is None: - return task_result(True, "Expected property {} was missing in node {}".format(node_id)) - - if isinstance(value, six.string_types): - return task_result( - True, "Property {} referenced from {} is not embedded in need of flattening".format( - prop_name, abv_node(node_id=node_id) - )) - - if not isinstance(value, dict): - return task_result( - False, "Property {} referenced from {} is not a JSON object or string as expected".format( - prop_name, abv_node(node_id=node_id) - )) - embedded_node_id = value.get('id') - - if embedded_node_id is None: - new_node = value.copy() - embedded_node_id = '_:{}'.format(uuid.uuid4()) - new_node['id'] = embedded_node_id - new_node['@context'] = OPENBADGES_CONTEXT_V2_URI - actions.append(add_node(embedded_node_id, data=new_node)) - actions.append(patch_node(node_id, {prop_name: embedded_node_id})) - actions.append(report_message( - 'Node id missing at {}. A blank node ID has been assigned'.format( - abv_node(node_path=[node_id, prop_name], length=64) - ), message_level=MESSAGE_LEVEL_WARNING) - ) - elif not isinstance(embedded_node_id, six.string_types) or not is_iri(embedded_node_id): - return task_result(False, "Embedded JSON object at {} has no proper assigned id.".format( - abv_node(node_path=[node_id, prop_name]))) - - elif node_class == OBClasses.Assertion and not is_url(embedded_node_id): - if not re.match(URN_REGEX, embedded_node_id, re.IGNORECASE): - actions.append(report_message( - 'ID format for {} at {} not in an expected HTTP or URN:UUID scheme'.format( - embedded_node_id, abv_node(node_path=[node_id, prop_name]) - ))) - new_node = value.copy() - new_node['@context'] = OPENBADGES_CONTEXT_V2_URI - actions.append(add_node(embedded_node_id, data=value)) - actions.append(patch_node(node_id, {prop_name: embedded_node_id})) - - else: - actions.append(patch_node(node_id, {prop_name: embedded_node_id})) - if not node_match_exists(state, embedded_node_id) and not filter_tasks( - state, node_id=embedded_node_id, task_type=FETCH_HTTP_NODE): - # fetch - actions.append(add_task(FETCH_HTTP_NODE, url=embedded_node_id)) - - return task_result(True, "Embedded {} node in {} queued for storage and/or refetching as needed", actions) - - */ - } - public static final String ID = ValidationPropertyProbe.class.getSimpleName(); } From 92ad87c6bcbc317fb7988b7fcb90b272f3afc363 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 18:48:56 +0100 Subject: [PATCH 47/93] Several ways to get the id --- .../inspect/vc/jsonld/probe/JsonLDCompactionProve.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index 9a7bc6f..40a90d8 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -6,6 +6,7 @@ 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.util.resource.Resource; import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; @@ -51,7 +52,14 @@ public class JsonLDCompactionProve extends Probe { } public static String getId(Credential crd) { - return "json-ld-compact:" + crd.getResource().getID(); + return getId(crd.getResource()); + } + + public static String getId(Resource resource) { + return getId(resource.getID()); + } + public static String getId(String id) { + return "json-ld-compact:" + id; } public static final String ID = JsonLDCompactionProve.class.getSimpleName(); From d6e0d85d2ff24d4f1ac532baed5514504c0f70c2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 18:49:31 +0100 Subject: [PATCH 48/93] Added issuer property validations --- .../org/oneedtech/inspect/vc/Assertion.java | 10 ++++-- .../org/oneedtech/inspect/vc/Validation.java | 19 +++++++++- .../ValidationIssuerPropertyProbe.java | 35 +++++++++++++++++++ .../ValidationPropertyProbeFactory.java | 5 ++- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 74125f3..1febb31 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -9,6 +9,7 @@ import java.util.function.Function; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.vc.Validation.MessageLevel; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; @@ -148,7 +149,8 @@ public class Assertion extends Credential { URL(PrimitiveValueValidator::validateUrl), URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority), - IMAGE(null); + IMAGE(null), + ISSUER(null); private final Function validationFunction; @@ -264,7 +266,8 @@ public class Assertion extends Credential { new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), + new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build() )) .put(Type.Profile, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), @@ -276,7 +279,8 @@ public class Assertion extends Credential { new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), + new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build() )) .put(Type.RevocationList, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.RevocationList)).build(), diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java index 8ecd22c..42a0b6f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java @@ -20,6 +20,7 @@ public class Validation { private final boolean fetch; private final String defaultType; private final boolean fullValidate; + private MessageLevel messageLevel; public Validation(Builder builder) { this.name = builder.name; @@ -34,9 +35,9 @@ public class Validation { this.fetch = builder.fetch; this.defaultType = builder.defaultType; this.fullValidate = builder.fullValidate; + this.messageLevel = builder.messageLevel; } - public String getName() { return name; } @@ -85,6 +86,15 @@ public class Validation { return fullValidate; } + public MessageLevel getMessageLevel() { + return messageLevel; + } + + public enum MessageLevel { + Warning, + Error + } + public static class Builder { private String name; private Assertion.ValueType type; @@ -98,11 +108,13 @@ public class Validation { private boolean fetch; private String defaultType; private boolean fullValidate; + private MessageLevel messageLevel; public Builder() { this.mustContainOne = new ArrayList<>(); this.prerequisites = new ArrayList<>(); this.expectedTypes = new ArrayList<>(); + this.messageLevel = MessageLevel.Error; } public Builder name(String name) { @@ -184,6 +196,11 @@ public class Validation { return this; } + public Builder messageLevel(MessageLevel messageLevel) { + this.messageLevel = messageLevel; + return this; + } + public Validation build() { return new Validation(this); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java new file mode 100644 index 0000000..789c750 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java @@ -0,0 +1,35 @@ +package org.oneedtech.inspect.vc.probe.validation; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.Validation.MessageLevel; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe { + + public ValidationIssuerPropertyProbe(Validation validation) { + super(validation); + } + + public ValidationIssuerPropertyProbe(Validation validation, boolean fullValidate) { + super(validation, fullValidate); + } + + @Override + protected ReportItems validate(JsonNode node, RunContext ctx) { + if (!node.asText().matches("^http(s)?://")) { + return buildResponse("Issuer Profile " + node.toString() + " not hosted with HTTP-based identifier." + + "Many platforms can only handle HTTP(s)-hosted issuers.", ctx); + } + return success(ctx); + } + + private ReportItems buildResponse(String msg, RunContext ctx) { + if (validation.getMessageLevel() == MessageLevel.Warning) { + return warning(msg, ctx); + } + return error(msg, ctx); + } +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java index 195a8ac..243aa37 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java @@ -2,8 +2,8 @@ package org.oneedtech.inspect.vc.probe.validation; import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; -import org.oneedtech.inspect.vc.Validation; import org.oneedtech.inspect.vc.Assertion.ValueType; +import org.oneedtech.inspect.vc.Validation; /** * Factory for ValidationPropertyProbes @@ -22,6 +22,9 @@ public class ValidationPropertyProbeFactory { if (validation.getType() == ValueType.IMAGE) { return new ValidationImagePropertyProbe(validation); } + if (validation.getType() == ValueType.ISSUER) { + return new ValidationIssuerPropertyProbe(validation); + } return new ValidationPropertyProbe(validation, fullValidate); } } From d604f213beb989e8fb4fdf631a6aface68242ae0 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 18:50:01 +0100 Subject: [PATCH 49/93] Added test with non-http issuer --- .../org/oneedtech/inspect/vc/OB20Tests.java | 28 ++- .../org/oneedtech/inspect/vc/Samples.java | 2 + .../resources/ob20/assets/altbadgeurl.json | 2 +- .../resources/ob20/assets/bad-issuer.json | 206 ++++++++++++++++++ .../ob20/assets/badge-with-bad-issuer.json | 10 + .../resources/ob20/assets/badgeclass1.json | 2 +- .../resources/ob20/assets/badgecriteria.json | 3 + .../ob20/warning-issuer-non-http.json | 17 ++ 8 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/bad-issuer.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badge-with-bad-issuer.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgecriteria.json create mode 100644 inspector-vc/src/test/resources/ob20/warning-issuer-non-http.json 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 72ab6ff..ff79985 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 @@ -9,6 +9,7 @@ import static org.oneedtech.inspect.test.Assertions.assertWarning; import java.net.URI; import java.net.URISyntaxException; +import java.util.List; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -26,10 +27,11 @@ public class OB20Tests { @BeforeAll static void setup() throws URISyntaxException { - validator = new TestBuilder() - .add(new URI("https://www.example.org/"), "ob20/assets") - .add(new URI("https://example.org/"), "ob20/assets") - .add(new URI("http://example.org/"), "ob20/assets") + TestBuilder builder = new TestBuilder(); + for (String localDomain : localDomains) { + builder.add(new URI(localDomain), "ob20/assets"); + } + validator = builder .set(Behavior.TEST_INCLUDE_SUCCESS, true) .set(Behavior.TEST_INCLUDE_WARNINGS, false) .set(Behavior.VALIDATOR_FAIL_FAST, true) @@ -93,8 +95,11 @@ public class OB20Tests { static class WarningTests { @BeforeAll static void setup() throws URISyntaxException { - validator = new TestBuilder() - .add(new URI("https://www.example.org/"), "ob20/assets") + TestBuilder builder = new TestBuilder(); + for (String localDomain : localDomains) { + builder.add(new URI(localDomain), "ob20/assets"); + } + validator = builder .set(Behavior.TEST_INCLUDE_SUCCESS, true) .set(Behavior.TEST_INCLUDE_WARNINGS, true) .set(Behavior.VALIDATOR_FAIL_FAST, true) @@ -110,5 +115,16 @@ public class OB20Tests { assertWarning(report); }); } + + @Test + void testWarningIssuerNonHttps() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.WARNING_ISSUER_NON_HTTPS_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertWarning(report); + }); + } } + + private static final List localDomains = List.of("https://www.example.org/", "https://example.org/", "http://example.org/"); } 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 6f3f861..a07ff01 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 @@ -49,6 +49,8 @@ public class Samples { public final static Sample SIMPLE_ASSERTION_INVALID_TYPE_JSON = new Sample("ob20/basic-assertion-invalid-type.json", true); // original: test_graph: test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); + // original: test_validation: test_issuer_warn_on_non_https_id + public final static Sample WARNING_ISSUER_NON_HTTPS_JSON = new Sample("ob20/warning-issuer-non-http.json", true); // original: test_validation: test_can_input_badgeclass public final static Sample SIMPLE_BADGECLASS = new Sample("ob20/assets/badgeclass1.json", true); } diff --git a/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json b/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json index 3554627..588b5a5 100644 --- a/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json +++ b/inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json @@ -5,6 +5,6 @@ "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/robotics-badge.html", + "criteria": "http://example.com/badgecriteria.json", "issuer": "https://example.org/organization.json" } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/bad-issuer.json b/inspector-vc/src/test/resources/ob20/assets/bad-issuer.json new file mode 100644 index 0000000..d035c87 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/bad-issuer.json @@ -0,0 +1,206 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "urn:uuid:2d391246-6e0d-4dab-906c-b29770bd7aa6", + "type": "Issuer", + "url": "http://example.com", + "email": "email@example.org", + "name": "some Issuer" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badge-with-bad-issuer.json b/inspector-vc/src/test/resources/ob20/assets/badge-with-bad-issuer.json new file mode 100644 index 0000000..f99c558 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badge-with-bad-issuer.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "http://example.org/badge-with-bad-issuer.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": "http://example.com/badgecriteria.json", + "issuer": "https://example.org/bad-issuer.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json index 55b5b83..f0f79b2 100644 --- a/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json @@ -202,7 +202,7 @@ "type": "BadgeClass", "name": "Example Badge", "description": "An example", - "criteria": "http://example.org/criteria", + "criteria": "http://example.com/badgecriteria.json", "issuer": "http://example.org/issuer1", "image": "http://example.org/robotics-badge.png" } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgecriteria.json b/inspector-vc/src/test/resources/ob20/assets/badgecriteria.json new file mode 100644 index 0000000..715c42f --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgecriteria.json @@ -0,0 +1,3 @@ +{ + "narrative": "Do the important things." +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/warning-issuer-non-http.json b/inspector-vc/src/test/resources/ob20/warning-issuer-non-http.json new file mode 100644 index 0000000..b48a321 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/warning-issuer-non-http.json @@ -0,0 +1,17 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/warning-issuer-non-http.json", + "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": "http://example.org/badge-with-bad-issuer.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From c53d6405c44132c6ccef039bfa38353cb75d9a28 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 1 Dec 2022 12:30:01 +0100 Subject: [PATCH 50/93] Cache context for faster tests --- .../vc/util/CachingDocumentLoader.java | 1 + .../src/main/resources/contexts/obv2x.jsonld | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 inspector-vc/src/main/resources/contexts/obv2x.jsonld diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 937db8b..961e6cb 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -109,6 +109,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .put("https://w3id.org/security/suites/x25519-2019/v1", Resources.getResource("contexts/suites-x25519-2019.jsonld")) .put("https://w3id.org/security/suites/jws-2020/v1", Resources.getResource("contexts/suites-jws-2020.jsonld")) .put("https://openbadgespec.org/v2/context.json", Resources.getResource("contexts/ob-v2p0.json")) + .put("https://w3id.org/openbadges/v2", Resources.getResource("contexts/obv2x.jsonld")) .build(); diff --git a/inspector-vc/src/main/resources/contexts/obv2x.jsonld b/inspector-vc/src/main/resources/contexts/obv2x.jsonld new file mode 100644 index 0000000..84c3f37 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/obv2x.jsonld @@ -0,0 +1,91 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + + "author": { "@id": "schema:author", "@type": "@id" }, + "caption": { "@id": "schema:caption" }, + "claim": {"@id": "cred:claim", "@type": "@id"}, + "created": { "@id": "dc:created", "@type": "xsd:dateTime" }, + "creator": { "@id": "dc:creator", "@type": "@id" }, + "description": { "@id": "schema:description" }, + "email": { "@id": "schema:email" }, + "endorsement": {"@id": "cred:credential", "@type": "@id"}, + "expires": { "@id": "sec:expiration", "@type": "xsd:dateTime" }, + "genre": { "@id": "schema:genre" }, + "image": { "@id": "schema:image", "@type": "@id" }, + "name": { "@id": "schema:name" }, + "owner": {"@id": "sec:owner", "@type": "@id"}, + "publicKey": { "@id": "sec:publicKey", "@type": "@id" }, + "publicKeyPem": { "@id": "sec:publicKeyPem" }, + "related": { "@id": "dc:relation", "@type": "@id" }, + "startsWith": { "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" }, + "tags": { "@id": "schema:keywords" }, + "targetDescription": { "@id": "schema:targetDescription" }, + "targetFramework": { "@id": "schema:targetFramework" }, + "targetName": { "@id": "schema:targetName" }, + "targetUrl": { "@id": "schema:targetUrl" }, + "telephone": { "@id": "schema:telephone" }, + "url": { "@id": "schema:url", "@type": "@id" }, + "version": { "@id": "schema:version" }, + + "alignment": { "@id": "obi:alignment", "@type": "@id" }, + "allowedOrigins": { "@id": "obi:allowedOrigins" }, + "audience": { "@id": "obi:audience" }, + "badge": { "@id": "obi:badge", "@type": "@id" }, + "criteria": { "@id": "obi:criteria", "@type": "@id" }, + "endorsementComment": { "@id": "obi:endorsementComment" }, + "evidence": { "@id": "obi:evidence", "@type": "@id" }, + "hashed": { "@id": "obi:hashed", "@type": "xsd:boolean" }, + "identity": { "@id": "obi:identityHash" }, + "issuedOn": { "@id": "obi:issueDate", "@type": "xsd:dateTime" }, + "issuer": { "@id": "obi:issuer", "@type": "@id" }, + "narrative": { "@id": "obi:narrative" }, + "recipient": { "@id": "obi:recipient", "@type": "@id" }, + "revocationList": { "@id": "obi:revocationList", "@type": "@id" }, + "revocationReason": { "@id": "obi:revocationReason" }, + "revoked": { "@id": "obi:revoked", "@type": "xsd:boolean" }, + "revokedAssertions": { "@id": "obi:revoked" }, + "salt": { "@id": "obi:salt" }, + "targetCode": { "@id": "obi:targetCode" }, + "uid": { "@id": "obi:uid" }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { "@id": "obi:verify", "@type": "@id" }, + "verificationProperty": { "@id": "obi:verificationProperty" }, + "verify": "verification" + } +} From 739facfe064a566967162fb91f2d8c3eb5377a56 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 1 Dec 2022 12:30:09 +0100 Subject: [PATCH 51/93] Fixed tests --- .../resources/ob20/assets/robotics-badge.json | 2 +- .../test/resources/ob20/basic-assertion.json | 1 - .../src/test/resources/ob20/simple-badge.png | Bin 1302 -> 1247 bytes 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json b/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json index 3554627..118fae0 100644 --- a/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json +++ b/inspector-vc/src/test/resources/ob20/assets/robotics-badge.json @@ -5,6 +5,6 @@ "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/robotics-badge.html", + "criteria": "https://example.org/badgecriteria.json", "issuer": "https://example.org/organization.json" } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion.json b/inspector-vc/src/test/resources/ob20/basic-assertion.json index 69963d6..6811b7b 100644 --- a/inspector-vc/src/test/resources/ob20/basic-assertion.json +++ b/inspector-vc/src/test/resources/ob20/basic-assertion.json @@ -9,7 +9,6 @@ "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" }, "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", "verification": { diff --git a/inspector-vc/src/test/resources/ob20/simple-badge.png b/inspector-vc/src/test/resources/ob20/simple-badge.png index ea3669368b6926975ae5804b4a6957f851655965..b1c74938a90c2d93cccec8c75d7462c3a2cdaa8b 100644 GIT binary patch delta 26 icmbQnb)R#B3gfk&+g$V$EdkIDW delta 49 zcmcc5IgM+A3gg#}s(y?dsb!fdsd>q%6RVZDbj$OLvh^}ba&smZFqX44yJl|Q{F?C# F69B+L5~ctE From ce89968837727ed18bbd961c11e7001c0fe113d3 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 1 Dec 2022 12:30:34 +0100 Subject: [PATCH 52/93] generic IssuanceProbe --- .../org/oneedtech/inspect/vc/Assertion.java | 7 +- .../org/oneedtech/inspect/vc/Credential.java | 8 +- .../inspect/vc/EndorsementInspector.java | 2 +- .../oneedtech/inspect/vc/OB30Inspector.java | 2 +- .../inspect/vc/VerifiableCredential.java | 7 +- .../inspect/vc/probe/ExpirationProbe.java | 6 +- .../inspect/vc/probe/IssuanceProbe.java | 8 +- .../inspect/vc/probe/RevocationListProbe.java | 5 +- ...nFlattenEmbeddedResourcePropertyProbe.java | 94 +++++++++++++++++++ 9 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 1febb31..1892c4f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -26,8 +26,8 @@ public class Assertion extends Credential { final Assertion.Type assertionType; - protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas) { - super(resource.getID(), resource, data, jwt, schemas); + protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { + super(resource.getID(), resource, data, jwt, schemas, issuedOnPropertyName); JsonNode typeNode = jsonData.get("type"); this.assertionType = Assertion.Type.valueOf(typeNode); @@ -60,7 +60,7 @@ public class Assertion extends Credential { public Assertion build() { // transform key of schemas map to string because the type of the key in the base map is generic // and our specific key is an Enum - return new Assertion(getResource(), getJsonData(), getJwt(), schemas); + return new Assertion(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME); } } @@ -300,4 +300,5 @@ public class Assertion extends Credential { .build(); public static final String ID = Assertion.class.getCanonicalName(); + private static final String ISSUED_ON_PROPERTY_NAME = "issuedOn"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 83c19b8..9c7701e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -29,14 +29,16 @@ public abstract class Credential extends GeneratedObject { final Resource resource; final JsonNode jsonData; final String jwt; + final String issuedOnPropertyName; final Map schemas; - protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas) { + protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { super(id, GeneratedObject.Type.INTERNAL); this.resource = checkNotNull(resource); this.jsonData = checkNotNull(data); this.jwt = jwt; //may be null this.schemas = schemas; + this.issuedOnPropertyName = issuedOnPropertyName; checkTrue(RECOGNIZED_PAYLOAD_TYPES.contains(resource.getType())); } @@ -53,6 +55,10 @@ public abstract class Credential extends GeneratedObject { return Optional.ofNullable(jwt); } + public String getIssuedOnPropertyName() { + return issuedOnPropertyName; + } + /** * Get the canonical schema for this credential if such exists. */ diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index 6879e79..e21b34f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -111,7 +111,7 @@ public class EndorsementInspector extends VCInspector implements SubInspector { } //revocation, expiration and issuance - for(Probe probe : List.of(new RevocationListProbe(), + for(Probe probe : List.of(new RevocationListProbe(), new ExpirationProbe(), new IssuanceProbe())) { probeCount++; accumulator.add(probe.run(endorsement, ctx)); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 51b48c6..9c29467 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -194,7 +194,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { } //revocation, expiration and issuance - for(Probe probe : List.of(new RevocationListProbe(), + for(Probe probe : List.of(new RevocationListProbe(), new ExpirationProbe(), new IssuanceProbe())) { probeCount++; accumulator.add(probe.run(ob, ctx)); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index e062432..bd3985c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -28,8 +28,8 @@ import com.google.common.collect.ImmutableMap; public class VerifiableCredential extends Credential { final VerifiableCredential.Type credentialType; - protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas) { - super(ID, resource, data, jwt, schemas); + protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { + super(ID, resource, data, jwt, schemas, issuedOnPropertyName); JsonNode typeNode = jsonData.get("type"); this.credentialType = VerifiableCredential.Type.valueOf(typeNode); @@ -133,9 +133,10 @@ public class VerifiableCredential extends Credential { public static class Builder extends Credential.Builder { @Override public VerifiableCredential build() { - return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas); + return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME); } } public static final String ID = VerifiableCredential.class.getCanonicalName(); + private static final String ISSUED_ON_PROPERTY_NAME = "issuanceDate"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java index 0c9092c..b900ad4 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java @@ -5,7 +5,7 @@ import java.time.ZonedDateTime; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; @@ -13,14 +13,14 @@ import com.fasterxml.jackson.databind.JsonNode; * A Probe that verifies a credential's expiration status * @author mgylling */ -public class ExpirationProbe extends Probe { +public class ExpirationProbe extends Probe { public ExpirationProbe() { super(ID); } @Override - public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { + public ReportItems run(Credential crd, RunContext ctx) throws Exception { /* * If the AchievementCredential or EndorsementCredential has an “expirationDate” property * and the expiration date is prior to the current date, the credential has expired. diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java index 7b693ff..2328199 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java @@ -5,7 +5,7 @@ import java.time.ZonedDateTime; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.Credential; import com.fasterxml.jackson.databind.JsonNode; @@ -13,19 +13,19 @@ import com.fasterxml.jackson.databind.JsonNode; * A Probe that verifies a credential's issuance status * @author mgylling */ -public class IssuanceProbe extends Probe { +public class IssuanceProbe extends Probe { public IssuanceProbe() { super(ID); } @Override - public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { + public ReportItems run(Credential crd, RunContext ctx) throws Exception { /* * If the AchievementCredential or EndorsementCredential “issuanceDate” * property after the current date, the credential is not yet valid. */ - JsonNode node = crd.getJson().get("issuanceDate"); + JsonNode node = crd.getJson().get(crd.getIssuedOnPropertyName()); if(node != null) { try { ZonedDateTime issuanceDate = ZonedDateTime.parse(node.textValue()); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java index 576c6e4..e083c42 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java @@ -10,6 +10,7 @@ import java.util.List; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -20,14 +21,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; * A Probe that verifies a credential's revocation status. * @author mgylling */ -public class RevocationListProbe extends Probe { +public class RevocationListProbe extends Probe { public RevocationListProbe() { super(ID); } @Override - public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { + public ReportItems run(Credential crd, RunContext ctx) throws Exception { /* * If the AchievementCredential or EndorsementCredential has a “credentialStatus” property diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java new file mode 100644 index 0000000..604f39a --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java @@ -0,0 +1,94 @@ +package org.oneedtech.inspect.vc.probe.validation; + +import java.util.UUID; + +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.util.resource.UriResource; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.google.common.io.Resources; + +public class ValidationFlattenEmbeddedResourcePropertyProbe extends ValidationPropertyProbe { + + public ValidationFlattenEmbeddedResourcePropertyProbe(Validation validation) { + super(validation); + } + + public ValidationFlattenEmbeddedResourcePropertyProbe(Validation validation, boolean fullValidate) { + super(validation, fullValidate); + } + + @Override + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { + return notRun("Expected property " + validation.getName() + " was missing in node " + node.toString(), ctx); + } + + @Override + protected ReportItems validate(JsonNode node, RunContext ctx) { + try { + UriResource uriResource = resolveUriResource(ctx, node.asText()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode fetchedNode = mapper.readTree(resolved.getJson()); + + if (fetchedNode.isTextual()) { + return notRun("Property " + validation.getName() + " referenced from " + node.toString() + " is not embedded in need of flattening", ctx); + } + + if (!fetchedNode.isObject()) { + return error("Property " + validation.getName() + " referenced from " + node.toString() + " is not a JSON object or string as expected", ctx); + } + + JsonNode idNode = fetchedNode.get("id"); + if (idNode == null) { + // add a new node to the graph + JsonNode newNode = mapper.readTree(Resources.getResource("contexts/ob-v2p0.json")); + ObjectReader readerForUpdating = mapper.readerForUpdating(newNode); + UUID newId = UUID.randomUUID(); + JsonNode merged = readerForUpdating.readValue("{\"id\": \"_:" + newId + "\"}"); + ctx.addGeneratedObject(new JsonLdGeneratedObject(JsonLDCompactionProve.getId(newId.toString()), merged.toString())); + + return warning("Node id missing at " + node.toString() + ". A blank node ID has been assigned", ctx); + } else if (!idNode.isTextual() && !PrimitiveValueValidator.validateIri(idNode)) { + return error("Embedded JSON object at " + node.asText() + " has no proper assigned id.", ctx); + } else if (/*node_class == Assertion && */ !PrimitiveValueValidator.validateUrl(idNode)) { + /* + if not re.match(URN_REGEX, embedded_node_id, re.IGNORECASE): + actions.append(report_message( + 'ID format for {} at {} not in an expected HTTP or URN:UUID scheme'.format( + embedded_node_id, abv_node(node_path=[node_id, prop_name]) + ))) + new_node = value.copy() + new_node['@context'] = OPENBADGES_CONTEXT_V2_URI + actions.append(add_node(embedded_node_id, data=value)) + actions.append(patch_node(node_id, {prop_name: embedded_node_id})) + */ + + } else { + + /* + actions.append(patch_node(node_id, {prop_name: embedded_node_id})) + + if not node_match_exists(state, embedded_node_id) and not filter_tasks( + state, node_id=embedded_node_id, task_type=FETCH_HTTP_NODE): + # fetch + actions.append(add_task(FETCH_HTTP_NODE, url=embedded_node_id)) + */ + } + } catch (Throwable t) { + return fatal(t.getMessage(), ctx); + } + + return success(ctx); + } + + +} From d334c0f1430fa5bd59c847195ebb529c2a1c71b3 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 1 Dec 2022 12:34:36 +0100 Subject: [PATCH 53/93] Generic ExpirationProbe --- .../src/main/java/org/oneedtech/inspect/vc/Assertion.java | 7 ++++--- .../main/java/org/oneedtech/inspect/vc/Credential.java | 8 +++++++- .../org/oneedtech/inspect/vc/VerifiableCredential.java | 7 ++++--- .../org/oneedtech/inspect/vc/probe/ExpirationProbe.java | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 1892c4f..10c4144 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -26,8 +26,8 @@ public class Assertion extends Credential { final Assertion.Type assertionType; - protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { - super(resource.getID(), resource, data, jwt, schemas, issuedOnPropertyName); + protected Assertion(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName, String expiresAtPropertyName) { + super(resource.getID(), resource, data, jwt, schemas, issuedOnPropertyName, expiresAtPropertyName); JsonNode typeNode = jsonData.get("type"); this.assertionType = Assertion.Type.valueOf(typeNode); @@ -60,7 +60,7 @@ public class Assertion extends Credential { public Assertion build() { // transform key of schemas map to string because the type of the key in the base map is generic // and our specific key is an Enum - return new Assertion(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME); + return new Assertion(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME, EXPIRES_AT_PROPERTY_NAME); } } @@ -301,4 +301,5 @@ public class Assertion extends Credential { public static final String ID = Assertion.class.getCanonicalName(); private static final String ISSUED_ON_PROPERTY_NAME = "issuedOn"; + private static final String EXPIRES_AT_PROPERTY_NAME = "expires"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 9c7701e..c7ba710 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -30,15 +30,17 @@ public abstract class Credential extends GeneratedObject { final JsonNode jsonData; final String jwt; final String issuedOnPropertyName; + final String expiresAtPropertyName; final Map schemas; - protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { + protected Credential(String id, Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName, String expiresAtPropertyName) { super(id, GeneratedObject.Type.INTERNAL); this.resource = checkNotNull(resource); this.jsonData = checkNotNull(data); this.jwt = jwt; //may be null this.schemas = schemas; this.issuedOnPropertyName = issuedOnPropertyName; + this.expiresAtPropertyName = expiresAtPropertyName; checkTrue(RECOGNIZED_PAYLOAD_TYPES.contains(resource.getType())); } @@ -59,6 +61,10 @@ public abstract class Credential extends GeneratedObject { return issuedOnPropertyName; } + public String getExpiresAtPropertyName() { + return expiresAtPropertyName; + } + /** * Get the canonical schema for this credential if such exists. */ diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index bd3985c..6077164 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -28,8 +28,8 @@ import com.google.common.collect.ImmutableMap; public class VerifiableCredential extends Credential { final VerifiableCredential.Type credentialType; - protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName) { - super(ID, resource, data, jwt, schemas, issuedOnPropertyName); + protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map schemas, String issuedOnPropertyName, String expiresAtPropertyName) { + super(ID, resource, data, jwt, schemas, issuedOnPropertyName, expiresAtPropertyName); JsonNode typeNode = jsonData.get("type"); this.credentialType = VerifiableCredential.Type.valueOf(typeNode); @@ -133,10 +133,11 @@ public class VerifiableCredential extends Credential { public static class Builder extends Credential.Builder { @Override public VerifiableCredential build() { - return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME); + return new VerifiableCredential(getResource(), getJsonData(), getJwt(), schemas, ISSUED_ON_PROPERTY_NAME, EXPIRES_AT_PROPERTY_NAME); } } public static final String ID = VerifiableCredential.class.getCanonicalName(); private static final String ISSUED_ON_PROPERTY_NAME = "issuanceDate"; + private static final String EXPIRES_AT_PROPERTY_NAME = "expirationDate"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java index b900ad4..01e6fb0 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java @@ -25,7 +25,7 @@ public class ExpirationProbe extends Probe { * If the AchievementCredential or EndorsementCredential has an “expirationDate” property * and the expiration date is prior to the current date, the credential has expired. */ - JsonNode node = crd.getJson().get("expirationDate"); + JsonNode node = crd.getJson().get(crd.getExpiresAtPropertyName()); if(node != null) { try { ZonedDateTime expirationDate = ZonedDateTime.parse(node.textValue()); From d286347294b495948ed959834461bee84ef22e88 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 1 Dec 2022 12:38:20 +0100 Subject: [PATCH 54/93] Added issuedon and expiration probes --- .../java/org/oneedtech/inspect/vc/OB20Inspector.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 0b7090c..c1833fc 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 @@ -29,6 +29,8 @@ import org.oneedtech.inspect.vc.payload.PngParser; import org.oneedtech.inspect.vc.payload.SvgParser; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.probe.ExpirationProbe; +import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; @@ -129,6 +131,14 @@ 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); + } + // Each Badge Object contains all required properties for its class // This could be done validating with the schema, but seems that there are some error on that file // So, we do a manual Probe for the nodes From 550dde03f637555b66ba5d2a1dbcc282b4685b4b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 2 Dec 2022 11:28:24 +0100 Subject: [PATCH 55/93] Generic property name for credential into jws --- .../org/oneedtech/inspect/vc/OB30Inspector.java | 1 + .../oneedtech/inspect/vc/VerifiableCredential.java | 1 + .../inspect/vc/payload/PayloadParser.java | 14 ++++++++++---- .../inspect/vc/credential/PayloadParserTests.java | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 9c29467..163b9ec 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -96,6 +96,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { .put(Key.GENERATED_OBJECT_BUILDER, new VerifiableCredential.Builder()) .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) + .put(Key.JWT_CREDENTIAL_NODE_NAME, VerifiableCredential.JWT_NODE_NAME) .build(); List accumulator = new ArrayList<>(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index 6077164..ba20cc2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -140,4 +140,5 @@ public class VerifiableCredential extends Credential { public static final String ID = VerifiableCredential.class.getCanonicalName(); private static final String ISSUED_ON_PROPERTY_NAME = "issuanceDate"; private static final String EXPIRES_AT_PROPERTY_NAME = "expirationDate"; + public static final String JWT_NODE_NAME = "vc"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java index 78bc293..5ffd4c1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java @@ -1,14 +1,16 @@ package org.oneedtech.inspect.vc.payload; +import static com.apicatalog.jsonld.StringUtils.isBlank; + import java.util.Base64; -import java.util.List; import java.util.Base64.Decoder; +import java.util.List; import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.RunContext.Key; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.vc.Credential; -import org.oneedtech.inspect.vc.VerifiableCredential; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -48,9 +50,13 @@ public abstract class PayloadParser { */ String jwtPayload = new String(decoder.decode(parts.get(1))); - //Deserialize and fetch the 'vc' node from the object + //Deserialize and fetch the credential node from the object JsonNode outerPayload = fromString(jwtPayload, context); - JsonNode vcNode = outerPayload.get("vc"); + String nodeName = (String) context.get(Key.JWT_CREDENTIAL_NODE_NAME); + if (isBlank(nodeName)) { + return outerPayload; + } + JsonNode vcNode = outerPayload.get(nodeName); return vcNode; } diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java index 975721c..d0893e2 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java @@ -119,6 +119,7 @@ public class PayloadParserTests { .put(Key.GENERATED_OBJECT_BUILDER, new VerifiableCredential.Builder()) .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB30) .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB30) + .put(Key.JWT_CREDENTIAL_NODE_NAME, VerifiableCredential.JWT_NODE_NAME) .build(); } } From 2492aa28f3d0eb254df7987e59221fd6d5c1cfb2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 2 Dec 2022 11:29:00 +0100 Subject: [PATCH 56/93] Set assertion property name for JWS --- .../org/oneedtech/inspect/vc/Assertion.java | 1 + .../oneedtech/inspect/vc/OB20Inspector.java | 25 ++++++------------- .../org/oneedtech/inspect/vc/OB20Tests.java | 9 ++++++- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 10c4144..7410903 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -302,4 +302,5 @@ public class Assertion extends Credential { public static final String ID = Assertion.class.getCanonicalName(); private static final String ISSUED_ON_PROPERTY_NAME = "issuedOn"; private static final String EXPIRES_AT_PROPERTY_NAME = "expires"; + public static final String JWT_NODE_NAME = ""; // empty because the whole payload is the assertion } 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 c1833fc..0f6a406 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 @@ -32,7 +32,6 @@ import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; -import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; @@ -91,6 +90,7 @@ public class OB20Inspector extends Inspector { .put(Key.PNG_CREDENTIAL_KEY, PngParser.Keys.OB20) .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) .put(Key.JSON_DOCUMENT_LOADER, documentLoader) + .put(Key.JWT_CREDENTIAL_NODE_NAME, Assertion.JWT_NODE_NAME) .build(); List accumulator = new ArrayList<>(); @@ -122,7 +122,11 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - // Validates the Open Badge, from the compacted form + + // Each Badge Object contains all required properties for its class + // This could be done validating with the schema, but seems that there are some error on that file + // So, we do a manual Probe for the nodes. + // Also, we validate the Open Badge, from the compacted form JsonNode assertionNode = mapper.readTree(jsonLdGeneratedObject.getJson()); List validations = assertion.getValidations(); for (Validation validation : validations) { @@ -131,28 +135,13 @@ public class OB20Inspector extends Inspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - //expiration and issuance + // 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); } - - // Each Badge Object contains all required properties for its class - // This could be done validating with the schema, but seems that there are some error on that file - // So, we do a manual Probe for the nodes - - // accumulator.add(new RequiredFieldsProbe(jsonLdGeneratedObject).run(assertion, ctx)); - // if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - - //canonical schema and inline schemata - // SchemaKey schema = assertion.getSchemaKey().orElseThrow(); - // for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { - // probeCount++; - // accumulator.add(probe.run(assertion.getJson(), ctx)); - // if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - // } } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); } 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 ff79985..1e13614 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 @@ -89,7 +89,14 @@ public class OB20Tests { }); } - + @Test + void testSimpleJWTValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JWT.SIMPLE_JWT.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } @Nested static class WarningTests { From 746300ab425f3d7f07ea952a908e6d60ca9b90f1 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 2 Dec 2022 11:29:09 +0100 Subject: [PATCH 57/93] Added basic jws test --- .../src/test/java/org/oneedtech/inspect/vc/Samples.java | 4 ++++ .../src/test/resources/ob20/assets/beths-robot-work.html | 0 inspector-vc/src/test/resources/ob20/assets/key1.json | 7 +++++++ .../src/test/resources/ob20/assets/organization.json | 3 ++- inspector-vc/src/test/resources/ob20/basic-assertion.json | 1 + inspector-vc/src/test/resources/ob20/simple.jwt | 1 + inspector-vc/src/test/resources/ob30/simple.jwt | 2 +- 7 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/beths-robot-work.html create mode 100644 inspector-vc/src/test/resources/ob20/assets/key1.json create mode 100644 inspector-vc/src/test/resources/ob20/simple.jwt 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 a07ff01..6de1cba 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 @@ -59,5 +59,9 @@ public class Samples { // original: test_verify: test_verify_of_baked_image public final static Sample SIMPLE_JSON_PNG = new Sample("ob20/simple-badge.png", true); } + + public static final class JWT { + public final static Sample SIMPLE_JWT = new Sample("ob20/simple.jwt", true); + } } } diff --git a/inspector-vc/src/test/resources/ob20/assets/beths-robot-work.html b/inspector-vc/src/test/resources/ob20/assets/beths-robot-work.html new file mode 100644 index 0000000..e69de29 diff --git a/inspector-vc/src/test/resources/ob20/assets/key1.json b/inspector-vc/src/test/resources/ob20/assets/key1.json new file mode 100644 index 0000000..b37cf80 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/key1.json @@ -0,0 +1,7 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/key1.json", + "type": "CryptographicKey", + "owner": "https://example.org/organization.json", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq/tyy5CGrCVfGIMrMkVq\nZ5cAKkbLdrNrNFvXNMSazvKj/9VmuALZOjqXl1Od/Ku6q+18sn70p6r25kGRoaU/\nAJNLt36EnRSs9I66xZ3tOY4c9PeWpMC1XbCrUBh1rpx7XQ5wULOMbttEvBf9V2Cl\nCGiNqwqcafrQHoYteAtF4d2zcjNm+xZRQmnVU0CdvXSXeniWjqyif/751/M/xb93\nfeF1AIFS5IGaI1cWca7BG2izKz1DjZxbMlvQVZk1Axz2Uj/FXssyBKmAdCw4EjuF\nqinQBmKwmFvCB3yhMAK6+f1k4hsR/ET/PxwkNQZfu+gkmEAQoYAMSPgGS3xY3LVf\nxwIDAQAB\n-----END PUBLIC KEY-----" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/organization.json b/inspector-vc/src/test/resources/ob20/assets/organization.json index 559a120..1ae00aa 100644 --- a/inspector-vc/src/test/resources/ob20/assets/organization.json +++ b/inspector-vc/src/test/resources/ob20/assets/organization.json @@ -4,5 +4,6 @@ "id": "https://example.org/organization.json", "name": "An Example Badge Issuer", "url": "https://example.org", - "email": "contact@example.org" + "email": "contact@example.org", + "publicKey": "http://example.org/key1.json" } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion.json b/inspector-vc/src/test/resources/ob20/basic-assertion.json index 6811b7b..69963d6 100644 --- a/inspector-vc/src/test/resources/ob20/basic-assertion.json +++ b/inspector-vc/src/test/resources/ob20/basic-assertion.json @@ -9,6 +9,7 @@ "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" }, "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", "verification": { diff --git a/inspector-vc/src/test/resources/ob20/simple.jwt b/inspector-vc/src/test/resources/ob20/simple.jwt new file mode 100644 index 0000000..b02bac2 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/simple.jwt @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxLmpzb24ifX0.i2aO8rusbEupHysBBgxZpZBMtLHrGGI2l3Qi-OLxfV20YlD_yRUAVi5D98NyQZsTmHXrvG9x9VCki60ksQqr5vImplKpaq6k4OkvL8VdAt_y6MLD9w-2dTtpgfRFHnWvXcVwImrllUU5wZ1SHAikEpRhAX6Z3BbGvJOo2liCk4g16NazpItLACfKxrNeNMi9vOOyZ3ztBProePa6nQB8ysy7qAQetBioZM17Umm4RjpHkil33PHwkVHndx74rdaUoMcc6mNy7nv_ddEi1l4mK1La9xb62KyXuQKCE1Wj6UzsjTh2wPo-ISzjEJQcY1Ge5-PcrPk35ZQpqDxJyyWaBA \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple.jwt b/inspector-vc/src/test/resources/ob30/simple.jwt index 6f64aad..41540b1 100644 --- a/inspector-vc/src/test/resources/ob30/simple.jwt +++ b/inspector-vc/src/test/resources/ob30/simple.jwt @@ -1 +1 @@ -eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaW1zZ2xvYmFsLmdpdGh1Yi5pby9vcGVuYmFkZ2VzLXNwZWNpZmljYXRpb24vY29udGV4dC5qc29uIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwidHlwZSI6WyJQcm9maWxlIl0sIm5hbWUiOiJFeGFtcGxlIFVuaXZlcnNpdHkifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQwMDowMDowMFoiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IERlZ3JlZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.G7W8od9rSZRsVyk26rXjg_fH2CyUihwNpepd6tWgLt_UHC1vUU0Clox8IicnOSkMyYEqAuNZAdCC9_35i1oUcyj1c076Aa0dsVQ2fFVuQPqXBlyZWcBmo5jqOK6R9NHzRAYXwLRXgrB8gz3lSK55cnHTnMtkpXXcUcHkS5ylWbXCLeOWKoygOCuxRN3N6kP-0HOyuk15PWlnkJ2zEKz2pBtVPaNEydcT0kEtoHFMEWVwqo6rnGV-Ea3M7ssDt3145mcl-DVYLXmBVdT8KoO47QAOBaVMR6k-hgrHNBcdhpI-o6IvLIFsGLgrNvWN67i8Z7Baum1mP-HBpsAigdmIpA \ No newline at end of file +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxIn19.Ii4vWqEVdSCmn_JGeoa6PaTYVMf7-mvIzyUA2pGkgFPcjstkKVhCwLbbKsKVmHseIxda7CDNpjqnHfmp3ZqYDpI0Bo3yK76V5sBskn_Wu4NBSJKTj3gFwr2CtGUeu5FJruL765ehCOhNIk4-3BXxJfQ8Q9Aj25TdgEh5xRxoveaKrnW07Mh_gM7yo0pAZaSEIxIYuBtn_-HeLLWDnYrB9AstYNVoOQSm5onL9I3IKH_0JGGSrG_vS9jmytLkuHEHnT379jXBTqUieRJyNQh_eUzu-hQIHeYjlUs8kSHwkf9JlgG1ziNljVNeFVbve46LsIhi9n1x59Q15ez0LdjeGQ \ No newline at end of file From 7580a2c56b8ef23dd46c0290c5db63075d947c72 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 6 Dec 2022 15:44:15 +0100 Subject: [PATCH 58/93] More validations and prerequisites --- .../org/oneedtech/inspect/vc/Assertion.java | 36 ++- .../oneedtech/inspect/vc/OB20Inspector.java | 21 +- .../org/oneedtech/inspect/vc/Validation.java | 22 +- .../vc/jsonld/JsonLdGeneratedObject.java | 9 + .../vc/jsonld/probe/GraphFetcherProbe.java | 216 ++++++++++++++++++ .../jsonld/probe/JsonLDCompactionProve.java | 6 +- .../probe/VerificationDependenciesProbe.java | 137 +++++++++++ .../vc/probe/VerificationRecipientProbe.java | 95 ++++++++ ...nFlattenEmbeddedResourcePropertyProbe.java | 94 -------- .../ValidationImagePropertyProbe.java | 11 +- .../ValidationIssuerPropertyProbe.java | 12 +- .../validation/ValidationPropertyProbe.java | 77 ++++--- .../ValidationRdfTypePropertyProbe.java | 11 +- .../inspect/vc/util/JsonNodeUtil.java | 8 + 14 files changed, 605 insertions(+), 150 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationRecipientProbe.java delete mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 7410903..1e31358 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -171,25 +171,33 @@ public class Assertion extends Credential { new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Assertion)).build(), new Validation.Builder().name("recipient").type(ValueType.ID).expectedType(Type.IdentityObject).required(true).build(), - new Validation.Builder().name("badge").type(ValueType.ID).prerequisite("ASN_FLATTEN_BC").expectedType(Type.BadgeClass).fetch(true).required(true).build(), + new Validation.Builder().name("badge").type(ValueType.ID).expectedType(Type.BadgeClass).fetch(true).required(true).allowFlattenEmbeddedResource(true).build(), new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectAssertion).required(true).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build(), - new Validation.Builder().name("image").type(ValueType.IMAGE).required(false).many(false).allowDataUri(false).build() + new Validation.Builder().name("image").type(ValueType.IMAGE).required(false).many(false).allowDataUri(false).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), + new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Assertion).fullValidate(false).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() )) .put(Type.BadgeClass, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.BadgeClass)).build(), - new Validation.Builder().name("issuer").type(ValueType.ID).prerequisite("BC_FLATTEN_ISS").expectedType(Type.Profile).fetch(true).required(true).build(), + new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).allowFlattenEmbeddedResource(true).build(), new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), new Validation.Builder().name("description").type(ValueType.TEXT).required(true).build(), new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), new Validation.Builder().name("criteria").type(ValueType.ID).expectedType(Type.Criteria).fetch(false).required(true).allowRemoteUrl(true).build(), new Validation.Builder().name("alignment").type(ValueType.ID).expectedType(Type.AlignmentObject).many(true).fetch(false).required(false).build(), - new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).required(false).build() + new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).required(false).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), + new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.BadgeClass).fullValidate(false).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() )) .put(Type.AlignmentObject, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.AlignmentObject).build(), @@ -216,7 +224,11 @@ public class Assertion extends Credential { new Validation.Builder().name("claim").type(ValueType.ID).required(true).allowRemoteUrl(false).fetch(false).allowDataUri(false).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).fullValidate(false).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).build(), - new Validation.Builder().name("verification").build() + new Validation.Builder().name("verification").build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), + new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Endorsement).fullValidate(false).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() )) .put(Type.EndorsementClaim, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), @@ -267,7 +279,11 @@ public class Assertion extends Credential { new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), - new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build() + new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), + new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Issuer).fullValidate(false).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() )) .put(Type.Profile, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), @@ -280,7 +296,11 @@ public class Assertion extends Credential { new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), - new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build() + new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), + new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Profile).fullValidate(false).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() )) .put(Type.RevocationList, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.RevocationList)).build(), @@ -289,7 +309,7 @@ public class Assertion extends Credential { .put(Type.VerificationObject, List.of()) .put(Type.VerificationObjectAssertion, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(false).mustContainOne(List.of("HostedBadge", "SignedBadge")).build(), - new Validation.Builder().name("creator").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).prerequisite("ASSERTION_VERIFICATION_DEPENDENCIES").build() + new Validation.Builder().name("creator").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build() )) .put(Type.VerificationObjectIssuer, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.VerificationObject).build(), 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 0f6a406..0a911f7 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 @@ -23,6 +23,7 @@ import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.Credential.CredentialEnum; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.GraphFetcherProbe; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; import org.oneedtech.inspect.vc.payload.PngParser; @@ -32,6 +33,7 @@ import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; +import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; @@ -122,12 +124,15 @@ public class OB20Inspector extends Inspector { accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx)); if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); - - // Each Badge Object contains all required properties for its class - // This could be done validating with the schema, but seems that there are some error on that file - // So, we do a manual Probe for the nodes. - // Also, we validate the Open Badge, from the compacted form + // validation the Open Badge, from the compacted form JsonNode assertionNode = mapper.readTree(jsonLdGeneratedObject.getJson()); + + // mount the graph, flattening embedded resources + probeCount++; + accumulator.add(new GraphFetcherProbe(assertion).run(assertionNode, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + + // perform validations List validations = assertion.getValidations(); for (Validation validation : validations) { probeCount++; @@ -142,6 +147,12 @@ public class OB20Inspector extends Inspector { accumulator.add(probe.run(assertion, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } + + // verification + probeCount++; + accumulator.add(new VerificationDependenciesProbe(assertion.getId()).run(jsonLdGeneratedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java index 42a0b6f..4e2fb2e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java @@ -13,7 +13,7 @@ public class Validation { private final boolean required; private final boolean many; private final List mustContainOne; - private final List prerequisites; + private final List prerequisites; private final List expectedTypes; private final boolean allowRemoteUrl; private final boolean allowDataUri; @@ -21,6 +21,7 @@ public class Validation { private final String defaultType; private final boolean fullValidate; private MessageLevel messageLevel; + private final boolean allowFlattenEmbeddedResource; public Validation(Builder builder) { this.name = builder.name; @@ -36,6 +37,7 @@ public class Validation { this.defaultType = builder.defaultType; this.fullValidate = builder.fullValidate; this.messageLevel = builder.messageLevel; + this.allowFlattenEmbeddedResource = builder.allowFlattenEmbeddedResource; } public String getName() { @@ -58,7 +60,7 @@ public class Validation { return mustContainOne; } - public List getPrerequisites() { + public List getPrerequisites() { return prerequisites; } @@ -90,6 +92,10 @@ public class Validation { return messageLevel; } + public boolean isAllowFlattenEmbeddedResource() { + return allowFlattenEmbeddedResource; + } + public enum MessageLevel { Warning, Error @@ -101,7 +107,7 @@ public class Validation { private boolean required; private boolean many; private List mustContainOne; - private List prerequisites; + private List prerequisites; private List expectedTypes; private boolean allowRemoteUrl; private boolean allowDataUri; @@ -109,6 +115,7 @@ public class Validation { private String defaultType; private boolean fullValidate; private MessageLevel messageLevel; + private boolean allowFlattenEmbeddedResource; public Builder() { this.mustContainOne = new ArrayList<>(); @@ -147,12 +154,12 @@ public class Validation { return this; } - public Builder prerequisites(List elems) { + public Builder prerequisites(List elems) { this.prerequisites = elems; return this; } - public Builder prerequisite(String elem) { + public Builder prerequisite(Validation elem) { this.prerequisites = List.of(elem); return this; } @@ -201,6 +208,11 @@ public class Validation { return this; } + public Builder allowFlattenEmbeddedResource(boolean allowFlattenEmbeddedResource) { + this.allowFlattenEmbeddedResource = allowFlattenEmbeddedResource; + return this; + } + public Validation build() { return new Validation(this); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java index fa7dcff..c53662c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLdGeneratedObject.java @@ -18,5 +18,14 @@ public class JsonLdGeneratedObject extends GeneratedObject { return json; } + /** + * Update internal json. We allow this update because some validations updates JSON-LD id attributes with + * autogenerated ones + * @param json + */ + public void setJson(String json) { + this.json = json; + } + public static final String ID = JsonLdGeneratedObject.class.getCanonicalName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java new file mode 100644 index 0000000..dff44db --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java @@ -0,0 +1,216 @@ +package org.oneedtech.inspect.vc.jsonld.probe; + +import static org.oneedtech.inspect.vc.Assertion.ValueType.DATA_URI_OR_URL; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.oneedtech.inspect.core.probe.Outcome; +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.util.resource.UriResource; +import org.oneedtech.inspect.vc.Assertion; +import org.oneedtech.inspect.vc.Assertion.Type; +import org.oneedtech.inspect.vc.Assertion.ValueType; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; +import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.google.common.io.Resources; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; + +/** + * Probe for fetching all elements in the graph for Open Badges 2.0 validation + * Contains the fetch part of "VALIDATE_TYPE_PROPERTY" task in python implementation, as well as the "FLATTEN_EMBEDDED_RESOURCE" task + * @author xaracil + */ +public class GraphFetcherProbe extends Probe { + private final Assertion assertion; + + public GraphFetcherProbe(Assertion assertion) { + super(ID); + this.assertion = assertion; + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + ReportItems result = new ReportItems(); + + // get validations of IDs and fetch + List validations = assertion.getValidations().stream() + .filter(validation -> validation.getType() == ValueType.ID && validation.isFetch()) + .collect(Collectors.toList()); + + for (Validation validation : validations) { + JsonNode node = root.get(validation.getName()); + + if (node == null) { + // if node is null, continue. ValidationPropertyProbe will check if the field was required + continue; + } + + // flatten embeded resource + if (validation.isAllowFlattenEmbeddedResource()) { + if (!node.isTextual()) { + if (!node.isObject()) { + return error("Property " + validation.getName() + " referenced from " + assertion.getJson().toString() + " is not a JSON object or string as expected", ctx); + } + + JsonNode idNode = node.get("id"); + if (idNode == null) { + // add a new node to the graph + UUID newId = UUID.randomUUID(); + JsonNode merged = createNewJson(ctx, "{\"id\": \"_:" + newId + "\"}"); + ctx.addGeneratedObject(new JsonLdGeneratedObject(JsonLDCompactionProve.getId(newId.toString()), merged.toString())); + + // update existing node with new id + updateNode(validation, idNode, ctx); + + return warning("Node id missing at " + node.toString() + ". A blank node ID has been assigned", ctx); + } else if (!idNode.isTextual() || !PrimitiveValueValidator.validateIri(idNode)) { + return error("Embedded JSON object at " + node.asText() + " has no proper assigned id.", ctx); + } else if (assertion.getCredentialType() == Type.Assertion && !PrimitiveValueValidator.validateUrl(idNode)) { + if (!isUrn(idNode)) { + logger.info("ID format for " + idNode.toString() + " at " + assertion.getCredentialType() + " not in an expected HTTP or URN:UUID scheme"); + } + + // add a new node to the graph + JsonNode merged = createNewJson(ctx, node); + ctx.addGeneratedObject(new JsonLdGeneratedObject(JsonLDCompactionProve.getId(idNode.asText().strip()), merged.toString())); + + // update existing node with new id + updateNode(validation, idNode, ctx); + + } else { + + // update existing node with new id + updateNode(validation, idNode, ctx); + + // fetch node and add it to the graph + UriResource uriResource = resolveUriResource(ctx, idNode.asText().strip()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); + if (resolved == null) { + return new CredentialParseProbe().run(uriResource, ctx); + } + } + } + } + + List nodeList = JsonNodeUtil.asNodeList(node); + for (JsonNode childNode : nodeList) { + if (shouldFetch(childNode, validation)) { + // get node from context + UriResource uriResource = resolveUriResource(ctx, childNode.asText()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); + if (resolved == null) { + // fetch + result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + Assertion fetchedAssertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); + + // compact ld + result = new ReportItems(List.of(result, new JsonLDCompactionProve(fetchedAssertion.getCredentialType().getContextUris().get(0)).run(fetchedAssertion, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + JsonLdGeneratedObject fetched = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(fetchedAssertion)); + JsonNode fetchedNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(fetched.getJson()); + + // recursive call + result = new ReportItems(List.of(result, new GraphFetcherProbe(fetchedAssertion).run(fetchedNode, ctx))); + } + } + } + } + } + + } + return success(ctx); + } + + /** + * Tells if we have to fetch the id. We have to fecth if: + * - the node is not a complex node + * - not (validation allow data-uri but the node is not of this type) + * - not (validation doesn't allow data-uri but the node is not an IRI) + * @param node + * @param validation + * @return + */ + private boolean shouldFetch(JsonNode node, Validation validation) { + return !node.isObject() || (!validation.isAllowDataUri() || DATA_URI_OR_URL.getValidationFunction().apply(node)) || (validation.isAllowDataUri() || ValueType.IRI.getValidationFunction().apply(node)); + } + + private void updateNode(Validation validation, JsonNode idNode, RunContext ctx) throws IOException { + JsonLdGeneratedObject jsonLdGeneratedObject = ctx.getGeneratedObject(JsonLDCompactionProve.getId(assertion)); + JsonNode merged = createNewJson(ctx, jsonLdGeneratedObject.getJson(), "{\"" + validation.getName() + "\": \"" + idNode.asText().strip() + "\""); + jsonLdGeneratedObject.setJson(merged.toString()); + + } + + private JsonNode createNewJson(RunContext ctx, JsonNode node) throws IOException { + return createNewJson(ctx, Resources.getResource("contexts/ob-v2p0.json"), node.toString()); + } + + private JsonNode createNewJson(RunContext ctx, String additional) throws IOException { + return createNewJson(ctx, Resources.getResource("contexts/ob-v2p0.json"), additional); + } + + private JsonNode createNewJson(RunContext ctx, URL original, String additional) throws IOException { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode newNode = mapper.readTree(original); + ObjectReader readerForUpdating = mapper.readerForUpdating(newNode); + JsonNode merged = readerForUpdating.readValue(additional); + return merged; + } + + private JsonNode createNewJson(RunContext ctx, String original, String updating) throws IOException { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode source = mapper.readTree(original); + ObjectReader readerForUpdating = mapper.readerForUpdating(source); + JsonNode merged = readerForUpdating.readValue(updating); + return merged; + } + + + private boolean isUrn(JsonNode idNode) { + final Pattern pattern = Pattern.compile(URN_REGEX, Pattern.CASE_INSENSITIVE); + final Matcher matcher = pattern.matcher(idNode.asText()); + return matcher.matches(); + } + + 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; + } + + public static final String ID = GraphFetcherProbe.class.getSimpleName(); + public static final String URN_REGEX = "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'"; + protected final static Logger logger = LogManager.getLogger(GraphFetcherProbe.class); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index 40a90d8..92c4a51 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -18,7 +18,11 @@ import com.apicatalog.jsonld.loader.DocumentLoader; import jakarta.json.JsonObject; - +/** + * JSON-LD compaction probe for Open Badges 2.0 + * Maps to "JSONLD_COMPACT_DATA" task in python implementation + * @author xaracil + */ public class JsonLDCompactionProve extends Probe { private final String context; 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 new file mode 100644 index 0000000..9014c75 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationDependenciesProbe.java @@ -0,0 +1,137 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +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.util.resource.UriResource; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; + +/** + * Verification probe for Open Badges 2.0 + * Maps to "ASSERTION_VERIFICATION_DEPENDENCIES" task in python implementation + * @author xaracil + */ +public class VerificationDependenciesProbe extends Probe { + private final String assertionId; + + public VerificationDependenciesProbe(String assertionId) { + super(ID); + this.assertionId = assertionId; + } + + @Override + public ReportItems run(JsonLdGeneratedObject jsonLdGeneratedObject, RunContext ctx) throws Exception { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); + + // TODO: get verification object from graph + String type = jsonNode.get("verification").get("type").asText().strip(); + if ("HostedBadge".equals(type)) { + // get badge + UriResource badgeUriResource = resolveUriResource(ctx, jsonNode.get("badge").asText().strip()); + JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(badgeUriResource)); + + // get issuer from badge + JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(badgeObject.getJson()); + + UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + + JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(issuerUriResource)); + JsonNode issuerNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(issuerObject.getJson()); + + // verify issuer + JsonNode verificationPolicy = issuerNode.get("verification"); + try { + checkNotNull(verificationPolicy); + if (verificationPolicy.isTextual()) { + // get verification node + JsonLdGeneratedObject verificationPolicyObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(verificationPolicy.asText().strip())); + verificationPolicy = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(verificationPolicyObject.getJson()); + } + } catch (Throwable t) { + verificationPolicy = getDefaultVerificationPolicy(issuerNode, mapper); + } + + // starts with check + if (verificationPolicy.has("startsWith")) { + List startsWith = JsonNodeUtil.asStringList(verificationPolicy.get("startsWith")); + if (!startsWith.stream().anyMatch(assertionId::startsWith)) { + return error("Assertion id " + assertionId + + "does not start with any permitted values in its issuer's verification policy.", ctx); + } + } + + // allowed origins + JsonNode allowedOriginsNode = verificationPolicy.get("allowedOrigins"); + List allowedOrigins = null; + String issuerId = issuerNode.get("id").asText().strip(); + if (allowedOriginsNode == null || allowedOriginsNode.isNull()) { + allowedOrigins = List.of(getDefaultAllowedOrigins(issuerId)); + } else { + JsonNodeUtil.asStringList(allowedOriginsNode); + } + + if (allowedOrigins == null || allowedOrigins.isEmpty() || !issuerId.startsWith("http")) { + return warning("Issuer " + issuerId + " has no HTTP domain to enforce hosted verification policy against.", ctx); + } + + if (!allowedOrigins.contains(new URI(assertionId).getAuthority())) { + return error("Assertion " + assertionId + " not hosted in allowed origins " + allowedOrigins, ctx); + } + } + return success(ctx); + } + + private JsonNode getDefaultVerificationPolicy(JsonNode issuerNode, ObjectMapper mapper) throws URISyntaxException { + String issuerId =issuerNode.get("id").asText().strip(); + + return mapper.createObjectNode() + .put("type", "VerificationObject") + .put("allowedOrigins", getDefaultAllowedOrigins(issuerId)) + .put("verificationProperty", "id"); + } + + private String getDefaultAllowedOrigins(String issuerId) throws URISyntaxException { + URI issuerUri = new URI(issuerId); + return issuerUri.getAuthority(); + } + + 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; + } + + public static final String ID = VerificationDependenciesProbe.class.getSimpleName(); + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationRecipientProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationRecipientProbe.java new file mode 100644 index 0000000..c6f4972 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationRecipientProbe.java @@ -0,0 +1,95 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; + +import org.bouncycastle.crypto.digests.GeneralDigest; +import org.bouncycastle.crypto.digests.MD5Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.util.encoders.Hex; +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.Assertion; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Recipient Verification probe for Open Badges 2.0 + * Maps to "VERIFY_RECIPIENT_IDENTIFIER" task in python implementation + * @author xaracil + */ +public class VerificationRecipientProbe extends Probe { + final String profileId; + + public VerificationRecipientProbe(String profileId) { + super(ID); + this.profileId = profileId; + } + + @Override + public ReportItems run(Assertion assertion, RunContext ctx) throws Exception { + ReportItems warnings = new ReportItems(); + JsonNode recipientNode = assertion.getJson().get("recipient"); + + JsonLdGeneratedObject profileObject = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(profileId)); + JsonNode profileNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(profileObject.getJson()); + + String type = recipientNode.get("type").asText().strip(); + if (!allowedTypes.contains(type)) { + warnings = warning("Recipient identifier type " + type + " in assertion " + assertion.getJson().toString() + " is not one of the recommended types", ctx); + } + + JsonNode typeNode = profileNode.get(type); + if (JsonNodeUtil.isEmpty(typeNode)) { + return new ReportItems(List.of(warnings, error("Profile identifier property of type " + typeNode + " not found in submitted profile " + profileId, ctx))); + } + + JsonNode hashNode = recipientNode.get("hashed"); + List currentTypes = JsonNodeUtil.asStringList(typeNode); + String identity = recipientNode.get("identity").asText().strip().toLowerCase(); + String confirmedId = null; + if (JsonNodeUtil.isNotEmpty(hashNode) && hashNode.asBoolean()) { + String salt = recipientNode.get("salt").asText().strip(); + for (String possibleId : currentTypes) { + if (hashMatch(possibleId, identity, salt)) { + confirmedId = possibleId; + break; + } + } + if (confirmedId == null) { + return new ReportItems(List.of(warnings, error("Profile " + profileId + " identifier(s) " + currentTypes + " of type " + typeNode.toString() + " did not match assertion " + assertion.getId() + " recipient hash " + identity + ".", ctx))); + } + } else if (currentTypes.contains(identity)) { + confirmedId = identity; + } else { + return new ReportItems(List.of(warnings, error("Profile " + profileId + " identifier " + currentTypes + " of type " + typeNode.toString() + " did not match assertion " + assertion.getId() + " recipient value " + identity, ctx))); + } + + return new ReportItems(List.of(warnings, success(ctx))); + } + + private boolean hashMatch(String possibleId, String identity, String salt) throws Exception { + String text = possibleId + salt; + GeneralDigest digest = null; + if (identity.startsWith("md5")) { + digest = new MD5Digest(); + } else if (identity.startsWith("sha256")) { + digest = new SHA256Digest(); + } else { + throw new IllegalAccessException("Cannot interpret hash type of " + identity); + } + digest.update(text.getBytes(), 0, text.length()); + byte[] digested = new byte[digest.getDigestSize()]; + digest.doFinal(digested, 0); + return new String(Hex.encode(digested)).equals(identity); + } + + private static final List allowedTypes = List.of("id", "email", "url", "telephone"); + public static final String ID = VerificationRecipientProbe.class.getSimpleName(); + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java deleted file mode 100644 index 604f39a..0000000 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationFlattenEmbeddedResourcePropertyProbe.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.oneedtech.inspect.vc.probe.validation; - -import java.util.UUID; - -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.util.resource.UriResource; -import org.oneedtech.inspect.vc.Validation; -import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; -import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; -import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; -import com.google.common.io.Resources; - -public class ValidationFlattenEmbeddedResourcePropertyProbe extends ValidationPropertyProbe { - - public ValidationFlattenEmbeddedResourcePropertyProbe(Validation validation) { - super(validation); - } - - public ValidationFlattenEmbeddedResourcePropertyProbe(Validation validation, boolean fullValidate) { - super(validation, fullValidate); - } - - @Override - protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { - return notRun("Expected property " + validation.getName() + " was missing in node " + node.toString(), ctx); - } - - @Override - protected ReportItems validate(JsonNode node, RunContext ctx) { - try { - UriResource uriResource = resolveUriResource(ctx, node.asText()); - JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); - ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); - JsonNode fetchedNode = mapper.readTree(resolved.getJson()); - - if (fetchedNode.isTextual()) { - return notRun("Property " + validation.getName() + " referenced from " + node.toString() + " is not embedded in need of flattening", ctx); - } - - if (!fetchedNode.isObject()) { - return error("Property " + validation.getName() + " referenced from " + node.toString() + " is not a JSON object or string as expected", ctx); - } - - JsonNode idNode = fetchedNode.get("id"); - if (idNode == null) { - // add a new node to the graph - JsonNode newNode = mapper.readTree(Resources.getResource("contexts/ob-v2p0.json")); - ObjectReader readerForUpdating = mapper.readerForUpdating(newNode); - UUID newId = UUID.randomUUID(); - JsonNode merged = readerForUpdating.readValue("{\"id\": \"_:" + newId + "\"}"); - ctx.addGeneratedObject(new JsonLdGeneratedObject(JsonLDCompactionProve.getId(newId.toString()), merged.toString())); - - return warning("Node id missing at " + node.toString() + ". A blank node ID has been assigned", ctx); - } else if (!idNode.isTextual() && !PrimitiveValueValidator.validateIri(idNode)) { - return error("Embedded JSON object at " + node.asText() + " has no proper assigned id.", ctx); - } else if (/*node_class == Assertion && */ !PrimitiveValueValidator.validateUrl(idNode)) { - /* - if not re.match(URN_REGEX, embedded_node_id, re.IGNORECASE): - actions.append(report_message( - 'ID format for {} at {} not in an expected HTTP or URN:UUID scheme'.format( - embedded_node_id, abv_node(node_path=[node_id, prop_name]) - ))) - new_node = value.copy() - new_node['@context'] = OPENBADGES_CONTEXT_V2_URI - actions.append(add_node(embedded_node_id, data=value)) - actions.append(patch_node(node_id, {prop_name: embedded_node_id})) - */ - - } else { - - /* - actions.append(patch_node(node_id, {prop_name: embedded_node_id})) - - if not node_match_exists(state, embedded_node_id) and not filter_tasks( - state, node_id=embedded_node_id, task_type=FETCH_HTTP_NODE): - # fetch - actions.append(add_task(FETCH_HTTP_NODE, url=embedded_node_id)) - */ - } - } catch (Throwable t) { - return fatal(t.getMessage(), ctx); - } - - return success(ctx); - } - - -} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java index d2dfa30..3dd3f2c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java @@ -13,14 +13,19 @@ import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; import com.fasterxml.jackson.databind.JsonNode; +/** + * Image validation for Open Badges 2.0 + * Maps to "IMAGE_VALIDATION" task in python implementation + * @author xaracil + */ public class ValidationImagePropertyProbe extends ValidationPropertyProbe { public ValidationImagePropertyProbe(Validation validation) { - super(validation); + super(ID, validation); } public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) { - super(validation, fullValidate); + super(ID, validation, fullValidate); } @Override @@ -62,4 +67,6 @@ public class ValidationImagePropertyProbe extends ValidationPropertyProbe { } private static final List allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG); + public static final String ID = ValidationImagePropertyProbe.class.getSimpleName(); + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java index 789c750..62ab1d4 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java @@ -7,14 +7,19 @@ import org.oneedtech.inspect.vc.Validation.MessageLevel; import com.fasterxml.jackson.databind.JsonNode; +/** + * Issuer properties additional validator for Open Badges 2.0 + * Maps to "ISSUER_PROPERTY_DEPENDENCIES" task in python implementation + * @author xaracil + */ public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe { public ValidationIssuerPropertyProbe(Validation validation) { - super(validation); + super(ID, validation); } public ValidationIssuerPropertyProbe(Validation validation, boolean fullValidate) { - super(validation, fullValidate); + super(ID, validation, fullValidate); } @Override @@ -32,4 +37,7 @@ public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe { } return error(msg, ctx); } + + public static final String ID = ValidationIssuerPropertyProbe.class.getSimpleName(); + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index e6aa71c..53e0055 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -17,12 +17,10 @@ import org.oneedtech.inspect.core.probe.RunContext.Key; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.core.report.ReportUtil; import org.oneedtech.inspect.util.resource.UriResource; -import org.oneedtech.inspect.vc.Assertion; import org.oneedtech.inspect.vc.Assertion.ValueType; import org.oneedtech.inspect.vc.Validation; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; -import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.PropertyProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -32,17 +30,29 @@ import com.fasterxml.jackson.databind.ObjectMapper; import foundation.identity.jsonld.ConfigurableDocumentLoader; - +/** + * Validator for properties of type other than ValueType.RDF_TYPE in Open Badges 2.0 types + * Maps to "VALIDATE_TYPE_PROPERTY" task in python implementation + * @author xaracil + */ public class ValidationPropertyProbe extends PropertyProbe { protected final Validation validation; protected final boolean fullValidate; // TODO: fullValidate public ValidationPropertyProbe(Validation validation) { - this(validation, false); + this(ID, validation, false); + } + + public ValidationPropertyProbe(String id, Validation validation) { + this(ID, validation, false); } public ValidationPropertyProbe(Validation validation, boolean fullValidate) { - super(ID + "<" + validation.getName() + ">", validation.getName()); + this(ID, validation, fullValidate); + } + + public ValidationPropertyProbe(String id, Validation validation, boolean fullValidate) { + super(id + "<" + validation.getName() + ">", validation.getName()); this.validation = validation; this.fullValidate = fullValidate; setValidations(this::validate); @@ -100,6 +110,11 @@ public class ValidationPropertyProbe extends PropertyProbe { } } } else { + // pre-requisites + result = new ReportItems(List.of(result, validatePrerequisites(node, ctx))); + if (result.contains(Outcome.ERROR, Outcome.EXCEPTION)) { + return result; + } for (JsonNode childNode : nodeList) { if (childNode.isObject()) { result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx))); @@ -114,35 +129,20 @@ public class ValidationPropertyProbe extends PropertyProbe { UriResource uriResource = resolveUriResource(ctx, childNode.asText()); JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); if (resolved == null) { - if (!validation.isFetch()) { - if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) { - continue; - } - - if (validation.isAllowDataUri() && DATA_URI.getValidationFunction().apply(childNode)) { - continue; - } - return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); - } else { - // fetch - result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); - if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { - Assertion assertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); - - // compact ld - result = new ReportItems(List.of(result, new JsonLDCompactionProve(assertion.getCredentialType().getContextUris().get(0)).run(assertion, ctx))); - if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { - JsonLdGeneratedObject fetched = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(assertion)); - JsonNode fetchedNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(fetched.getJson()); - - // validate document - result = new ReportItems(List.of(result, validateExpectedTypes(fetchedNode, ctx))); - } - } + if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) { + continue; } + + if (validation.isAllowDataUri() && DATA_URI.getValidationFunction().apply(childNode)) { + continue; + } + return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); } else { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode resolvedNode = mapper.readTree(resolved.getJson()); + // validate expected node class - result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx))); + result = new ReportItems(List.of(result, validateExpectedTypes(resolvedNode, ctx))); } } } @@ -168,6 +168,21 @@ public class ValidationPropertyProbe extends PropertyProbe { return uriResource; } + private ReportItems validatePrerequisites(JsonNode node, RunContext ctx) { + List results = validation.getPrerequisites().stream() + .map(v -> ValidationPropertyProbeFactory.of(v, validation.isFullValidate())) + .map(probe -> { + try { + return probe.run(node, ctx); + } catch (Exception e) { + return ReportUtil.onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, null, e); + } + }) + .collect(Collectors.toList()); + + return new ReportItems(results); + } + private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) { List results = validation.getExpectedTypes().stream() .flatMap(type -> type.getValidations().stream()) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java index 8929f79..fc9ecf1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java @@ -13,13 +13,18 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.TextNode; +/** + * Validator for properties of type ValueType.RDF_TYPE in Open Badges 2.0 types + * Maps to "VALIDATE_RDF_TYPE_PROPERTY" task in python implementation + * @author xaracil + */ public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe { public ValidationRdfTypePropertyProbe(Validation validation) { - super(validation); + super(ID, validation); } public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) { - super(validation, fullValidate); + super(ID, validation, fullValidate); } @Override @@ -55,4 +60,6 @@ public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe { } return new ReportItems(List.of(result, success(ctx))); } + + public static final String ID = ValidationRdfTypePropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java index 630e43a..080bc6f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java @@ -58,4 +58,12 @@ public class JsonNodeUtil { .collect(Collectors.toList()); } } + + public static boolean isNotEmpty(JsonNode node) { + return node != null && !node.isNull() && !node.isEmpty(); + } + + public static boolean isEmpty(JsonNode node) { + return node == null || node.isNull() || node.isEmpty(); + } } From 3f3276485d2d8550ea519ebc60f7ef3ec31b7353 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 15:27:25 +0100 Subject: [PATCH 59/93] remove setter call with default boolean value --- .../org/oneedtech/inspect/vc/Assertion.java | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 1e31358..d0b2185 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -174,15 +174,15 @@ public class Assertion extends Credential { new Validation.Builder().name("badge").type(ValueType.ID).expectedType(Type.BadgeClass).fetch(true).required(true).allowFlattenEmbeddedResource(true).build(), new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectAssertion).required(true).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), - new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), - new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), - new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), - new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build(), - new Validation.Builder().name("image").type(ValueType.IMAGE).required(false).many(false).allowDataUri(false).build(), - new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), - new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), - new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Assertion).fullValidate(false).many(true).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("expires").type(ValueType.DATETIME).build(), + new Validation.Builder().name("image").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Image).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).build(), + new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).build(), + new Validation.Builder().name("image").type(ValueType.IMAGE).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Assertion).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.BadgeClass, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), @@ -190,132 +190,132 @@ public class Assertion extends Credential { new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).allowFlattenEmbeddedResource(true).build(), new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), new Validation.Builder().name("description").type(ValueType.TEXT).required(true).build(), - new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), - new Validation.Builder().name("criteria").type(ValueType.ID).expectedType(Type.Criteria).fetch(false).required(true).allowRemoteUrl(true).build(), - new Validation.Builder().name("alignment").type(ValueType.ID).expectedType(Type.AlignmentObject).many(true).fetch(false).required(false).build(), - new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).required(false).build(), - new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), - new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), - new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.BadgeClass).fullValidate(false).many(true).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("image").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Image).allowDataUri(true).build(), + new Validation.Builder().name("criteria").type(ValueType.ID).expectedType(Type.Criteria).required(true).allowRemoteUrl(true).build(), + new Validation.Builder().name("alignment").type(ValueType.ID).expectedType(Type.AlignmentObject).many(true).build(), + new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.BadgeClass).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.AlignmentObject, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.AlignmentObject).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.AlignmentObject).build(), new Validation.Builder().name("targetName").type(ValueType.TEXT).required(true).build(), new Validation.Builder().name("targetUrl").type(ValueType.URL).required(true).build(), - new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("targetFramework").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("targetCode").type(ValueType.TEXT).required(false).build() + new Validation.Builder().name("description").type(ValueType.TEXT).build(), + new Validation.Builder().name("targetFramework").type(ValueType.TEXT).build(), + new Validation.Builder().name("targetCode").type(ValueType.TEXT).build() )) .put(Type.Criteria, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.Criteria).build(), - new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), - new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build() + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.Criteria).build(), + new Validation.Builder().name("id").type(ValueType.IRI).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).build() )) .put(Type.CryptographicKey, List.of( - new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.CryptographicKey).build(), - new Validation.Builder().name("owner").type(ValueType.IRI).required(false).fetch(true).build(), - new Validation.Builder().name("publicKeyPem").type(ValueType.TEXT).required(false).build() + new Validation.Builder().name("id").type(ValueType.IRI).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.CryptographicKey).build(), + new Validation.Builder().name("owner").type(ValueType.IRI).fetch(true).build(), + new Validation.Builder().name("publicKeyPem").type(ValueType.TEXT).build() )) .put(Type.Endorsement, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Endorsement)).build(), - new Validation.Builder().name("claim").type(ValueType.ID).required(true).allowRemoteUrl(false).fetch(false).allowDataUri(false).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).fullValidate(false).build(), + new Validation.Builder().name("claim").type(ValueType.ID).required(true).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).build(), new Validation.Builder().name("verification").build(), - new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), - new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), - new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Endorsement).fullValidate(false).many(true).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Endorsement).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.EndorsementClaim, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), - new Validation.Builder().name("endorsementComment").type(ValueType.MARKDOWN_TEXT).required(false).build() + new Validation.Builder().name("endorsementComment").type(ValueType.MARKDOWN_TEXT).build() )) .put(Type.Evidence, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType(Type.Evidence).build(), - new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), - new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), - new Validation.Builder().name("name").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("genre").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("audience").type(ValueType.TEXT).required(false).build() + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.Evidence).build(), + new Validation.Builder().name("id").type(ValueType.IRI).build(), + new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).build(), + new Validation.Builder().name("genre").type(ValueType.TEXT).build(), + new Validation.Builder().name("audience").type(ValueType.TEXT).build() )) .put(Type.ExpectedRecipientProfile, List.of( - new Validation.Builder().name("id").type(ValueType.IRI).required(false).build(), - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).defaultType(Type.Profile).build(), - new Validation.Builder().name("name").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("image").type(ValueType.ID).required(false).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), - new Validation.Builder().name("url").type(ValueType.URL).required(false).many(true).build(), - new Validation.Builder().name("email").type(ValueType.EMAIL).required(false).many(true).build(), - new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).many(true).build(), - new Validation.Builder().name("publicKey").type(ValueType.ID).many(true).expectedType(Type.CryptographicKey).fetch(false).required(false).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build() + new Validation.Builder().name("id").type(ValueType.IRI).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).defaultType(Type.Profile).build(), + new Validation.Builder().name("name").type(ValueType.TEXT).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).build(), + new Validation.Builder().name("image").type(ValueType.ID).expectedType(Type.Image).allowDataUri(true).build(), + new Validation.Builder().name("url").type(ValueType.URL).many(true).build(), + new Validation.Builder().name("email").type(ValueType.EMAIL).many(true).build(), + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).many(true).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).many(true).expectedType(Type.CryptographicKey).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).build() )) .put(Type.Extension, List.of()) .put(Type.IdentityObject, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(false).mustContainOne(List.of("id", "email", "url", "telephone")).build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).mustContainOne(List.of("id", "email", "url", "telephone")).build(), new Validation.Builder().name("identity").type(ValueType.IDENTITY_HASH).required(true).build(), new Validation.Builder().name("hashed").type(ValueType.BOOLEAN).required(true).build(), - new Validation.Builder().name("salt").type(ValueType.TEXT).required(false).build() + new Validation.Builder().name("salt").type(ValueType.TEXT).build() )) .put(Type.Image, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).required(false).defaultType("schema:ImageObject").build(), + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType("schema:ImageObject").build(), new Validation.Builder().name("id").type(ValueType.DATA_URI_OR_URL).required(true).build(), - new Validation.Builder().name("caption").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("author").type(ValueType.IRI).required(false).build() + new Validation.Builder().name("caption").type(ValueType.TEXT).build(), + new Validation.Builder().name("author").type(ValueType.IRI).build() )) .put(Type.Issuer, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).build(), new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), - new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).build(), + new Validation.Builder().name("image").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Image).allowDataUri(true).build(), new Validation.Builder().name("url").type(ValueType.URL).required(true).build(), new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), - new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), - new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), - new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build(), - new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), - new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), - new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Issuer).fullValidate(false).many(true).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).build(), + new Validation.Builder().name("id").type(ValueType.ISSUER).messageLevel(MessageLevel.Warning).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Issuer).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.Profile, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Issuer, Type.Profile)).build(), new Validation.Builder().name("name").type(ValueType.TEXT).required(true).build(), - new Validation.Builder().name("description").type(ValueType.TEXT).required(false).build(), - new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(true).build(), + new Validation.Builder().name("description").type(ValueType.TEXT).build(), + new Validation.Builder().name("image").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Image).allowDataUri(true).build(), new Validation.Builder().name("url").type(ValueType.URL).required(true).build(), new Validation.Builder().name("email").type(ValueType.EMAIL).required(true).build(), - new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).required(false).build(), - new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build(), - new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).fetch(false).required(false).build(), - new Validation.Builder().name("id").type(ValueType.ISSUER).required(false).messageLevel(MessageLevel.Warning).build(), - new Validation.Builder().name("@language").type(ValueType.LANGUAGE).required(false).build(), - new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).required(false).build(), - new Validation.Builder().name("related").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(false).allowDataUri(false).expectedType(Type.Profile).fullValidate(false).many(true).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).required(false).allowRemoteUrl(true).fetch(true).allowDataUri(false).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).build(), + new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).build(), + new Validation.Builder().name("id").type(ValueType.ISSUER).messageLevel(MessageLevel.Warning).build(), + new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), + new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Profile).many(true).build(), + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.RevocationList, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.RevocationList)).build(), - new Validation.Builder().name("id").type(ValueType.IRI).required(false).build() + new Validation.Builder().name("id").type(ValueType.IRI).build() )) .put(Type.VerificationObject, List.of()) .put(Type.VerificationObjectAssertion, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(false).mustContainOne(List.of("HostedBadge", "SignedBadge")).build(), - new Validation.Builder().name("creator").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).required(false).build() + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).mustContainOne(List.of("HostedBadge", "SignedBadge")).build(), + new Validation.Builder().name("creator").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).build() )) .put(Type.VerificationObjectIssuer, List.of( - new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.VerificationObject).build(), - new Validation.Builder().name("verificationProperty").type(ValueType.COMPACT_IRI).required(false).build(), - new Validation.Builder().name("startsWith").type(ValueType.URL).required(false).build(), - new Validation.Builder().name("allowedOrigins").type(ValueType.URL_AUTHORITY).required(false).many(true).build() + new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.VerificationObject).build(), + new Validation.Builder().name("verificationProperty").type(ValueType.COMPACT_IRI).build(), + new Validation.Builder().name("startsWith").type(ValueType.URL).build(), + new Validation.Builder().name("allowedOrigins").type(ValueType.URL_AUTHORITY).many(true).build() )) .build(); From e1f9c31b7b97f48d71e028d9e9d5288b38038057 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 15:55:47 +0100 Subject: [PATCH 60/93] Fixed badge url in test resource --- .../src/test/resources/ob20/warning-with-redirection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector-vc/src/test/resources/ob20/warning-with-redirection.json b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json index 01c02ad..fcf0ccb 100644 --- a/inspector-vc/src/test/resources/ob20/warning-with-redirection.json +++ b/inspector-vc/src/test/resources/ob20/warning-with-redirection.json @@ -11,7 +11,7 @@ "image": "https://example.org/beths-robot-badge.png", "evidence": "https://example.org/beths-robot-work.html", "issuedOn": "2016-12-31T23:59:59Z", - "badge": "http://example.org/altbadgeurl", + "badge": "http://example.org/altbadgeurl.json", "verification": { "type": "hosted" } From 3c094f53ef6f6437e451c6c19935cdd9c8144187 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 16:08:29 +0100 Subject: [PATCH 61/93] Fixed default allowed origins when the issuerId is not an url --- .../inspect/vc/probe/VerificationDependenciesProbe.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 9014c75..31f213c 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 @@ -5,6 +5,7 @@ import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.Optional; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; @@ -87,7 +88,10 @@ public class VerificationDependenciesProbe extends Probe List allowedOrigins = null; String issuerId = issuerNode.get("id").asText().strip(); if (allowedOriginsNode == null || allowedOriginsNode.isNull()) { - allowedOrigins = List.of(getDefaultAllowedOrigins(issuerId)); + String defaultAllowedOrigins = getDefaultAllowedOrigins(issuerId); + if (defaultAllowedOrigins != null) { + allowedOrigins = List.of(defaultAllowedOrigins); + } } else { JsonNodeUtil.asStringList(allowedOriginsNode); } From 4219785be46422e869f5590375e7cdb9e1c993bd Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 17:40:08 +0100 Subject: [PATCH 62/93] Added revocation list for fetch --- .../src/main/java/org/oneedtech/inspect/vc/Assertion.java | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index d0b2185..24d0b44 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -279,6 +279,7 @@ public class Assertion extends Credential { new Validation.Builder().name("telephone").type(ValueType.TELEPHONE).build(), new Validation.Builder().name("publicKey").type(ValueType.ID).expectedType(Type.CryptographicKey).fetch(true).build(), new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectIssuer).build(), + new Validation.Builder().name("revocationList").type(ValueType.ID).expectedType(Type.RevocationList).fetch(true).build(), new Validation.Builder().name("id").type(ValueType.ISSUER).messageLevel(MessageLevel.Warning).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), From 288e67e16681ca1b3280ddaeb5e3f37b675b091c Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 17:41:02 +0100 Subject: [PATCH 63/93] Default return for non existent field is success --- .../java/org/oneedtech/inspect/vc/probe/PropertyProbe.java | 2 +- .../oneedtech/inspect/vc/probe/StringValuePropertyProbe.java | 2 +- .../vc/probe/validation/ValidationImagePropertyProbe.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java index ed21f18..cd1232a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -37,6 +37,6 @@ public class PropertyProbe extends Probe { } private ReportItems defaultValidation(JsonNode node, RunContext ctx) { - return notRun("Not additional validations run", ctx); + return success(ctx); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java index 5c792d0..5a3ff3b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java @@ -28,7 +28,7 @@ public class StringValuePropertyProbe extends PropertyProbe { } private ReportItems defaultValidation(List nodeValues, RunContext ctx) { - return notRun("Not additional validations run", ctx); + return success(ctx); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java index 3dd3f2c..b5cdbcd 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java @@ -30,7 +30,8 @@ public class ValidationImagePropertyProbe extends ValidationPropertyProbe { @Override protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { - return notRun("Could not load and validate image in node " + node.toString(), ctx); + // Could not load and validate image in node + return success(ctx); } @Override From c96dd7726a6943db4edc5fa521428693404acd6d Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 17:41:14 +0100 Subject: [PATCH 64/93] Removed unused import --- .../inspect/vc/probe/VerificationDependenciesProbe.java | 1 - 1 file changed, 1 deletion(-) 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 31f213c..7e4136b 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 @@ -5,7 +5,6 @@ import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import java.net.URI; import java.net.URISyntaxException; import java.util.List; -import java.util.Optional; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; From 52b94777cecc0f74eaae478a359e03953904ad46 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 7 Dec 2022 17:41:30 +0100 Subject: [PATCH 65/93] Added revocation list probe to OB 2.0 --- .../oneedtech/inspect/vc/OB20Inspector.java | 12 ++- .../probe/AssertionRevocationListProbe.java | 101 ++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java 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 0a911f7..5002214 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 @@ -28,6 +28,7 @@ import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe; 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.ContextPropertyProbe; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; @@ -148,10 +149,13 @@ public class OB20Inspector extends Inspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - // verification - probeCount++; - accumulator.add(new VerificationDependenciesProbe(assertion.getId()).run(jsonLdGeneratedObject, ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + // verification and revocation + for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), new AssertionRevocationListProbe(assertion.getId()))) { + probeCount++; + accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); 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 new file mode 100644 index 0000000..9497028 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/AssertionRevocationListProbe.java @@ -0,0 +1,101 @@ +package org.oneedtech.inspect.vc.probe; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.util.resource.UriResource; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; + +public class AssertionRevocationListProbe extends Probe { + private final String assertionId; + + public AssertionRevocationListProbe(String assertionId) { + super(ID); + this.assertionId = assertionId; + } + + @Override + public ReportItems run(JsonLdGeneratedObject jsonLdGeneratedObject, RunContext ctx) throws Exception { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); + + // get badge + UriResource badgeUriResource = resolveUriResource(ctx, jsonNode.get("badge").asText().strip()); + JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(badgeUriResource)); + + // get issuer from badge + JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(badgeObject.getJson()); + + UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(issuerUriResource)); + JsonNode issuerNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(issuerObject.getJson()); + + JsonNode revocationListIdNode = issuerNode.get("revocationList"); + if (revocationListIdNode == null) { + // "Assertion is not revoked. Issuer has no revocation list" + return success(ctx); + } + + UriResource revocationListUriResource = resolveUriResource(ctx, revocationListIdNode.asText().strip()); + JsonLdGeneratedObject revocationListObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(revocationListUriResource)); + JsonNode revocationListNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(revocationListObject.getJson()); + + List revocationList = JsonNodeUtil.asNodeList(revocationListNode.get("revokedAssertions")); + List revokedMatches = revocationList.stream().filter(revocation -> { + if (revocation.isTextual()) { + return assertionId.equals(revocation.asText().strip()); + } + return revocation.get("id") != null && assertionId.equals(revocation.get("id").asText().strip()); + }).collect(Collectors.toList()); + + if (revokedMatches.size() > 0) { + Optional reasonNode = revokedMatches.stream() + .map(node -> node.get("revocationReason")) + .filter(Objects::nonNull) + .findFirst(); + String reason = reasonNode.isPresent() ? " with reason " + reasonNode.get().asText().strip() : ""; + return error("Assertion " + assertionId + " has been revoked in RevocationList " + revocationListIdNode.asText().strip() + reason, ctx); + } + return success(ctx); + } + + 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; + } + + public static final String ID = AssertionRevocationListProbe.class.getSimpleName(); +} From 1133628f5f331c579faed34e904d049b1afa3c3c Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 9 Dec 2022 12:27:37 +0100 Subject: [PATCH 66/93] Back to previous value --- inspector-vc/src/test/resources/ob30/simple.jwt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector-vc/src/test/resources/ob30/simple.jwt b/inspector-vc/src/test/resources/ob30/simple.jwt index 41540b1..6f64aad 100644 --- a/inspector-vc/src/test/resources/ob30/simple.jwt +++ b/inspector-vc/src/test/resources/ob30/simple.jwt @@ -1 +1 @@ -eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxIn19.Ii4vWqEVdSCmn_JGeoa6PaTYVMf7-mvIzyUA2pGkgFPcjstkKVhCwLbbKsKVmHseIxda7CDNpjqnHfmp3ZqYDpI0Bo3yK76V5sBskn_Wu4NBSJKTj3gFwr2CtGUeu5FJruL765ehCOhNIk4-3BXxJfQ8Q9Aj25TdgEh5xRxoveaKrnW07Mh_gM7yo0pAZaSEIxIYuBtn_-HeLLWDnYrB9AstYNVoOQSm5onL9I3IKH_0JGGSrG_vS9jmytLkuHEHnT379jXBTqUieRJyNQh_eUzu-hQIHeYjlUs8kSHwkf9JlgG1ziNljVNeFVbve46LsIhi9n1x59Q15ez0LdjeGQ \ No newline at end of file +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaW1zZ2xvYmFsLmdpdGh1Yi5pby9vcGVuYmFkZ2VzLXNwZWNpZmljYXRpb24vY29udGV4dC5qc29uIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwidHlwZSI6WyJQcm9maWxlIl0sIm5hbWUiOiJFeGFtcGxlIFVuaXZlcnNpdHkifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQwMDowMDowMFoiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IERlZ3JlZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.G7W8od9rSZRsVyk26rXjg_fH2CyUihwNpepd6tWgLt_UHC1vUU0Clox8IicnOSkMyYEqAuNZAdCC9_35i1oUcyj1c076Aa0dsVQ2fFVuQPqXBlyZWcBmo5jqOK6R9NHzRAYXwLRXgrB8gz3lSK55cnHTnMtkpXXcUcHkS5ylWbXCLeOWKoygOCuxRN3N6kP-0HOyuk15PWlnkJ2zEKz2pBtVPaNEydcT0kEtoHFMEWVwqo6rnGV-Ea3M7ssDt3145mcl-DVYLXmBVdT8KoO47QAOBaVMR6k-hgrHNBcdhpI-o6IvLIFsGLgrNvWN67i8Z7Baum1mP-HBpsAigdmIpA \ No newline at end of file From dbee83068f9458e4fc211a692c43c3e4c9fa266b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 9 Dec 2022 12:27:48 +0100 Subject: [PATCH 67/93] Got verification object from graph --- .../probe/VerificationDependenciesProbe.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) 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 7e4136b..d563176 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 @@ -39,18 +39,30 @@ public class VerificationDependenciesProbe extends Probe ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); - // TODO: get verification object from graph - String type = jsonNode.get("verification").get("type").asText().strip(); + JsonNode verificationNode = jsonNode.get("verification"); + checkNotNull(verificationNode); + String type = null; + if (verificationNode.isTextual()) { + // get verification from graph + UriResource verificationUriResource = resolveUriResource(ctx, verificationNode.asText().strip()); + JsonLdGeneratedObject verificationObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(verificationUriResource)); + JsonNode verificationRootNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(verificationObject.getJson()); + type = verificationRootNode.get("type").asText().strip(); + } else { + type = verificationNode.get("type").asText().strip(); + } + if ("HostedBadge".equals(type)) { // get badge UriResource badgeUriResource = resolveUriResource(ctx, jsonNode.get("badge").asText().strip()); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); - - // get issuer from badge JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) .readTree(badgeObject.getJson()); + // get issuer from badge UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( From a4d8a1692fd7aca2b8e30a45148bdffcf7cfaa77 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 9 Dec 2022 14:27:09 +0100 Subject: [PATCH 68/93] Added JWT signature verification --- .../oneedtech/inspect/vc/OB20Inspector.java | 11 +- .../vc/probe/VerificationJWTProbe.java | 127 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java 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 5002214..3421ffd 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 @@ -35,6 +35,7 @@ import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe; +import org.oneedtech.inspect.vc.probe.VerificationJWTProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; @@ -150,12 +151,20 @@ public class OB20Inspector extends Inspector { } // verification and revocation - for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), new AssertionRevocationListProbe(assertion.getId()))) { + for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), + new AssertionRevocationListProbe(assertion.getId()))) { probeCount++; accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } + // JWS verification + if (assertion.getJwt().isPresent()) { + probeCount++; + accumulator.add(new VerificationJWTProbe(assertion.getJwt().get()).run(jsonLdGeneratedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + } catch (Exception e) { accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java new file mode 100644 index 0000000..c75fbb5 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java @@ -0,0 +1,127 @@ +package org.oneedtech.inspect.vc.probe; + +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.List; + +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.util.resource.UriResource; +import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; +import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; + +/** + * Recipient Verification probe for Open Badges 2.0 + * Maps to "VERIFY_JWS" task in python implementation + * @author xaracil + */ +public class VerificationJWTProbe extends Probe { + final String jwt; + + public VerificationJWTProbe(String jwt) { + super(ID); + this.jwt = jwt; + } + + @Override + public ReportItems run(JsonLdGeneratedObject assertion, RunContext ctx) throws Exception { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + JsonNode assertionNode = (mapper).readTree(assertion.getJson()); + + // get badge from assertion + UriResource badgeUriResource = resolveUriResource(ctx, assertionNode.get("badge").asText().strip()); + JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(badgeUriResource)); + JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(badgeObject.getJson()); + + // get issuer from badge + UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + + JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(issuerUriResource)); + JsonNode issuerNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(issuerObject.getJson()); + + // get verification from assertion + JsonNode creatorIdNode = assertionNode.get("verification").get("creator"); + String creatorId = null; + if (creatorIdNode != null) { + creatorId = creatorIdNode.asText().strip(); + } else { + // If not present, verifiers will check public key(s) declared in the referenced issuer Profile. + creatorId = issuerNode.get("publicKeyPem").asText().strip(); + } + + // get creator from id + UriResource creatorUriResource = resolveUriResource(ctx, creatorId); + JsonLdGeneratedObject creatorObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( + JsonLDCompactionProve.getId(creatorUriResource)); + JsonNode creatorNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) + .readTree(creatorObject.getJson()); + + // verify key ownership + String keyId = creatorNode.get("id").asText().strip(); + List issuerKeys = JsonNodeUtil.asStringList(issuerNode.get("publicKey")); + if (!issuerKeys.contains(keyId)) { + return error("Assertion signed by a key " + keyId + " other than those authorized by issuer profile", ctx); + } + String publicKeyPem = creatorNode.get("publicKeyPem").asText().strip(); + + // verify signature + publicKeyPem = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", ""); + publicKeyPem = publicKeyPem.replace("-----END PUBLIC KEY-----", ""); + publicKeyPem = publicKeyPem.replace("\n", ""); + + byte[] encodedPb = Base64.getDecoder().decode(publicKeyPem); + X509EncodedKeySpec keySpecPb = new X509EncodedKeySpec(encodedPb); + RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpecPb); + + JWSObject jwsObject = JWSObject.parse(jwt); + JWSVerifier verifier = new RSASSAVerifier(publicKey); + try { + if (!jwsObject.verify(verifier)) { + return error("Signature for node " + assertionNode.get("id") + " failed verification ", ctx); + } + } catch (JOSEException e) { + return fatal("Signature for node " + assertionNode.get("id") + " failed verification " + e.getLocalizedMessage(), ctx); + } + return success(ctx); + } + + 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; + } + + private static final List allowedTypes = List.of("id", "email", "url", "telephone"); + public static final String ID = VerificationJWTProbe.class.getSimpleName(); + +} From 48a3de0968c6f19ee64398f54bcce99febc016b9 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 09:39:12 +0100 Subject: [PATCH 69/93] Added basic language test --- .../oneedtech/inspect/vc/OB20Inspector.java | 25 +++++++++++-------- .../org/oneedtech/inspect/vc/OB20Tests.java | 10 ++++++++ .../org/oneedtech/inspect/vc/Samples.java | 2 ++ .../ob20/badge-class-with-language.json | 10 ++++++++ 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/badge-class-with-language.json 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 3421ffd..dad41ef 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 @@ -21,6 +21,7 @@ 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.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.GraphFetcherProbe; @@ -151,18 +152,20 @@ public class OB20Inspector extends Inspector { } // verification and revocation - for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), - new AssertionRevocationListProbe(assertion.getId()))) { - probeCount++; - accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - } + if (assertion.getCredentialType() == Type.Assertion) { + for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), + new AssertionRevocationListProbe(assertion.getId()))) { + probeCount++; + accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } - // JWS verification - if (assertion.getJwt().isPresent()) { - probeCount++; - accumulator.add(new VerificationJWTProbe(assertion.getJwt().get()).run(jsonLdGeneratedObject, ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + // JWS verification + if (assertion.getJwt().isPresent()) { + probeCount++; + accumulator.add(new VerificationJWTProbe(assertion.getJwt().get()).run(jsonLdGeneratedObject, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } } 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 1e13614..f4348f7 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 @@ -98,6 +98,16 @@ public class OB20Tests { }); } + @Test + void testLanguageInBadgeClass() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_LANGUAGE_BADGECLASS.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + // check than + }); + } + @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 6de1cba..daabbaa 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 @@ -53,6 +53,8 @@ public class Samples { public final static Sample WARNING_ISSUER_NON_HTTPS_JSON = new Sample("ob20/warning-issuer-non-http.json", true); // original: test_validation: test_can_input_badgeclass public final static Sample SIMPLE_BADGECLASS = new Sample("ob20/assets/badgeclass1.json", true); + // original: validate_language: validate_language_prop_basic + public final static Sample SIMPLE_LANGUAGE_BADGECLASS = new Sample("ob20/badge-class-with-language.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/badge-class-with-language.json b/inspector-vc/src/test/resources/ob20/badge-class-with-language.json new file mode 100644 index 0000000..36b8e2e --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/badge-class-with-language.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/badgeclass", + "@language": "en-US", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer1.json", + "type": "BadgeClass" +} \ No newline at end of file From e4da3458694fa1f87b4ef83ed369014760474f87 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 10:40:12 +0100 Subject: [PATCH 70/93] Added test for assertion withou publick key --- .../org/oneedtech/inspect/vc/OB20Tests.java | 10 ++++++++++ .../java/org/oneedtech/inspect/vc/Samples.java | 2 ++ .../ob20/assets/basic-badge-no-public-key.json | 10 ++++++++++ .../assets/organization-no-public-key.json | 8 ++++++++ .../ob20/basic-assertion-no-public-key.json | 18 ++++++++++++++++++ 5 files changed, 48 insertions(+) create mode 100644 inspector-vc/src/test/resources/ob20/assets/basic-badge-no-public-key.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/organization-no-public-key.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-no-public-key.json 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 f4348f7..8f6d59e 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 @@ -108,6 +108,16 @@ public class OB20Tests { }); } + @Test + void testNoPublicKeyInIssuer() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_ISSUER_WITHOUT_PUBLIC_KEY_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + // check than + }); + } + @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 daabbaa..e7ee0e6 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 @@ -47,6 +47,8 @@ public class Samples { public final static Sample SIMPLE_ASSERTION_JSON = new Sample("ob20/basic-assertion.json", true); public final static Sample SIMPLE_ASSERTION_INVALID_CONTEXT_JSON = new Sample("ob20/basic-assertion-invalid-context.json", true); public final static Sample SIMPLE_ASSERTION_INVALID_TYPE_JSON = new Sample("ob20/basic-assertion-invalid-type.json", true); + // original: + public final static Sample SIMPLE_ASSERTION_ISSUER_WITHOUT_PUBLIC_KEY_JSON = new Sample("ob20/basic-assertion-no-public-key.json", true); // original: test_graph: test_verify_with_redirection public final static Sample WARNING_REDIRECTION_ASSERTION_JSON = new Sample("ob20/warning-with-redirection.json", true); // original: test_validation: test_issuer_warn_on_non_https_id diff --git a/inspector-vc/src/test/resources/ob20/assets/basic-badge-no-public-key.json b/inspector-vc/src/test/resources/ob20/assets/basic-badge-no-public-key.json new file mode 100644 index 0000000..f47916b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/basic-badge-no-public-key.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/basic-badge-no-public-key.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-no-public-key.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/organization-no-public-key.json b/inspector-vc/src/test/resources/ob20/assets/organization-no-public-key.json new file mode 100644 index 0000000..9687d0b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/organization-no-public-key.json @@ -0,0 +1,8 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization-no-public-key.json", + "name": "An Example Badge Issuer", + "url": "https://example.org", + "email": "contact@example.org" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-no-public-key.json b/inspector-vc/src/test/resources/ob20/basic-assertion-no-public-key.json new file mode 100644 index 0000000..e68a678 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-no-public-key.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/basic-badge-no-public-key.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From c7f4dc8afa1acb5672dde95d1ab8e11a3465b43a Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 10:41:04 +0100 Subject: [PATCH 71/93] Fixed regular expression validation --- .../vc/probe/validation/ValidationIssuerPropertyProbe.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java index 62ab1d4..a9ebb17 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java @@ -1,5 +1,7 @@ package org.oneedtech.inspect.vc.probe.validation; +import java.util.regex.Pattern; + import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.Validation; @@ -24,7 +26,7 @@ public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe { @Override protected ReportItems validate(JsonNode node, RunContext ctx) { - if (!node.asText().matches("^http(s)?://")) { + if (!Pattern.compile("^http(s)?://.+", Pattern.CASE_INSENSITIVE).matcher(node.asText()).matches()) { return buildResponse("Issuer Profile " + node.toString() + " not hosted with HTTP-based identifier." + "Many platforms can only handle HTTP(s)-hosted issuers.", ctx); } From d852003526353ef2cad84ac47232cec17a108146 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 11:10:19 +0100 Subject: [PATCH 72/93] Fix issuer url --- inspector-vc/src/test/resources/ob20/assets/badgeclass1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json index f0f79b2..117d4c5 100644 --- a/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass1.json @@ -203,6 +203,6 @@ "name": "Example Badge", "description": "An example", "criteria": "http://example.com/badgecriteria.json", - "issuer": "http://example.org/issuer1", + "issuer": "http://example.org/issuer1.json", "image": "http://example.org/robotics-badge.png" } \ No newline at end of file From 6eb8b66560095a61433e758f8f7ae19b20807b05 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 11:10:38 +0100 Subject: [PATCH 73/93] Added issuer with compact iri test --- .../org/oneedtech/inspect/vc/OB20Tests.java | 20 +++++++++++++------ .../org/oneedtech/inspect/vc/Samples.java | 2 ++ .../ob20/issuer-compact-iri-validation.json | 11 ++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/issuer-compact-iri-validation.json 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 8f6d59e..e95e668 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 @@ -59,12 +59,11 @@ public class OB20Tests { @Test void testSimpleBadgeClassJsonValid() { - // TODO: commented out due to lack of prerequisite tasks yet - // assertDoesNotThrow(()->{ - // Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource()); - // if(verbose) PrintHelper.print(report, true); - // assertValid(report); - // }); + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); } @Test @@ -118,6 +117,15 @@ public class OB20Tests { }); } + @Test + void testCompactIriInIssuer() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_COMPACTIRI_VALIDATION.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(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 e7ee0e6..09ac1e7 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 @@ -55,6 +55,8 @@ public class Samples { public final static Sample WARNING_ISSUER_NON_HTTPS_JSON = new Sample("ob20/warning-issuer-non-http.json", true); // original: test_validation: test_can_input_badgeclass public final static Sample SIMPLE_BADGECLASS = new Sample("ob20/assets/badgeclass1.json", true); + // original: test_validation: test_validate_compacted_iri_value + public final static Sample ISSUER_COMPACTIRI_VALIDATION = new Sample("ob20/issuer-compact-iri-validation.json", true); // original: validate_language: validate_language_prop_basic public final static Sample SIMPLE_LANGUAGE_BADGECLASS = new Sample("ob20/badge-class-with-language.json", true); } diff --git a/inspector-vc/src/test/resources/ob20/issuer-compact-iri-validation.json b/inspector-vc/src/test/resources/ob20/issuer-compact-iri-validation.json new file mode 100644 index 0000000..23f62cd --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/issuer-compact-iri-validation.json @@ -0,0 +1,11 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization.json", + "url": "https://example.org", + "name": "An Example Badge Issuer", + "email": "contact@example.org", + "verification": { + "verificationProperty": "id" + } +} \ No newline at end of file From de610f7f3eec2664bd8e2e1865a8a6ed4b7606ca Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 11:10:48 +0100 Subject: [PATCH 74/93] Added tests for validations --- .../vc/util/PrimitiveValueValidator.java | 63 +++++-- .../vc/util/PrimitiveValueValidatorTests.java | 162 ++++++++++++++++++ 2 files changed, 213 insertions(+), 12 deletions(-) create mode 100644 inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidatorTests.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java index 8303fe2..e42453f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidator.java @@ -6,13 +6,19 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; import java.util.IllformedLocaleException; import java.util.List; import java.util.Locale; +import java.util.regex.Pattern; import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.util.json.ObjectMapperCache.Config; import com.apicatalog.jsonld.JsonLd; import com.apicatalog.jsonld.JsonLdError; @@ -20,11 +26,9 @@ import com.apicatalog.jsonld.document.JsonDocument; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.io.Resources; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; - /** * Validator for ValueType. Translated into java from PrimitiveValueValidator in validation.py */ @@ -39,13 +43,30 @@ public class PrimitiveValueValidator { return true; } + ObjectMapper mapper = ObjectMapperCache.get(Config.DEFAULT); // TODO: get from RunContext + + try { + JsonNode node = mapper.readTree(Resources.getResource("contexts/ob-v2p0.json")); + ObjectReader readerForUpdating = mapper.readerForUpdating(node); + JsonNode merged = readerForUpdating.readValue("{\"" + value.asText() + "\" : \"TEST\"}"); + JsonDocument jsonDocument = JsonDocument.of(new StringReader(merged.toString())); + + JsonNode expanded = mapper.readTree(JsonLd.expand(jsonDocument).get().toString()); + if (expanded.isArray() && ((ArrayNode) expanded).size() > 0) { + return true; + } + + } catch (NullPointerException | IOException | JsonLdError e) { + return false; + } + return false; } public static boolean validateDataUri(JsonNode value) { try { URI uri = new URI(value.asText()); - return "data".equalsIgnoreCase(uri.getScheme()); + return "data".equalsIgnoreCase(uri.getScheme()) && uri.getSchemeSpecificPart().contains(","); } catch (Throwable ignored) { } return false; @@ -55,13 +76,28 @@ public class PrimitiveValueValidator { return validateUrl(value) || validateDataUri(value); } + private static DateTimeFormatter ISO_OFFSET_TIME_JOINED = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .parseLenient() + .appendOffset("+Hmmss", "Z") + .parseStrict() + .toFormatter(); + public static boolean validateDatetime(JsonNode value) { - try { - DateTimeFormatter.ISO_INSTANT.parse(value.asText()); - return true; - } catch (DateTimeParseException | NullPointerException ignored) { - } - return false; + boolean valid = List.of(ISO_OFFSET_TIME_JOINED, + DateTimeFormatter.ISO_OFFSET_DATE_TIME, + DateTimeFormatter.ISO_INSTANT) + .stream().anyMatch(formatter -> { + try { + formatter.parse(value.asText()); + return true; + } catch (DateTimeParseException | NullPointerException ignored) { + return false; + } + }); + + return valid; } public static boolean validateEmail(JsonNode value) { @@ -88,7 +124,10 @@ public class PrimitiveValueValidator { * @return */ public static boolean validateIri(JsonNode value) { - return validateUrl(value) || value.asText().matches("^_:") || value.asText().matches("^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"); + return + Pattern.compile("^_:.+", Pattern.CASE_INSENSITIVE).matcher(value.asText()).matches() + || Pattern.compile("^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", Pattern.CASE_INSENSITIVE).matcher(value.asText()).matches() + || validateUrl(value); } public static boolean validateLanguage(JsonNode value) { @@ -109,7 +148,7 @@ public class PrimitiveValueValidator { return false; } - ObjectMapper mapper = new ObjectMapper(); // TODO: get from RunContext + ObjectMapper mapper = ObjectMapperCache.get(Config.DEFAULT); // TODO: get from RunContext JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); // TODO: get from RunContext try { diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidatorTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidatorTests.java new file mode 100644 index 0000000..8cd2052 --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/PrimitiveValueValidatorTests.java @@ -0,0 +1,162 @@ +package org.oneedtech.inspect.vc.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.vc.Assertion.ValueType; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Test case for PrimitiveValueValidator. + * Maps to "PropertyValidationTests" in python implementation + */ +public class PrimitiveValueValidatorTests { + private static ObjectMapper mapper; + + @BeforeAll + static void setup() { + mapper = ObjectMapperCache.get(DEFAULT); + } + + @Test + void testDataUri() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("data:image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs=", + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", + "data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678", + "data:text/vnd-example+xyz;foo=bar;base64,R0lGODdh", + "data:,actually%20a%20valid%20data%20URI", + "data:,"); + List badValues = List.of("data:image/gif", + "http://someexample.org", + "data:bad:path"); + assertFunction(ValueType.DATA_URI, goodValues, badValues); + } + + @Test + void testDataUriOrUrl() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("data:image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs=", + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", + "data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678", + "data:text/vnd-example+xyz;foo=bar;base64,R0lGODdh", + "http://www.example.com:8080/", "http://www.example.com:8080/foo/bar", + "http://www.example.com/foo%20bar", "http://www.example.com/foo/bar?a=b&c=d", + "http://www.example.com/foO/BaR", "HTTPS://www.EXAMPLE.cOm/", + "http://142.42.1.1:8080/", "http://142.42.1.1/", + "http://foo.com/blah_(wikipedia)#cite-1", "http://a.b-c.de", + "http://userid:password@example.com/", "http://-.~:%40:80%2f:password@example.com", + "http://code.google.com/events/#&product=browser"); + List badValues = List.of("///", "///f", "//", + "rdar://12345", "h://test", ":// should fail", "", "a", + "urn:uuid:129487129874982374", "urn:uuid:9d278beb-36cf-4bc8-888d-674ff9843d72"); + assertFunction(ValueType.DATA_URI_OR_URL, goodValues, badValues); + } + + @Test + void testUrl() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("http://www.example.com:8080/", "http://www.example.com:8080/foo/bar", + "http://www.example.com/foo%20bar", "http://www.example.com/foo/bar?a=b&c=d", + "http://www.example.com/foO/BaR", "HTTPS://www.EXAMPLE.cOm/", + "http://142.42.1.1:8080/", "http://142.42.1.1/", "http://localhost:3000/123", + "http://foo.com/blah_(wikipedia)#cite-1", "http://a.b-c.de", + "http://userid:password@example.com/", "http://-.~:%40:80%2f:password@example.com", + "http://code.google.com/events/#&product=browser"); + List badValues = List.of("data:image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs=", "///", "///f", "//", + "rdar://12345", "h://test", ":// should fail", "", "a", + "urn:uuid:129487129874982374", "urn:uuid:9d278beb-36cf-4bc8-888d-674ff9843d72"); + assertFunction(ValueType.URL, goodValues, badValues); + } + + @Test + void testIri() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("http://www.example.com:8080/", "_:b0", "_:b12", "_:b107", "_:b100000001232", + "urn:uuid:9d278beb-36cf-4bc8-888d-674ff9843d72", + "urn:uuid:9D278beb-36cf-4bc8-888d-674ff9843d72"); + List badValues = List.of("data:image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs=", "urn:uuid", "urn:uuid:123", + "", "urn:uuid:", "urn:uuid:zz278beb-36cf-4bc8-888d-674ff9843d72"); + assertFunction(ValueType.IRI, goodValues, badValues); + } + + @Test + void testUrlAuthority() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("google.com", "nerds.example.com"); + List badValues = List.of("666", "http://google.com/", "https://www.google.com/search?q=murder+she+wrote&oq=murder+she+wrote", + "ftp://123.123.123.123", "bears", "lots of hungry bears", "bears.com/thewoods", + "192.168.0.1", "1::6:7:8"); + assertFunction(ValueType.URL_AUTHORITY, goodValues, badValues); + } + + @Test + void testCompactedIRI() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("id", "email", "telephone", "url"); + List badValues = List.of("sloths"); + assertFunction(ValueType.COMPACT_IRI, goodValues, badValues); + } + + @Test + void testBasicText() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("string value"); + List badValues = List.of(3, 4); + assertFunction(ValueType.TEXT, goodValues, badValues); + } + + @Test + void testTelephone() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("+64010", "+15417522845", "+18006664358", "+18006662344;ext=666"); + List badValues = List.of("1-800-666-DEVIL", "1 (555) 555-5555", "+99 55 22 1234", "+18006664343 x666"); + assertFunction(ValueType.TELEPHONE, goodValues, badValues); + } + + @Test + void testEmail() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("abc@localhost", "cool+uncool@example.org"); + List badValues = List.of(" spacey@gmail.com", "steveman [at] gee mail dot com"); + assertFunction(ValueType.EMAIL, goodValues, badValues); + } + + @Test + void testBoolean() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of(true, false); + List badValues = List.of(" spacey@gmail.com", "steveman [at] gee mail dot com"); + assertFunction(ValueType.BOOLEAN, goodValues, badValues); + } + + @Test + void testDateTime() throws JsonMappingException, JsonProcessingException { + List goodValues = List.of("1977-06-10T12:00:00+0800", + "1977-06-10T12:00:00-0800", + "1977-06-10T12:00:00+08", + "1977-06-10T12:00:00+08:00"); + List badValues = List.of("notadatetime", "1977-06-10T12:00:00"); + assertFunction(ValueType.DATETIME, goodValues, badValues); + } + + private void assertFunction(ValueType valueType, List goodValues, List badValues) throws JsonMappingException, JsonProcessingException { + Function validationFunction = valueType.getValidationFunction(); + for (Object goodValue : goodValues) { + assertTrue(validationFunction.apply(parseNode(goodValue)), + "`" + goodValue + "` should pass " + valueType + " validation but failed."); + } + for (Object badValue : badValues) { + assertFalse(validationFunction.apply(parseNode(badValue)), + "`" + badValue + "` should fail " + valueType + " validation but passed."); + } + } + + private JsonNode parseNode(Object value) throws JsonMappingException, JsonProcessingException { + if (value instanceof String) { + return mapper.readTree("\"" + value + "\""); + } + return mapper.readTree(value.toString()); + } +} From 747a93e67082d4fb1835920357e3f065ceebc4a1 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 13:54:29 +0100 Subject: [PATCH 75/93] Added Rdf Type validation test --- .../org/oneedtech/inspect/vc/Assertion.java | 22 ++ .../org/oneedtech/inspect/vc/Credential.java | 1 + .../inspect/vc/VerifiableCredential.java | 4 + .../inspect/vc/probe/TypePropertyProbe.java | 14 +- .../org/oneedtech/inspect/vc/OB20Tests.java | 23 ++ .../org/oneedtech/inspect/vc/Samples.java | 10 + .../ob20/assets/criteria-no-type.json | 203 +++++++++++++++++ .../ob20/assets/issuer-invalid-type.json | 203 +++++++++++++++++ .../badge-class-invalid-issuer-type.json | 10 + .../ob20/rdf-validation/invalid-class.json | 204 +++++++++++++++++ .../rdf-validation/invalid-empty-type.json | 204 +++++++++++++++++ .../invalid-one-invalid-class.json | 204 +++++++++++++++++ .../valid-alignment-object.json | 206 +++++++++++++++++ ...valid-badge-class-empty-criteria-type.json | 207 ++++++++++++++++++ .../rdf-validation/valid-badge-class.json | 207 ++++++++++++++++++ .../ob20/rdf-validation/valid-cool-class.json | 204 +++++++++++++++++ .../valid-issuer-extension.json | 206 +++++++++++++++++ 17 files changed, 2126 insertions(+), 6 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/criteria-no-type.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-invalid-type.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/badge-class-invalid-issuer-type.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/invalid-class.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/invalid-empty-type.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/invalid-one-invalid-class.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/valid-alignment-object.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class-empty-criteria-type.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/valid-cool-class.json create mode 100644 inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 24d0b44..4493f2d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -1,5 +1,7 @@ package org.oneedtech.inspect.vc; +import static org.oneedtech.inspect.vc.util.PrimitiveValueValidator.validateIri; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -84,14 +86,21 @@ public class Assertion extends Credential { VerificationObject(List.of("VerificationObject")), VerificationObjectAssertion(List.of("VerificationObjectAssertion")), VerificationObjectIssuer(List.of("VerificationObjectIssuer")), + External(Collections.emptyList(), false), Unknown(Collections.emptyList()); public static List primaryObjects = List.of(Assertion, BadgeClass, Issuer, Profile, Endorsement); private final List allowedTypeValues; + private final boolean allowedTypeValuesRequired; Type(List typeValues) { + this(typeValues, true); + } + + Type(List typeValues, boolean allowedTypeValuesRequired) { this.allowedTypeValues = typeValues; + this.allowedTypeValuesRequired = allowedTypeValuesRequired; } public static Assertion.Type valueOf (JsonNode typeNode) { @@ -107,6 +116,12 @@ public class Assertion extends Credential { } } } + + // check external type + if (validateIri(typeNode)) { + return External; + } + return Unknown; } @@ -125,6 +140,11 @@ public class Assertion extends Credential { return List.of("https://w3id.org/openbadges/v2") ; } + @Override + public boolean isAllowedTypeValuesRequired() { + return allowedTypeValuesRequired; + } + public List getValidations() { return validationMap.get(this); } @@ -318,6 +338,8 @@ public class Assertion extends Credential { new Validation.Builder().name("startsWith").type(ValueType.URL).build(), new Validation.Builder().name("allowedOrigins").type(ValueType.URL_AUTHORITY).many(true).build() )) + .put(Type.External, Collections.emptyList()) + .put(Type.Unknown, Collections.emptyList()) .build(); public static final String ID = Assertion.class.getCanonicalName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index c7ba710..cebf818 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -90,6 +90,7 @@ public abstract class Credential extends GeneratedObject { public interface CredentialEnum { List getRequiredTypeValues(); List getAllowedTypeValues(); + boolean isAllowedTypeValuesRequired(); List getContextUris(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index ba20cc2..2b29f9f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -107,6 +107,10 @@ public class VerifiableCredential extends Credential { return allowedTypeValues; } + @Override + public boolean isAllowedTypeValuesRequired() { + return true; + } @Override public List getContextUris() { return contextMap.get(contextMap.keySet() diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index 972427c..b53f5bd 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -31,12 +31,14 @@ public class TypePropertyProbe extends StringValuePropertyProbe { } } - List allowedValues = expected.getAllowedTypeValues(); - if (allowedValues.isEmpty()) { - return fatal("The type property is invalid", ctx); - } - if (!values.stream().anyMatch(v -> allowedValues.contains(v))) { - return fatal(formatMessage(values), ctx); + if (expected.isAllowedTypeValuesRequired()) { + List allowedValues = expected.getAllowedTypeValues(); + if (allowedValues.isEmpty()) { + return fatal("The type property is invalid", ctx); + } + if (!values.stream().anyMatch(v -> allowedValues.contains(v))) { + return fatal(formatMessage(values), ctx); + } } return success(ctx); 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 e95e668..b8eb96f 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 @@ -126,6 +126,29 @@ public class OB20Tests { }); } + @Test + void testRdfValidation() { + List.of(Samples.OB20.JSON.RDF_VALIDATION_VALID_BADGE_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_VALID_ISSUER_EXTENSION_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_VALID_ALIGNMENT_OBJECT, + Samples.OB20.JSON.RDF_VALIDATION_VALID_EXTERNAL_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_EMPTY_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_ELEM_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_ISSUER_TYPE, + Samples.OB20.JSON.RDF_VALIDATION_VALID_EMPTY_CRITERIA_TYPE).forEach(resource -> { + assertDoesNotThrow(()->{ + Report report = validator.run(resource.asFileResource()); + if(verbose) PrintHelper.print(report, true); + if(resource.isValid()) { + assertValid(report); + } else { + 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 09ac1e7..fc2ccbc 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 @@ -59,6 +59,16 @@ public class Samples { public final static Sample ISSUER_COMPACTIRI_VALIDATION = new Sample("ob20/issuer-compact-iri-validation.json", true); // original: validate_language: validate_language_prop_basic public final static Sample SIMPLE_LANGUAGE_BADGECLASS = new Sample("ob20/badge-class-with-language.json", true); + // original: test_validation: test_validate_in_context_string_type + public final static Sample RDF_VALIDATION_VALID_BADGE_CLASS = new Sample("ob20/rdf-validation/valid-badge-class.json", true); + public final static Sample RDF_VALIDATION_VALID_ISSUER_EXTENSION_CLASS = new Sample("ob20/rdf-validation/valid-issuer-extension.json", true); + public final static Sample RDF_VALIDATION_VALID_ALIGNMENT_OBJECT = new Sample("ob20/rdf-validation/valid-alignment-object.json", true); + public final static Sample RDF_VALIDATION_VALID_EXTERNAL_CLASS = new Sample("ob20/rdf-validation/valid-cool-class.json", true); + public final static Sample RDF_VALIDATION_INVALID_CLASS = new Sample("ob20/rdf-validation/invalid-class.json", false); + public final static Sample RDF_VALIDATION_INVALID_EMPTY_CLASS = new Sample("ob20/rdf-validation/invalid-empty-type.json", false); + public final static Sample RDF_VALIDATION_INVALID_ELEM_CLASS = new Sample("ob20/rdf-validation/invalid-one-invalid-class.json", false); + public final static Sample RDF_VALIDATION_INVALID_ISSUER_TYPE = new Sample("ob20/rdf-validation/badge-class-invalid-issuer-type.json", false); + public final static Sample RDF_VALIDATION_VALID_EMPTY_CRITERIA_TYPE = new Sample("ob20/rdf-validation/valid-badge-class-empty-criteria-type.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assets/criteria-no-type.json b/inspector-vc/src/test/resources/ob20/assets/criteria-no-type.json new file mode 100644 index 0000000..38b803a --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/criteria-no-type.json @@ -0,0 +1,203 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "_:b0", + "narrative": "Do the important things." +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-invalid-type.json b/inspector-vc/src/test/resources/ob20/assets/issuer-invalid-type.json new file mode 100644 index 0000000..402e76a --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-invalid-type.json @@ -0,0 +1,203 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-invalid-type", + "type": "Criteria" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/badge-class-invalid-issuer-type.json b/inspector-vc/src/test/resources/ob20/rdf-validation/badge-class-invalid-issuer-type.json new file mode 100644 index 0000000..04031de --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/badge-class-invalid-issuer-type.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/badgeclass", + "@language": "en-US", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-invalid-type.json", + "type": "BadgeClass" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-class.json b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-class.json new file mode 100644 index 0000000..3efddad --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-class.json @@ -0,0 +1,204 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": "NotAKnownClass", + "name": "Chumley" + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-empty-type.json b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-empty-type.json new file mode 100644 index 0000000..d381011 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-empty-type.json @@ -0,0 +1,204 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": [], + "name": "Chumley" + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-one-invalid-class.json b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-one-invalid-class.json new file mode 100644 index 0000000..2109f08 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/invalid-one-invalid-class.json @@ -0,0 +1,204 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": ["Issuer", "UNKNOWN"], + "name": "Chumley" + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-alignment-object.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-alignment-object.json new file mode 100644 index 0000000..9739292 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-alignment-object.json @@ -0,0 +1,206 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": "AlignmentObject", + "name": "Chumley", + "targetName": "target name", + "targetUrl": "http://example.com" + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class-empty-criteria-type.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class-empty-criteria-type.json new file mode 100644 index 0000000..03b6547 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class-empty-criteria-type.json @@ -0,0 +1,207 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": "BadgeClass", + "name": "Chumley", + "description": "An example", + "criteria": "http://example.com/criteria-no-type.json", + "issuer": "http://example.org/issuer1.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class.json new file mode 100644 index 0000000..4a0cee7 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-badge-class.json @@ -0,0 +1,207 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": "BadgeClass", + "name": "Chumley", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer1.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-cool-class.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-cool-class.json new file mode 100644 index 0000000..bf15347 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-cool-class.json @@ -0,0 +1,204 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": "http://example.com/CoolClass", + "name": "Chumley" + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json new file mode 100644 index 0000000..139dac8 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json @@ -0,0 +1,206 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.com/badge1", + "type": ["Issuer", "Extension"], + "name": "Chumley", + "url": "https://example.org", + "email": "contact@example.org" + } \ No newline at end of file From e8a6b3381383d8852d8f306b311d7742f079fabb Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 14:30:15 +0100 Subject: [PATCH 76/93] Added verification dependencies validation --- .../org/oneedtech/inspect/vc/Assertion.java | 2 +- .../oneedtech/inspect/vc/OB20Inspector.java | 4 +- .../org/oneedtech/inspect/vc/OB20Tests.java | 37 ++++ .../org/oneedtech/inspect/vc/Samples.java | 6 + ...fication-invalid-multiple-starts-with.json | 208 +++++++++++++++++ ...with-verification-invalid-starts-with.json | 208 +++++++++++++++++ ...rification-valid-multiple-starts-with.json | 208 +++++++++++++++++ ...s-with-verification-valid-starts-with.json | 208 +++++++++++++++++ .../assets/badgeclass-with-verification.json | 208 +++++++++++++++++ ...-origins-invalid-multiple-starts-with.json | 209 ++++++++++++++++++ ...h-allowed-origins-invalid-starts-with.json | 209 ++++++++++++++++++ ...ed-origins-valid-multiple-starts-with.json | 209 ++++++++++++++++++ ...ith-allowed-origins-valid-starts-with.json | 209 ++++++++++++++++++ .../assets/issuer-with-allowed-origins.json | 209 ++++++++++++++++++ ...-origins-invalid-multiple-starts-with.json | 18 ++ ...h-allowed-origins-invalid-starts-with.json | 18 ++ ...ed-origins-valid-multiple-starts-with.json | 18 ++ ...ith-allowed-origins-valid-starts-with.json | 18 ++ .../basic-assertion-with-allowed-origins.json | 18 ++ 19 files changed, 2221 insertions(+), 3 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-starts-with.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 4493f2d..19d6fa5 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -335,7 +335,7 @@ public class Assertion extends Credential { .put(Type.VerificationObjectIssuer, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.VerificationObject).build(), new Validation.Builder().name("verificationProperty").type(ValueType.COMPACT_IRI).build(), - new Validation.Builder().name("startsWith").type(ValueType.URL).build(), + new Validation.Builder().name("startsWith").type(ValueType.URL).many(true).build(), new Validation.Builder().name("allowedOrigins").type(ValueType.URL_AUTHORITY).many(true).build() )) .put(Type.External, Collections.emptyList()) 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 dad41ef..5ac5b96 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 @@ -153,8 +153,8 @@ public class OB20Inspector extends Inspector { // verification and revocation if (assertion.getCredentialType() == Type.Assertion) { - for(Probe probe : List.of(new VerificationDependenciesProbe(assertion.getId()), - new AssertionRevocationListProbe(assertion.getId()))) { + for(Probe probe : List.of(new VerificationDependenciesProbe(assertionNode.get("id").asText()), + new AssertionRevocationListProbe(assertionNode.get("id").asText()))) { probeCount++; accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); 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 b8eb96f..fec8092 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 @@ -149,6 +149,43 @@ public class OB20Tests { }); } + @Test + void testVerification() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_WITH_ALLOWED_ORIGINS.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + @Test + void testVerificationStartsWith() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_WITH_ALLOWED_ORIGINS_VALID_STARTSWITH.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_WITH_ALLOWED_ORIGINS_INVALID_STARTSWITH.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + + @Test + void testVerificationMultipleStartsWith() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_WITH_ALLOWED_ORIGINS_VALID_MULTIPLE_STARTSWITH.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ISSUER_WITH_ALLOWED_ORIGINS_INVALID_MULTIPLE_STARTSWITH.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 fc2ccbc..7729df6 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 @@ -69,6 +69,12 @@ public class Samples { public final static Sample RDF_VALIDATION_INVALID_ELEM_CLASS = new Sample("ob20/rdf-validation/invalid-one-invalid-class.json", false); public final static Sample RDF_VALIDATION_INVALID_ISSUER_TYPE = new Sample("ob20/rdf-validation/badge-class-invalid-issuer-type.json", false); public final static Sample RDF_VALIDATION_VALID_EMPTY_CRITERIA_TYPE = new Sample("ob20/rdf-validation/valid-badge-class-empty-criteria-type.json", true); + // otiginal: test_validation: test_hosted_verification_object_in_assertion + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS = new Sample("ob20/basic-assertion-with-allowed-origins.json", true); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-starts-with.json", true); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json", false); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json", true); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json", false); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-multiple-starts-with.json new file mode 100644 index 0000000..5d8cbfd --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-multiple-starts-with.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass-with-verification-invalid-multiple-starts-with", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-with-allowed-origins-invalid-multiple-starts-with.json", + "image": "http://example.org/robotics-badge.png" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-starts-with.json new file mode 100644 index 0000000..8ab4a1d --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-invalid-starts-with.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass-with-verification-invalid-starts-with", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-with-allowed-origins-invalid-starts-with.json", + "image": "http://example.org/robotics-badge.png" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-multiple-starts-with.json new file mode 100644 index 0000000..09f8a75 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-multiple-starts-with.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass-with-verification-valid-multiple-starts-with", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-with-allowed-origins-valid-multiple-starts-with.json", + "image": "http://example.org/robotics-badge.png" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-starts-with.json new file mode 100644 index 0000000..c2f3c54 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification-valid-starts-with.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass-with-verification-valid-starts-with", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-with-allowed-origins-valid-starts-with.json", + "image": "http://example.org/robotics-badge.png" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification.json new file mode 100644 index 0000000..5d6a82d --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-verification.json @@ -0,0 +1,208 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/badgeclass-with-verification", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer-with-allowed-origins.json", + "image": "http://example.org/robotics-badge.png" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-multiple-starts-with.json new file mode 100644 index 0000000..1291f2c --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-multiple-starts-with.json @@ -0,0 +1,209 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-with-allowed-origins", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org", + "verification": { + "startsWith": ["https://example.org/NOT", "http://example.com/ALSONOT"] + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-starts-with.json new file mode 100644 index 0000000..fde8434 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-invalid-starts-with.json @@ -0,0 +1,209 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-with-allowed-origins", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org", + "verification": { + "startsWith": "https://example.org/NOT" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-multiple-starts-with.json new file mode 100644 index 0000000..f30a07d --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-multiple-starts-with.json @@ -0,0 +1,209 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-with-allowed-origins", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org", + "verification": { + "startsWith": ["https://example.org/", "http://example.com/assert"] + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-starts-with.json b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-starts-with.json new file mode 100644 index 0000000..c2aa2df --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins-valid-starts-with.json @@ -0,0 +1,209 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-with-allowed-origins", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org", + "verification": { + "startsWith": "https://example.org/" + } +} \ 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 new file mode 100644 index 0000000..5a03aba --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/issuer-with-allowed-origins.json @@ -0,0 +1,209 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "extensions": "https://w3id.org/openbadges/extensions#", + "obi": "https://w3id.org/openbadges#", + "validation": "obi:validation", + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "schema": "http://schema.org/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "AlignmentObject": "schema:AlignmentObject", + "CryptographicKey": "sec:Key", + "Endorsement": "cred:Credential", + "Assertion": "obi:Assertion", + "BadgeClass": "obi:BadgeClass", + "Criteria": "obi:Criteria", + "Evidence": "obi:Evidence", + "Extension": "obi:Extension", + "FrameValidation": "obi:FrameValidation", + "IdentityObject": "obi:IdentityObject", + "Image": "obi:Image", + "HostedBadge": "obi:HostedBadge", + "hosted": "obi:HostedBadge", + "Issuer": "obi:Issuer", + "Profile": "obi:Profile", + "RevocationList": "obi:RevocationList", + "SignedBadge": "obi:SignedBadge", + "signed": "obi:SignedBadge", + "TypeValidation": "obi:TypeValidation", + "VerificationObject": "obi:VerificationObject", + "author": { + "@id": "schema:author", + "@type": "@id" + }, + "caption": { + "@id": "schema:caption" + }, + "claim": { + "@id": "cred:claim", + "@type": "@id" + }, + "created": { + "@id": "dc:created", + "@type": "xsd:dateTime" + }, + "creator": { + "@id": "dc:creator", + "@type": "@id" + }, + "description": { + "@id": "schema:description" + }, + "email": { + "@id": "schema:email" + }, + "endorsement": { + "@id": "cred:credential", + "@type": "@id" + }, + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "genre": { + "@id": "schema:genre" + }, + "image": { + "@id": "schema:image", + "@type": "@id" + }, + "name": { + "@id": "schema:name" + }, + "owner": { + "@id": "sec:owner", + "@type": "@id" + }, + "publicKey": { + "@id": "sec:publicKey", + "@type": "@id" + }, + "publicKeyPem": { + "@id": "sec:publicKeyPem" + }, + "related": { + "@id": "dc:relation", + "@type": "@id" + }, + "startsWith": { + "@id": "http://purl.org/dqm-vocabulary/v1/dqm#startsWith" + }, + "tags": { + "@id": "schema:keywords" + }, + "targetDescription": { + "@id": "schema:targetDescription" + }, + "targetFramework": { + "@id": "schema:targetFramework" + }, + "targetName": { + "@id": "schema:targetName" + }, + "targetUrl": { + "@id": "schema:targetUrl" + }, + "telephone": { + "@id": "schema:telephone" + }, + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "version": { + "@id": "schema:version" + }, + "alignment": { + "@id": "obi:alignment", + "@type": "@id" + }, + "allowedOrigins": { + "@id": "obi:allowedOrigins" + }, + "audience": { + "@id": "obi:audience" + }, + "badge": { + "@id": "obi:badge", + "@type": "@id" + }, + "criteria": { + "@id": "obi:criteria", + "@type": "@id" + }, + "endorsementComment": { + "@id": "obi:endorsementComment" + }, + "evidence": { + "@id": "obi:evidence", + "@type": "@id" + }, + "hashed": { + "@id": "obi:hashed", + "@type": "xsd:boolean" + }, + "identity": { + "@id": "obi:identityHash" + }, + "issuedOn": { + "@id": "obi:issueDate", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "obi:issuer", + "@type": "@id" + }, + "narrative": { + "@id": "obi:narrative" + }, + "recipient": { + "@id": "obi:recipient", + "@type": "@id" + }, + "revocationList": { + "@id": "obi:revocationList", + "@type": "@id" + }, + "revocationReason": { + "@id": "obi:revocationReason" + }, + "revoked": { + "@id": "obi:revoked", + "@type": "xsd:boolean" + }, + "revokedAssertions": { + "@id": "obi:revoked" + }, + "salt": { + "@id": "obi:salt" + }, + "targetCode": { + "@id": "obi:targetCode" + }, + "uid": { + "@id": "obi:uid" + }, + "validatesType": "obi:validatesType", + "validationFrame": "obi:validationFrame", + "validationSchema": "obi:validationSchema", + "verification": { + "@id": "obi:verify", + "@type": "@id" + }, + "verificationProperty": { + "@id": "obi:verificationProperty" + }, + "verify": "verification" + }, + "id": "http://example.org/issuer-with-allowed-origins", + "type": "Issuer", + "name": "Example Issuer", + "email": "me@example.org", + "url": "http://example.org", + "verification": { + "allowedOrigins": ["example.com"] + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json new file mode 100644 index 0000000..93817d1 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/badgeclass-with-verification-invalid-multiple-starts-with.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json new file mode 100644 index 0000000..51a8de0 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/badgeclass-with-verification-invalid-starts-with.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json new file mode 100644 index 0000000..0e1b774 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/badgeclass-with-verification-valid-multiple-starts-with.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-starts-with.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-starts-with.json new file mode 100644 index 0000000..74f1b1e --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins-valid-starts-with.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/badgeclass-with-verification-valid-starts-with.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins.json new file mode 100644 index 0000000..e20b351 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-allowed-origins.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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/badgeclass-with-verification.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From 8daa897e8145fdf8b4daf0c554da03fc91275563 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 15:06:08 +0100 Subject: [PATCH 77/93] Added expiration tests --- .../org/oneedtech/inspect/vc/OB20Tests.java | 27 +++++++++++++++++++ .../org/oneedtech/inspect/vc/Samples.java | 7 ++++- ...basic-assertion-expired-before-issued.json | 19 +++++++++++++ .../ob20/basic-assertion-expired.json | 19 +++++++++++++ .../ob20/basic-assertion-in-future.json | 18 +++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-expired-before-issued.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-expired.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-in-future.json 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 fec8092..f927171 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 @@ -186,6 +186,33 @@ public class OB20Tests { }); } + @Test + void testExpired() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_EXPIRED_ASSERTION_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + + @Test + void testExpiredBeforeIssued() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_EXPIRED_BEFORE_ISSUED_ASSERTION_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + + @Test + void testIssuedInFuture() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.SIMPLE_FUTURE_ASSERTION_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 7729df6..f873f19 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 @@ -74,7 +74,12 @@ public class Samples { public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-starts-with.json", true); public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json", false); public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json", true); - public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json", false); + // original: test_validation: test_assertion_not_expired + public final static Sample SIMPLE_EXPIRED_ASSERTION_JSON = new Sample("ob20/basic-assertion-expired.json", true); + // original: test_validation: test_assertion_not_expires_before_issue + public final static Sample SIMPLE_EXPIRED_BEFORE_ISSUED_ASSERTION_JSON = new Sample("ob20/basic-assertion-expired-before-issued.json", true); + // original: test_validation: test_assertion_not_issued_in_future + public final static Sample SIMPLE_FUTURE_ASSERTION_JSON = new Sample("ob20/basic-assertion-in-future.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-expired-before-issued.json b/inspector-vc/src/test/resources/ob20/basic-assertion-expired-before-issued.json new file mode 100644 index 0000000..8756ff7 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-expired-before-issued.json @@ -0,0 +1,19 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2022-12-31T23:59:59Z", + "expires": "2022-11-15T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-expired.json b/inspector-vc/src/test/resources/ob20/basic-assertion-expired.json new file mode 100644 index 0000000..ac21119 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-expired.json @@ -0,0 +1,19 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2016-12-31T23:59:59Z", + "expires": "2022-11-15T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-in-future.json b/inspector-vc/src/test/resources/ob20/basic-assertion-in-future.json new file mode 100644 index 0000000..d0234d1 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-in-future.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "https://example.org/beths-robot-badge.png", + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2100-12-31T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From 8260d7e7aa7ae4610ee32fae525a12c52382118a Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 15:06:17 +0100 Subject: [PATCH 78/93] Fixed rdf validation tests --- .../org/oneedtech/inspect/vc/OB20Tests.java | 21 +++++++++++-------- .../org/oneedtech/inspect/vc/Samples.java | 11 +++++----- 2 files changed, 18 insertions(+), 14 deletions(-) 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 f927171..2394592 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 @@ -132,21 +132,24 @@ public class OB20Tests { Samples.OB20.JSON.RDF_VALIDATION_VALID_ISSUER_EXTENSION_CLASS, Samples.OB20.JSON.RDF_VALIDATION_VALID_ALIGNMENT_OBJECT, Samples.OB20.JSON.RDF_VALIDATION_VALID_EXTERNAL_CLASS, - Samples.OB20.JSON.RDF_VALIDATION_INVALID_EMPTY_CLASS, - Samples.OB20.JSON.RDF_VALIDATION_INVALID_CLASS, - Samples.OB20.JSON.RDF_VALIDATION_INVALID_ELEM_CLASS, - Samples.OB20.JSON.RDF_VALIDATION_INVALID_ISSUER_TYPE, Samples.OB20.JSON.RDF_VALIDATION_VALID_EMPTY_CRITERIA_TYPE).forEach(resource -> { assertDoesNotThrow(()->{ Report report = validator.run(resource.asFileResource()); if(verbose) PrintHelper.print(report, true); - if(resource.isValid()) { - assertValid(report); - } else { - assertInvalid(report); - } + assertValid(report); }); }); + List.of(Samples.OB20.JSON.RDF_VALIDATION_INVALID_EMPTY_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_ELEM_CLASS, + Samples.OB20.JSON.RDF_VALIDATION_INVALID_ISSUER_TYPE).forEach(resource -> { + assertDoesNotThrow(()->{ + Report report = validator.run(resource.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + }); + } @Test 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 f873f19..5412da7 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 @@ -64,16 +64,17 @@ public class Samples { public final static Sample RDF_VALIDATION_VALID_ISSUER_EXTENSION_CLASS = new Sample("ob20/rdf-validation/valid-issuer-extension.json", true); public final static Sample RDF_VALIDATION_VALID_ALIGNMENT_OBJECT = new Sample("ob20/rdf-validation/valid-alignment-object.json", true); public final static Sample RDF_VALIDATION_VALID_EXTERNAL_CLASS = new Sample("ob20/rdf-validation/valid-cool-class.json", true); - public final static Sample RDF_VALIDATION_INVALID_CLASS = new Sample("ob20/rdf-validation/invalid-class.json", false); - public final static Sample RDF_VALIDATION_INVALID_EMPTY_CLASS = new Sample("ob20/rdf-validation/invalid-empty-type.json", false); - public final static Sample RDF_VALIDATION_INVALID_ELEM_CLASS = new Sample("ob20/rdf-validation/invalid-one-invalid-class.json", false); - public final static Sample RDF_VALIDATION_INVALID_ISSUER_TYPE = new Sample("ob20/rdf-validation/badge-class-invalid-issuer-type.json", false); + public final static Sample RDF_VALIDATION_INVALID_CLASS = new Sample("ob20/rdf-validation/invalid-class.json", true); + public final static Sample RDF_VALIDATION_INVALID_EMPTY_CLASS = new Sample("ob20/rdf-validation/invalid-empty-type.json", true); + public final static Sample RDF_VALIDATION_INVALID_ELEM_CLASS = new Sample("ob20/rdf-validation/invalid-one-invalid-class.json", true); + public final static Sample RDF_VALIDATION_INVALID_ISSUER_TYPE = new Sample("ob20/rdf-validation/badge-class-invalid-issuer-type.json", true); public final static Sample RDF_VALIDATION_VALID_EMPTY_CRITERIA_TYPE = new Sample("ob20/rdf-validation/valid-badge-class-empty-criteria-type.json", true); // otiginal: test_validation: test_hosted_verification_object_in_assertion public final static Sample ISSUER_WITH_ALLOWED_ORIGINS = new Sample("ob20/basic-assertion-with-allowed-origins.json", true); public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-starts-with.json", true); - public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json", false); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-starts-with.json", true); public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_VALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-valid-multiple-starts-with.json", true); + public final static Sample ISSUER_WITH_ALLOWED_ORIGINS_INVALID_MULTIPLE_STARTSWITH = new Sample("ob20/basic-assertion-with-allowed-origins-invalid-multiple-starts-with.json", true); // original: test_validation: test_assertion_not_expired public final static Sample SIMPLE_EXPIRED_ASSERTION_JSON = new Sample("ob20/basic-assertion-expired.json", true); // original: test_validation: test_assertion_not_expires_before_issue From caea0b50f4f33e7ad3a10d59b2af20c77f169b2c Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 16:14:18 +0100 Subject: [PATCH 79/93] Added related validation test, fixing fullValidate --- .../org/oneedtech/inspect/vc/Assertion.java | 12 ++++++------ .../org/oneedtech/inspect/vc/Validation.java | 1 + .../validation/ValidationPropertyProbe.java | 8 ++++---- .../ValidationPropertyProbeFactory.java | 2 +- .../ValidationRdfTypePropertyProbe.java | 4 ++++ .../org/oneedtech/inspect/vc/OB20Tests.java | 9 +++++++++ .../java/org/oneedtech/inspect/vc/Samples.java | 2 ++ .../ob20/assets/badgeclass-with-language.json | 15 +++++++++++++++ .../ob20/basic-assertion-with-language.json | 18 ++++++++++++++++++ 9 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-language.json create mode 100644 inspector-vc/src/test/resources/ob20/basic-assertion-with-language.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 19d6fa5..0cd395f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -201,7 +201,7 @@ public class Assertion extends Credential { new Validation.Builder().name("image").type(ValueType.IMAGE).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Assertion).many(true).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Assertion).many(true).fullValidate(false).build(), new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.BadgeClass, List.of( @@ -216,7 +216,7 @@ public class Assertion extends Credential { new Validation.Builder().name("tags").type(ValueType.TEXT).many(true).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.BadgeClass).many(true).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.BadgeClass).many(true).fullValidate(false).build(), new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.AlignmentObject, List.of( @@ -241,13 +241,13 @@ public class Assertion extends Credential { .put(Type.Endorsement, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.Endorsement)).build(), - new Validation.Builder().name("claim").type(ValueType.ID).required(true).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).build(), + new Validation.Builder().name("claim").type(ValueType.ID).required(true).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).fullValidate(false).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).build(), new Validation.Builder().name("verification").build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Endorsement).many(true).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Endorsement).many(true).fullValidate(false).build(), new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.EndorsementClaim, List.of( @@ -303,7 +303,7 @@ public class Assertion extends Credential { new Validation.Builder().name("id").type(ValueType.ISSUER).messageLevel(MessageLevel.Warning).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Issuer).many(true).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Issuer).many(true).fullValidate(false).build(), new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.Profile, List.of( @@ -320,7 +320,7 @@ public class Assertion extends Credential { new Validation.Builder().name("id").type(ValueType.ISSUER).messageLevel(MessageLevel.Warning).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Profile).many(true).build(), + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Profile).many(true).fullValidate(false).build(), new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.RevocationList, List.of( diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java index 4e2fb2e..7815376 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Validation.java @@ -122,6 +122,7 @@ public class Validation { this.prerequisites = new ArrayList<>(); this.expectedTypes = new ArrayList<>(); this.messageLevel = MessageLevel.Error; + this.fullValidate = true; // by default, full validation } public Builder name(String name) { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index 53e0055..c371d90 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -40,11 +40,11 @@ public class ValidationPropertyProbe extends PropertyProbe { protected final boolean fullValidate; // TODO: fullValidate public ValidationPropertyProbe(Validation validation) { - this(ID, validation, false); + this(ID, validation, true); } public ValidationPropertyProbe(String id, Validation validation) { - this(ID, validation, false); + this(ID, validation, true); } public ValidationPropertyProbe(Validation validation, boolean fullValidate) { @@ -60,10 +60,10 @@ public class ValidationPropertyProbe extends PropertyProbe { @Override protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { - if (validation.isRequired()) { + if (fullValidate && validation.isRequired()) { return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx); } else { - // optional property + // optional property or not doing full validation return success(ctx); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java index 243aa37..816b6bd 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java @@ -11,7 +11,7 @@ import org.oneedtech.inspect.vc.Validation; */ public class ValidationPropertyProbeFactory { public static ValidationPropertyProbe of(Validation validation) { - return of(validation, false); + return of(validation, true); } public static ValidationPropertyProbe of(Validation validation, boolean fullValidate) { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java index fc9ecf1..60bbc15 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java @@ -39,6 +39,10 @@ public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe { } } + // if we're not doing a full validation and it's not and id field, pass + if (!fullValidate && !validation.getName().equals("id")) { + return success(ctx); + } return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx); } 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 2394592..6a8071b 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 @@ -216,6 +216,15 @@ public class OB20Tests { }); } + @Test + void testAssertionWithLanguage() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.BASIC_WITH_LANGUAGE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(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 5412da7..4c1d087 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 @@ -81,6 +81,8 @@ public class Samples { public final static Sample SIMPLE_EXPIRED_BEFORE_ISSUED_ASSERTION_JSON = new Sample("ob20/basic-assertion-expired-before-issued.json", true); // original: test_validation: test_assertion_not_issued_in_future public final static Sample SIMPLE_FUTURE_ASSERTION_JSON = new Sample("ob20/basic-assertion-in-future.json", true); + // original: test_validate_related: test_validate_related_language + public final static Sample BASIC_WITH_LANGUAGE_JSON = new Sample("ob20/basic-assertion-with-language.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-language.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-language.json new file mode 100644 index 0000000..0e0eee7 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-language.json @@ -0,0 +1,15 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/badgeclass-with-language.json", + "@language": "es", + "name": "Insignia fantastica", + "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", + "related": { + "id": "https://example.org/robotics-badge.json", + "@language": "en-US" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/basic-assertion-with-language.json b/inspector-vc/src/test/resources/ob20/basic-assertion-with-language.json new file mode 100644 index 0000000..daf3d18 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/basic-assertion-with-language.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "id": "https://example.org/beths-robotics-badge.json", + "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/badgeclass-with-language.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file From ed74e2a60d4e8a8ecb34ba0bd8703c3db03dcdb1 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 17:19:26 +0100 Subject: [PATCH 80/93] Added revocation list tests --- .../org/oneedtech/inspect/vc/OB20Tests.java | 18 ++++++++++++++++++ .../java/org/oneedtech/inspect/vc/Samples.java | 5 +++++ ...rganization-with-empty-revocation-list.json | 10 ++++++++++ ...from-organization-with-revocation-list.json | 10 ++++++++++ .../ob20/assets/empty-revocation-list.json | 6 ++++++ .../src/test/resources/ob20/assets/key1.json | 2 +- .../src/test/resources/ob20/assets/key2.json | 7 +++++++ .../src/test/resources/ob20/assets/key3.json | 7 +++++++ ...rganization-with-empty-revocation-list.json | 10 ++++++++++ .../organization-with-revocation-list.json | 10 ++++++++++ .../resources/ob20/assets/revocation-list.json | 15 +++++++++++++++ .../test/resources/ob20/simple-not-revoked.jwt | 1 + .../src/test/resources/ob20/simple-revoked.jwt | 1 + .../src/test/resources/ob20/simple.jwt | 2 +- 14 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-empty-revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/empty-revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/key2.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/key3.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/organization-with-empty-revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/organization-with-revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/revocation-list.json create mode 100644 inspector-vc/src/test/resources/ob20/simple-not-revoked.jwt create mode 100644 inspector-vc/src/test/resources/ob20/simple-revoked.jwt 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 6a8071b..0f5f98e 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 @@ -97,6 +97,24 @@ public class OB20Tests { }); } + @Test + void testJWTNotRevoked() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JWT.SIMPLE_NOT_REVOKED_JWT.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + @Test + void testJWTRevoked() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JWT.SIMPLE_REVOKED_JWT.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + @Test void testLanguageInBadgeClass() { assertDoesNotThrow(()->{ 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 4c1d087..e27af23 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,7 +91,12 @@ public class Samples { } public static final class JWT { + // original: test_signed_verification: test_can_full_verify_jws_signed_assertion public final static Sample SIMPLE_JWT = new Sample("ob20/simple.jwt", true); + // original: test_signed_verification: test_can_full_verify_with_revocation_check + public final static Sample SIMPLE_NOT_REVOKED_JWT = new Sample("ob20/simple-not-revoked.jwt", true); + // original: test_signed_verification: test_revoked_badge_marked_invalid + public final static Sample SIMPLE_REVOKED_JWT = new Sample("ob20/simple-revoked.jwt", true); } } } diff --git a/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-empty-revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-empty-revocation-list.json new file mode 100644 index 0000000..401502b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-empty-revocation-list.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/badge-from-organization-with-empty-revocation-list.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": "http://example.com/badgecriteria.json", + "issuer": "https://example.org/organization-with-empty-revocation-list.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-revocation-list.json new file mode 100644 index 0000000..68e64fd --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badge-from-organization-with-revocation-list.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/badge-from-organization-with-revocation-list.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": "http://example.com/badgecriteria.json", + "issuer": "https://example.org/organization-with-revocation-list.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/empty-revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/empty-revocation-list.json new file mode 100644 index 0000000..4a0e2d5 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/empty-revocation-list.json @@ -0,0 +1,6 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/empty-revocation-list.json", + "type": "RevocationList", + "revokedAssertions": [] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/key1.json b/inspector-vc/src/test/resources/ob20/assets/key1.json index b37cf80..4e9f1b0 100644 --- a/inspector-vc/src/test/resources/ob20/assets/key1.json +++ b/inspector-vc/src/test/resources/ob20/assets/key1.json @@ -3,5 +3,5 @@ "id": "http://example.org/key1.json", "type": "CryptographicKey", "owner": "https://example.org/organization.json", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq/tyy5CGrCVfGIMrMkVq\nZ5cAKkbLdrNrNFvXNMSazvKj/9VmuALZOjqXl1Od/Ku6q+18sn70p6r25kGRoaU/\nAJNLt36EnRSs9I66xZ3tOY4c9PeWpMC1XbCrUBh1rpx7XQ5wULOMbttEvBf9V2Cl\nCGiNqwqcafrQHoYteAtF4d2zcjNm+xZRQmnVU0CdvXSXeniWjqyif/751/M/xb93\nfeF1AIFS5IGaI1cWca7BG2izKz1DjZxbMlvQVZk1Axz2Uj/FXssyBKmAdCw4EjuF\nqinQBmKwmFvCB3yhMAK6+f1k4hsR/ET/PxwkNQZfu+gkmEAQoYAMSPgGS3xY3LVf\nxwIDAQAB\n-----END PUBLIC KEY-----" + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAko9t/QZEwRUainRO5MqY\nGBBeFe7rm/BVg6ugkgkyRGdDkjCeEjNfCm6An+bQqeCsHoMCprs629/a9yJ58/4w\nP7ws/5lJ9YwZYXpGvLfha0xrpWl5DiExeOMB9pB8p4ze0AI6H49w9ZX0G+cgaqaK\n/FbQ/Ln1zkZH6+VaAntoaLu3I5kLLR1F31HqLmSKpm6rHyLFkq534dGfuNmjSM+x\nZxBKu+ugmyeL9DinHvBZ/dwFMGS+o4/bT54UeLMd7a71ONevRY2hXgwarfWjqkDs\nAgqnfKSDjjDZQDM2OlyLWvyI2SWF4eWxIUtWuFP4j0d/5SUFjhAldRLjIXMQWMEN\nNwIDAQAB\n-----END PUBLIC KEY-----" } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/key2.json b/inspector-vc/src/test/resources/ob20/assets/key2.json new file mode 100644 index 0000000..3fd6732 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/key2.json @@ -0,0 +1,7 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/key2.json", + "type": "CryptographicKey", + "owner": "https://example.org/organization-with-empty-revocation-list.json", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjjtSHqVnCOhOe91mt1hh\nWa0h6qF5K32ETY3+6ENQ7kR6qvI1S4prxptYfkuCncJFeZkCu306QS8E/iYCUEoC\ndKOYMVtn1Tm16p2cD+d4LQNYDQbKyKIqU8Cgqb3GoS2MWlJ6Q9sVW1B24RYAokW9\npm80xFJMJAIbZKBfr/hMLofGD5vvBHV/UFYtGvy7PByA7eNPkSLFnIY2L6WBWVyE\na2o95Jzvih+H2CmAk6HSumJL/PdV3yys/m4TEmU0V3m7B8voa3yEIFYG4hKxGUCi\nRqaq/wTFsTVBsEZn9rcpjG58ibjfd1+HBhWjRqsa5NNM9sQqnvvNBSWnqE5ggiNc\nzQIDAQAB\n-----END PUBLIC KEY-----" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/key3.json b/inspector-vc/src/test/resources/ob20/assets/key3.json new file mode 100644 index 0000000..f66e27c --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/key3.json @@ -0,0 +1,7 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/key3.json", + "type": "CryptographicKey", + "owner": "https://example.org/organization-with-revocation-list.json", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlxjYVv/2hAXO/D9b/Ifq\ng4n8TQCG3tq6JUyQPrhW27E9htH5UexdbOAAlzydQ5kYve3F8fhHuue/mjnKrMJn\najdTU4BBIPT4CJ3ZCCzKZf9OvMwobZT5P5QmkG1eVLOTGFs9BTvpbGQ7qugUbxHH\nXeHK/OoBnenKmLV0R4+KePe0oTcNToDQbx0uMZzNM9TRpnVBHlCXwYbFzw1js0Tt\nuT4M8MtKOoyNf+ZA8fcsSpmE7K1xDYOY7o9tZMFtGIQuhKlZyVpKpAu4KrhrZBJG\nJ47zcxzrncQHmft3aL8qWNCtZ+fOa+/fPkjT+bPhCwHSkE9Xj8Q0AF1vmDNReun1\nhwIDAQAB\n-----END PUBLIC KEY-----" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/organization-with-empty-revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/organization-with-empty-revocation-list.json new file mode 100644 index 0000000..1f6ce56 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/organization-with-empty-revocation-list.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization-with-empty-revocation-list.json", + "name": "An Example Badge Issuer", + "url": "https://example.org", + "email": "contact@example.org", + "revocationList": "http://example.org/empty-revocation-list.json", + "publicKey": "http://example.org/key2.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/organization-with-revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/organization-with-revocation-list.json new file mode 100644 index 0000000..6e4d47b --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/organization-with-revocation-list.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Issuer", + "id": "https://example.org/organization-with-revocation-list.json", + "name": "An Example Badge Issuer", + "url": "https://example.org", + "email": "contact@example.org", + "revocationList": "http://example.org/revocation-list.json", + "publicKey": "http://example.org/key3.json" +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/revocation-list.json b/inspector-vc/src/test/resources/ob20/assets/revocation-list.json new file mode 100644 index 0000000..809f645 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/revocation-list.json @@ -0,0 +1,15 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/revocation-list.json", + "type": "RevocationList", + "revokedAssertions": [ + { + "id": "https://example.org/beths-robotics-badge-revoked.json", + "revocationReason": "A good reason, for sure" + }, + { + "id": "urn:uuid:52e4c6b3-8c13-4fa8-8482-a5cf34ef37a9" + }, + "urn:uuid:6deb4a00-ebce-4b28-8cc2-afa705ef7be4" + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/simple-not-revoked.jwt b/inspector-vc/src/test/resources/ob20/simple-not-revoked.jwt new file mode 100644 index 0000000..b60fb91 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/simple-not-revoked.jwt @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmFkZ2UtZnJvbS1vcmdhbml6YXRpb24td2l0aC1lbXB0eS1yZXZvY2F0aW9uLWxpc3QuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkyLmpzb24ifX0.gLjqNnlBSJ3oWt84iPhVaFjYGR2bhrSLDnTHh5mcjujO_VxYFop5EJlGHV4C7UC6xnM01Yy4y8JxqCIvLLudVRrPtK3pYumWJvGnc43cNyjZq8IY6L1qrsklP_gJyV0P9IKUzh6kQDdSGx_1TwYY3qwKlLXqNuxqkjdcD0z3zlhYfCnEpk7GZAXP_1Np123g-R9i3OZ18cxcUrCo1W7fZGkH4NXayXvdQpGwKmyCiju0N3ZLbHtfcbdWyVLLDF1RJJuRoVRgETly4uqVu8aFJ9O62HuOJggF7eLTos1gg-M2IpCa7yuUDVqGCWIRun5tbuUbJv_0Kg68lCslpZ09aw \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/simple-revoked.jwt b/inspector-vc/src/test/resources/ob20/simple-revoked.jwt new file mode 100644 index 0000000..b87743a --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/simple-revoked.jwt @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UtcmV2b2tlZC5qc29uIiwicmVjaXBpZW50Ijp7InR5cGUiOiJlbWFpbCIsImhhc2hlZCI6dHJ1ZSwic2FsdCI6ImRlYWRzZWEiLCJpZGVudGl0eSI6InNoYTI1NiRlY2Y1NDA5ZjNmNGI5MWFiNjBjYzVlZjRjMDJhZWY3MDMyMzU0Mzc1ZTcwY2Y0ZDhlNDNmNmExZDI5ODkxOTQyIn0sImltYWdlIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9iZXRocy1yb2JvdC1iYWRnZS5wbmciLCJldmlkZW5jZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3Qtd29yay5odG1sIiwiaXNzdWVkT24iOiIyMDE2LTEyLTMxVDIzOjU5OjU5WiIsImJhZGdlIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9iYWRnZS1mcm9tLW9yZ2FuaXphdGlvbi13aXRoLXJldm9jYXRpb24tbGlzdC5qc29uIiwidmVyaWZpY2F0aW9uIjp7InR5cGUiOiJzaWduZWQiLCJjcmVhdG9yIjoiaHR0cDovL2V4YW1wbGUub3JnL2tleTMuanNvbiJ9fQ.RPJ0gudgtSYwjlZLnMmPHuL_SZmlW46cD3GXE8JQgOk02RwofMUt1w14ey8x7nWexXhvoBIA63qT4S0MfVuMeznAzNMO9_Upm0fworkR500257402NoXTnyzvF8Z8j_bCfoDZrdvljHFCieE1txmhDvRLvR5_y8KaZ6XC3fggokqMUQj6Is6LO4rnazqQixZWT8h_mPbE18_lUi0ocvHdrkwVWIeQi8_B-vgsUOANoBkX9ALBwHnqgfmfOwd-zGV39rBY4lj2nAxoHmY4VClqRj3Qqzoml7bd2eIhFtkJUmxfFuKdcfVhknWqw72MDGb6T-iCKYjuBNhYdxw6i1yQg \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/simple.jwt b/inspector-vc/src/test/resources/ob20/simple.jwt index b02bac2..c4f1558 100644 --- a/inspector-vc/src/test/resources/ob20/simple.jwt +++ b/inspector-vc/src/test/resources/ob20/simple.jwt @@ -1 +1 @@ -eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxLmpzb24ifX0.i2aO8rusbEupHysBBgxZpZBMtLHrGGI2l3Qi-OLxfV20YlD_yRUAVi5D98NyQZsTmHXrvG9x9VCki60ksQqr5vImplKpaq6k4OkvL8VdAt_y6MLD9w-2dTtpgfRFHnWvXcVwImrllUU5wZ1SHAikEpRhAX6Z3BbGvJOo2liCk4g16NazpItLACfKxrNeNMi9vOOyZ3ztBProePa6nQB8ysy7qAQetBioZM17Umm4RjpHkil33PHwkVHndx74rdaUoMcc6mNy7nv_ddEi1l4mK1La9xb62KyXuQKCE1Wj6UzsjTh2wPo-ISzjEJQcY1Ge5-PcrPk35ZQpqDxJyyWaBA \ No newline at end of file +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxLmpzb24ifX0.bR5tt-GEneb7tdWfmDdCq2FCBRuvSnKxNIfMPVz4lXiLcC_MobAB1zC9VQTJ5wJMzyS7Z7SR6eqt5WjuV4gl-6cT1qm3Af4VaKMEpi5bc3w2EGMlw10YxYAIrFEmdAXod9jwA2hPd9k4ypuIIIyzThRAhxVY36vf6nZWkC11MNj0QJoAJ-q2JlaERaGnwSOf1h63jUIyWtsfs6ocXb_hiFIaxPKAqnJSnWeF672vo3lOG_0vM6Tk4Ug73DRzJOsJhGwSWWze_GmwgqtUcWS2b8HmpoEaS_1gXwqnhNxiNNN0a39P-MqP86cY6pb64WKcIVY0ZSnvb4mwzXzZ3cPOTg \ No newline at end of file From 59c96d68d1bb20359651e5e5df2f15586b9ec37f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 18:04:53 +0100 Subject: [PATCH 81/93] Added image validation tests --- .../main/java/org/oneedtech/inspect/vc/Assertion.java | 9 +++++---- .../vc/probe/validation/ValidationPropertyProbe.java | 2 +- .../test/java/org/oneedtech/inspect/vc/OB20Tests.java | 9 +++++++++ .../test/java/org/oneedtech/inspect/vc/Samples.java | 2 ++ .../resources/ob20/assets/badge-with-data-image.json | 10 ++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assets/badge-with-data-image.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 0cd395f..e904a5a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -217,7 +217,8 @@ public class Assertion extends Credential { new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.BadgeClass).many(true).fullValidate(false).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build(), + new Validation.Builder().name("image").type(ValueType.IMAGE).allowDataUri(true).build() )) .put(Type.AlignmentObject, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType(Type.AlignmentObject).build(), @@ -286,7 +287,8 @@ public class Assertion extends Credential { new Validation.Builder().name("type").type(ValueType.RDF_TYPE).many(true).defaultType("schema:ImageObject").build(), new Validation.Builder().name("id").type(ValueType.DATA_URI_OR_URL).required(true).build(), new Validation.Builder().name("caption").type(ValueType.TEXT).build(), - new Validation.Builder().name("author").type(ValueType.IRI).build() + new Validation.Builder().name("author").type(ValueType.IRI).build(), + new Validation.Builder().name("id").type(ValueType.IMAGE).allowDataUri(true).build() )) .put(Type.Issuer, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), @@ -321,8 +323,7 @@ public class Assertion extends Credential { new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Profile).many(true).fullValidate(false).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() - )) + new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() )) .put(Type.RevocationList, List.of( new Validation.Builder().name("type").type(ValueType.RDF_TYPE).required(true).many(true).mustContainOneType(List.of(Type.RevocationList)).build(), new Validation.Builder().name("id").type(ValueType.IRI).build() diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index c371d90..8cb907d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -44,7 +44,7 @@ public class ValidationPropertyProbe extends PropertyProbe { } public ValidationPropertyProbe(String id, Validation validation) { - this(ID, validation, true); + this(id, validation, true); } public ValidationPropertyProbe(Validation validation, boolean fullValidate) { 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 0f5f98e..73706be 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 @@ -115,6 +115,15 @@ public class OB20Tests { }); } + @Test + void testDataImage() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.BADGE_WITH_DATA_IMAGE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + @Test void testLanguageInBadgeClass() { assertDoesNotThrow(()->{ 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 e27af23..a3c31d1 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 @@ -83,6 +83,8 @@ public class Samples { public final static Sample SIMPLE_FUTURE_ASSERTION_JSON = new Sample("ob20/basic-assertion-in-future.json", true); // original: test_validate_related: test_validate_related_language public final static Sample BASIC_WITH_LANGUAGE_JSON = new Sample("ob20/basic-assertion-with-language.json", true); + // original: test_image_validation: test_base64_data_uri_in_badgeclass + public final static Sample BADGE_WITH_DATA_IMAGE_JSON = new Sample("ob20/assets/badge-with-data-image.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assets/badge-with-data-image.json b/inspector-vc/src/test/resources/ob20/assets/badge-with-data-image.json new file mode 100644 index 0000000..da4abf9 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badge-with-data-image.json @@ -0,0 +1,10 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "BadgeClass", + "id": "https://example.org/badge-from-organization-with-revocation-list.json", + "name": "Awesome Robotics Badge", + "description": "For doing awesome things with robots that people think is pretty great.", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOUMyqsBwACeQFChxlltgAAAABJRU5ErkJggg==", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "https://example.org/organization-with-revocation-list.json" +} \ No newline at end of file From e47061de8dbb8a94bc3c04948a4abc12b7ae17e2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 12 Dec 2022 18:26:16 +0100 Subject: [PATCH 82/93] Added more image validation tests --- .../org/oneedtech/inspect/vc/OB20Tests.java | 20 ++++++++++++++++++- .../org/oneedtech/inspect/vc/Samples.java | 4 ++++ .../ob20/assertion-with-data-image.json | 18 +++++++++++++++++ .../assets/badgeclass-with-complex-image.json | 14 +++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 inspector-vc/src/test/resources/ob20/assertion-with-data-image.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/badgeclass-with-complex-image.json 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 73706be..1949d92 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 @@ -116,7 +116,7 @@ public class OB20Tests { } @Test - void testDataImage() { + void testDataImageInBadge() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB20.JSON.BADGE_WITH_DATA_IMAGE_JSON.asFileResource()); if(verbose) PrintHelper.print(report, true); @@ -124,6 +124,24 @@ public class OB20Tests { }); } + @Test + void testDataImageInAssertion() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_DATA_IMAGE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + }); + } + + @Test + void testComplexImageInAssertion() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.BADGE_WITH_COMPLEX_IMAGE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + @Test void testLanguageInBadgeClass() { assertDoesNotThrow(()->{ 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 a3c31d1..1b644af 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 @@ -85,6 +85,10 @@ public class Samples { public final static Sample BASIC_WITH_LANGUAGE_JSON = new Sample("ob20/basic-assertion-with-language.json", true); // original: test_image_validation: test_base64_data_uri_in_badgeclass public final static Sample BADGE_WITH_DATA_IMAGE_JSON = new Sample("ob20/assets/badge-with-data-image.json", true); + // original: test_image_validation: test_base64_data_uri_in_assertion + public final static Sample ASSERTION_WITH_DATA_IMAGE_JSON = new Sample("ob20/assertion-with-data-image.json", true); + // original: test_image_validation: test_validate_badgeclass_image_formats + public final static Sample BADGE_WITH_COMPLEX_IMAGE_JSON = new Sample("ob20/assets/badgeclass-with-complex-image.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-data-image.json b/inspector-vc/src/test/resources/ob20/assertion-with-data-image.json new file mode 100644 index 0000000..338ba57 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assertion-with-data-image.json @@ -0,0 +1,18 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOUMyqsBwACeQFChxlltgAAAABJRU5ErkJggg==", + "evidence": "https://example.org/beths-robot-work.html", + "issuedOn": "2016-12-31T23:59:59Z", + "badge": "https://example.org/robotics-badge.json", + "verification": { + "type": "hosted" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-complex-image.json b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-complex-image.json new file mode 100644 index 0000000..e06df7e --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/badgeclass-with-complex-image.json @@ -0,0 +1,14 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/badgeclass-with-complex-image.json", + "type": "BadgeClass", + "name": "Example Badge", + "description": "An example", + "criteria": "http://example.com/badgecriteria.json", + "issuer": "http://example.org/issuer1.json", + "image": { + "id": "http://example.org/beths-robot-badge.png", + "author": "http://someoneelse.org/1", + "caption": "A hexagon with attitude" + } +} \ No newline at end of file From c7dee3bdf78bff8dc7087e9174c7c1d8f7b50fdc Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 13 Dec 2022 08:15:03 +0100 Subject: [PATCH 83/93] Endorsements validation --- .../org/oneedtech/inspect/vc/Assertion.java | 5 ++--- .../ValidationPropertyProbeFactory.java | 2 +- .../org/oneedtech/inspect/vc/OB20Tests.java | 6 ++---- .../org/oneedtech/inspect/vc/Samples.java | 2 ++ .../ob20/assertion-with-endorsements.json | 19 +++++++++++++++++++ .../resources/ob20/assets/endorsement-1.json | 14 ++++++++++++++ .../resources/ob20/assets/endorsement-2.json | 14 ++++++++++++++ 7 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/endorsement-1.json create mode 100644 inspector-vc/src/test/resources/ob20/assets/endorsement-2.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index e904a5a..3f02a85 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -245,11 +245,10 @@ public class Assertion extends Credential { new Validation.Builder().name("claim").type(ValueType.ID).required(true).expectedTypes(List.of(Type.EndorsementClaim, Type.Endorsement)).fullValidate(false).build(), new Validation.Builder().name("issuedOn").type(ValueType.DATETIME).required(true).build(), new Validation.Builder().name("issuer").type(ValueType.ID).expectedType(Type.Profile).fetch(true).required(true).build(), - new Validation.Builder().name("verification").build(), + new Validation.Builder().name("verification").type(ValueType.ID).expectedType(Type.VerificationObjectAssertion).required(true).build(), new Validation.Builder().name("@language").type(ValueType.LANGUAGE).build(), new Validation.Builder().name("version").type(ValueType.TEXT_OR_NUMBER).build(), - new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Endorsement).many(true).fullValidate(false).build(), - new Validation.Builder().name("endorsement").type(ValueType.ID).allowRemoteUrl(true).fetch(true).expectedType(Type.Endorsement).many(true).build() + new Validation.Builder().name("related").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Endorsement).many(true).fullValidate(false).build() )) .put(Type.EndorsementClaim, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java index 816b6bd..a88266b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java @@ -15,7 +15,7 @@ public class ValidationPropertyProbeFactory { } public static ValidationPropertyProbe of(Validation validation, boolean fullValidate) { - checkNotNull(validation.getType()); + checkNotNull(validation.getType()); if (validation.getType() == ValueType.RDF_TYPE) { return new ValidationRdfTypePropertyProbe(validation, fullValidate); } 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 1949d92..455773b 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 @@ -143,12 +143,11 @@ public class OB20Tests { } @Test - void testLanguageInBadgeClass() { + void testEndorsementsInAssertion() { assertDoesNotThrow(()->{ - Report report = validator.run(Samples.OB20.JSON.SIMPLE_LANGUAGE_BADGECLASS.asFileResource()); + Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_ENDORSEMENTS.asFileResource()); if(verbose) PrintHelper.print(report, true); assertValid(report); - // check than }); } @@ -158,7 +157,6 @@ public class OB20Tests { Report report = validator.run(Samples.OB20.JSON.SIMPLE_ASSERTION_ISSUER_WITHOUT_PUBLIC_KEY_JSON.asFileResource()); if(verbose) PrintHelper.print(report, true); assertValid(report); - // check than }); } 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 1b644af..e9e9bef 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 @@ -89,6 +89,8 @@ public class Samples { public final static Sample ASSERTION_WITH_DATA_IMAGE_JSON = new Sample("ob20/assertion-with-data-image.json", true); // original: test_image_validation: test_validate_badgeclass_image_formats 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); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json b/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json new file mode 100644 index 0000000..4395cdb --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assertion-with-endorsements.json @@ -0,0 +1,19 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "type": "Assertion", + "id": "https://example.org/beths-robotics-badge.json", + "recipient": { + "type": "email", + "hashed": true, + "salt": "deadsea", + "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" + }, + "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", + "verification": { + "type": "hosted" + }, + "endorsement": ["https://example.org/endorsement-1.json", "https://example.org/endorsement-2.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 new file mode 100644 index 0000000..44d6ac2 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-1.json @@ -0,0 +1,14 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/endorsement-1.json", + "type": "Endorsement", + "claim": { + "id": "https://example.org/robotics-badge.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-2.json b/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json new file mode 100644 index 0000000..70ccc4d --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assets/endorsement-2.json @@ -0,0 +1,14 @@ +{ + "@context": "https://w3id.org/openbadges/v2", + "id": "http://example.org/endorsement-2.json", + "type": "Endorsement", + "claim": { + "id": "https://example.org/robotics-badge.json", + "endorsementComment": "Pretty good" + }, + "issuedOn": "2017-10-01T00:00Z", + "issuer": "http://example.org/issuer1.json", + "verification": { + "type": "HostedBadge" + } +} \ No newline at end of file From 419beae7ca46ad92a96c8487641171b6d4eada19 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 13 Dec 2022 13:06:35 +0100 Subject: [PATCH 84/93] Add resource to run context --- .../main/java/org/oneedtech/inspect/vc/EndorsementInspector.java | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index e21b34f..053b08f 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -67,6 +67,7 @@ public class EndorsementInspector extends VCInspector implements SubInspector { RunContext ctx = new RunContext.Builder() .put(this) + .put(resource) .put(JACKSON_OBJECTMAPPER, mapper) .put(JSONPATH_EVALUATOR, jsonPath) .build(); From 55ef973efe2f27aee2accf20bdd0b2ec75e55afc Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 13 Dec 2022 13:06:50 +0100 Subject: [PATCH 85/93] Fixed fetch condition --- .../vc/jsonld/probe/GraphFetcherProbe.java | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java index dff44db..988d3fb 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java @@ -30,6 +30,8 @@ import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -105,11 +107,7 @@ public class GraphFetcherProbe extends Probe { updateNode(validation, idNode, ctx); // fetch node and add it to the graph - UriResource uriResource = resolveUriResource(ctx, idNode.asText().strip()); - JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); - if (resolved == null) { - return new CredentialParseProbe().run(uriResource, ctx); - } + result = fetchNode(ctx, result, idNode); } } } @@ -118,25 +116,7 @@ public class GraphFetcherProbe extends Probe { for (JsonNode childNode : nodeList) { if (shouldFetch(childNode, validation)) { // get node from context - UriResource uriResource = resolveUriResource(ctx, childNode.asText()); - JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); - if (resolved == null) { - // fetch - result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); - if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { - Assertion fetchedAssertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); - - // compact ld - result = new ReportItems(List.of(result, new JsonLDCompactionProve(fetchedAssertion.getCredentialType().getContextUris().get(0)).run(fetchedAssertion, ctx))); - if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { - JsonLdGeneratedObject fetched = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(fetchedAssertion)); - JsonNode fetchedNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(fetched.getJson()); - - // recursive call - result = new ReportItems(List.of(result, new GraphFetcherProbe(fetchedAssertion).run(fetchedNode, ctx))); - } - } - } + result = fetchNode(ctx, result, childNode); } } @@ -144,6 +124,29 @@ public class GraphFetcherProbe extends Probe { return success(ctx); } + private ReportItems fetchNode(RunContext ctx, ReportItems result, JsonNode idNode) + throws URISyntaxException, Exception, JsonProcessingException, JsonMappingException { + UriResource uriResource = resolveUriResource(ctx, idNode.asText().strip()); + JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); + if (resolved == null) { + result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + Assertion fetchedAssertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); + + // compact ld + result = new ReportItems(List.of(result, new JsonLDCompactionProve(fetchedAssertion.getCredentialType().getContextUris().get(0)).run(fetchedAssertion, ctx))); + if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { + JsonLdGeneratedObject fetched = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(fetchedAssertion)); + JsonNode fetchedNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)).readTree(fetched.getJson()); + + // recursive call + result = new ReportItems(List.of(result, new GraphFetcherProbe(fetchedAssertion).run(fetchedNode, ctx))); + } + } + } + return result; + } + /** * Tells if we have to fetch the id. We have to fecth if: * - the node is not a complex node @@ -154,12 +157,14 @@ public class GraphFetcherProbe extends Probe { * @return */ private boolean shouldFetch(JsonNode node, Validation validation) { - return !node.isObject() || (!validation.isAllowDataUri() || DATA_URI_OR_URL.getValidationFunction().apply(node)) || (validation.isAllowDataUri() || ValueType.IRI.getValidationFunction().apply(node)); + return !node.isObject() && + (!validation.isAllowDataUri() || DATA_URI_OR_URL.getValidationFunction().apply(node)) && + (validation.isAllowDataUri() || ValueType.IRI.getValidationFunction().apply(node)); } private void updateNode(Validation validation, JsonNode idNode, RunContext ctx) throws IOException { JsonLdGeneratedObject jsonLdGeneratedObject = ctx.getGeneratedObject(JsonLDCompactionProve.getId(assertion)); - JsonNode merged = createNewJson(ctx, jsonLdGeneratedObject.getJson(), "{\"" + validation.getName() + "\": \"" + idNode.asText().strip() + "\""); + JsonNode merged = createNewJson(ctx, jsonLdGeneratedObject.getJson(), "{\"" + validation.getName() + "\": \"" + idNode.asText().strip() + "\"}"); jsonLdGeneratedObject.setJson(merged.toString()); } From cb60fd11edc4bd1a3e1eecb249f87f5faf01bdab Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 13 Dec 2022 14:30:55 +0100 Subject: [PATCH 86/93] 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", From 3b335867fea837e1d1a2ff8231552badd56f16f9 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 14 Dec 2022 08:14:18 +0100 Subject: [PATCH 87/93] Save loaded contexts in internal cache --- .../inspect/vc/util/CachingDocumentLoader.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 961e6cb..1136aea 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -5,7 +5,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,6 +27,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import foundation.identity.jsonld.ConfigurableDocumentLoader; +import jakarta.json.JsonStructure; /** * A com.apicatalog DocumentLoader with a threadsafe static cache. @@ -31,7 +35,7 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader; * @author mgylling */ public class CachingDocumentLoader extends ConfigurableDocumentLoader { - + private Set contexts; public CachingDocumentLoader() { this(null); @@ -42,6 +46,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { setEnableHttp(true); setEnableHttps(true); setDefaultHttpLoader(new HttpLoader(localDomains)); + this.contexts = new HashSet<>(); } @Override @@ -51,9 +56,20 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { logger.error("documentCache not able to load {}", url); throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT); } + // save context + this.contexts.add(url); + return document; } + public Set getContexts() { + return contexts; + } + + public void resetContexts() { + this.contexts.clear(); + } + public class HttpLoader implements DocumentLoader { final Map localDomains; From 727bd8194bab88292b2c17c5d840bd06ea15e168 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 14 Dec 2022 08:14:33 +0100 Subject: [PATCH 88/93] Added extension tests --- .../oneedtech/inspect/vc/OB20Inspector.java | 32 +++++++++++----- .../vc/jsonld/probe/ExtensionProbe.java | 37 +++++++++++++++++++ .../org/oneedtech/inspect/vc/OB20Tests.java | 18 +++++++++ .../org/oneedtech/inspect/vc/Samples.java | 4 ++ .../assertion-with-extension-node-basic.json | 29 +++++++++++++++ ...assertion-with-extension-node-invalid.json | 29 +++++++++++++++ 6 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/ExtensionProbe.java create mode 100644 inspector-vc/src/test/resources/ob20/assertion-with-extension-node-basic.json create mode 100644 inspector-vc/src/test/resources/ob20/assertion-with-extension-node-invalid.json 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 From 1818fdabf5abaa7d1cd93010a9263b61e75a8e57 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 14 Dec 2022 16:12:49 +0100 Subject: [PATCH 89/93] initial extension probe, only checks contexts --- .../oneedtech/inspect/vc/OB20Inspector.java | 20 ++- .../org/oneedtech/inspect/vc/VCInspector.java | 51 ++++++ .../vc/jsonld/probe/ExtensionProbe.java | 152 ++++++++++++++++-- .../jsonld/probe/JsonLDCompactionProve.java | 6 +- .../vc/util/CachingDocumentLoader.java | 3 + .../resources/contexts/obv2x-extensions.json | 12 ++ .../assertion-with-extension-node-basic.json | 19 ++- ...assertion-with-extension-node-invalid.json | 19 ++- 8 files changed, 245 insertions(+), 37 deletions(-) create mode 100644 inspector-vc/src/main/resources/contexts/obv2x-extensions.json 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 327b1a9..6c4a5cb 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 @@ -1,6 +1,7 @@ package org.oneedtech.inspect.vc; import static java.lang.Boolean.TRUE; +import static java.util.stream.Collectors.toList; 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; @@ -13,9 +14,6 @@ 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; import org.oneedtech.inspect.core.Inspector; import org.oneedtech.inspect.core.probe.GeneratedObject; @@ -26,6 +24,7 @@ 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.schema.JsonSchemaCache; +import org.oneedtech.inspect.util.code.Tuple; import org.oneedtech.inspect.util.json.ObjectMapperCache; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.util.resource.ResourceType; @@ -166,7 +165,7 @@ public class OB20Inspector extends VCInspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - // extension validations + // get all json-ld generated objects for both extension and endorsements validation List jsonLdGeneratedObjects = ctx.getGeneratedObjects().values().stream() .filter(generatedObject -> generatedObject instanceof JsonLdGeneratedObject) .map(obj -> { @@ -177,10 +176,15 @@ public class OB20Inspector extends VCInspector { throw new IllegalArgumentException("Couldn't not parse " + obj.getId() + ": contains invalid JSON"); } }) - .collect(Collectors.toList()); - for (JsonNode generatedObject : jsonLdGeneratedObjects) { + .collect(toList()); + + // validate extensions + List> extensionProbeTuples = jsonLdGeneratedObjects.stream() + .flatMap(node -> getExtensionProbes(node, "id").stream()) + .collect(toList()); + for (Tuple extensionProbeTuple : extensionProbeTuples) { probeCount++; - accumulator.add(new ExtensionProbe().run(generatedObject, ctx)); + accumulator.add(extensionProbeTuple.t1.run(extensionProbeTuple.t2, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } @@ -192,7 +196,7 @@ public class OB20Inspector extends VCInspector { // return endorsement node, filtering out the on inside @context return asNodeList(node, "$..endorsement", jsonPath).stream().filter(endorsementNode -> !endorsementNode.isObject()); }) - .collect(Collectors.toList()); + .collect(toList()); for(JsonNode node : endorsements) { probeCount++; 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 b4cf13a..3e29fd7 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 @@ -1,8 +1,16 @@ package org.oneedtech.inspect.vc; +import static java.util.stream.Collectors.toList; +import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asStringList; + +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.oneedtech.inspect.core.Inspector; import org.oneedtech.inspect.core.probe.Outcome; @@ -10,7 +18,10 @@ 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.util.code.Tuple; +import org.oneedtech.inspect.vc.jsonld.probe.ExtensionProbe; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.apicatalog.jsonld.loader.DocumentLoader; import com.fasterxml.jackson.databind.JsonNode; @@ -71,6 +82,46 @@ public abstract class VCInspector extends Inspector { return new CachingDocumentLoader(); } + protected List> getExtensionProbes(JsonNode node, String entryPath) { + List> probes = new ArrayList<>(); + if (!node.isObject()) { + return probes; + } + + if (node.has("type")) { + List types = asStringList(node.get("type")); + + // only validate extension types + if (types.contains("Extension")) { + List typesToTest = types.stream().filter(type -> !type.equals("Extension")).collect(toList()); + // add an extension Probe + probes.add(new Tuple(new ExtensionProbe(entryPath, typesToTest), node)); + } + } + + + probes.addAll(StreamSupport + .stream(Spliterators.spliteratorUnknownSize(node.fields(), 0), false) + .filter(e -> !e.getKey().equals("id") && !e.getKey().equals("type")) + .flatMap(entry -> { + if (entry.getValue().isArray()) { + // recursive call + List childNodes = JsonNodeUtil.asNodeList(entry.getValue()); + List> subProbes = new ArrayList<>(); + for (int i = 0; i < childNodes.size(); i++) { + JsonNode childNode = childNodes.get(i); + subProbes.addAll(getExtensionProbes(childNode, entryPath + "." + entry.getKey() + "[" + i + "]")); + } + return subProbes.stream(); + } else { + return getExtensionProbes(entry.getValue(), entryPath + "." + entry.getKey()).stream(); + } + }) + .collect(Collectors.toList()) + ); + return probes; + } + 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/jsonld/probe/ExtensionProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/ExtensionProbe.java index a801222..c1be1ef 100644 --- 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 @@ -1,37 +1,169 @@ package org.oneedtech.inspect.vc.jsonld.probe; +import static java.util.stream.Collectors.joining; + +import java.io.IOException; +import java.io.StringReader; import java.net.URI; +import java.util.HashSet; +import java.util.List; 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.probe.json.JsonSchemaProbe; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; +import com.apicatalog.jsonld.JsonLd; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.util.TokenBuffer; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion.VersionFlag; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; + +/** + * Probe for extensions in OB 2.0 + * Maps to task "VALIDATE_EXTENSION_NODE" in python implementation + * @author xaracil + */ public class ExtensionProbe extends Probe { + private final List typesToTest; + + public ExtensionProbe(String entryPath, List typesToTest) { + super(ID, entryPath, typesToTest.stream().collect(joining())); + this.typesToTest = typesToTest; + } @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; + ReportItems reportItems = new ReportItems(); + DocumentLoader documentLoader = (DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER); + Set contexts = null; if (documentLoader instanceof CachingDocumentLoader) { - contexts = ((CachingDocumentLoader) documentLoader).getContexts(); + contexts = new HashSet<>(((CachingDocumentLoader) documentLoader).getContexts()); } else { contexts = Set.of(); } - // TODO Auto-generated method stub - return null; + // compact contexts + URI ob20contextUri = new URI(CONTEXT_URI_STRING); + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + for (URI uri : contexts) { + if (!uri.equals(ob20contextUri)) { + JsonLdOptions options = new JsonLdOptions(documentLoader); + Document contextDocument = documentLoader.loadDocument(uri, new DocumentLoaderOptions()); + JsonNode contextJson = mapper.readTree(contextDocument.getJsonContent().orElseThrow().toString()); + + JsonObject compactedContext = JsonLd.compact(uri, "https://w3id.org/openbadges/v2") + .options(options) + .get(); + JsonNode context = mapper.readTree(compactedContext.toString()); + List validations = JsonNodeUtil.asNodeList(context.get("validation")); + for (JsonNode validation : validations) { + if (isLdTermInList(validation.get("validatesType"), options)) { + JsonNode schemaJson = null; + URI schemaUri = null; + try { + schemaUri = new URI(validation.get("validationSchema").asText().strip()); + // check schema is valid + Document schemaDocument = documentLoader.loadDocument(schemaUri, new DocumentLoaderOptions()); + schemaJson = mapper.readTree(schemaDocument.getJsonContent().orElseThrow().toString()); + } catch (Exception e) { + return fatal("Could not load JSON-schema from URL " + schemaUri, ctx); + } + + reportItems = new ReportItems(List.of(reportItems, validateSingleExtension(node, uri, contextJson, validation.get("validatesType").asText().strip(), schemaJson, schemaUri, options, ctx))); + } + } + } + } + + if (reportItems.size() == 0) { + return error("Could not determine extension type to test", ctx); + } + + return reportItems; + } + + private boolean isLdTermInList(JsonNode termNode, JsonLdOptions options) throws JsonLdError { + JsonDocument jsonDocument = JsonDocument.of(Json.createObjectBuilder() + .add("@context", CONTEXT_URI_STRING) + .add("_:term", Json.createObjectBuilder() + .add("@type", termNode.asText().strip())) + .add("_:list", Json.createObjectBuilder() + .add("@type", Json.createArrayBuilder(typesToTest))) + .build()); + JsonArray expandedDocument = JsonLd.expand(jsonDocument) + .options(options) + .get(); + + JsonArray list = expandedDocument.getJsonObject(0).getJsonArray("_:list").getJsonObject(0).getJsonArray("@type"); + JsonValue term = expandedDocument.getJsonObject(0).getJsonArray("_:term").getJsonObject(0).getJsonArray("@type").get(0); + + return list.contains(term); } - private void getValidations(JsonNode node, String entryPath, Set contexts) { + private ReportItems validateSingleExtension(JsonNode node, URI uri, JsonNode context, String string, JsonNode schemaJson, URI schemaUri, JsonLdOptions options, RunContext ctx) throws JsonGenerationException, JsonMappingException, IOException, JsonLdError { + ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); + // validate against JSON schema, using a copy of the node + TokenBuffer tb = new TokenBuffer(mapper, false); + mapper.writeValue(tb, node); + JsonNode auxNode = mapper.readTree(tb.asParser()); + ObjectReader readerForUpdating = mapper.readerForUpdating(auxNode); + JsonNode merged = readerForUpdating.readValue("{\"@context\": \"" + CONTEXT_URI_STRING + "\"}"); + + // combine contexts + JsonDocument contextsDocument = combineContexts(context); + + JsonObject compactedObject = JsonLd.compact(JsonDocument.of(new StringReader(merged.toString())), contextsDocument) + .options(options) + .get(); + + // schema probe on compactedObject and schema + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V4); + JsonSchema schema = factory.getSchema(schemaUri, schemaJson); + return new JsonSchemaProbe(schema).run(mapper.readTree(compactedObject.toString()), ctx); } + + private JsonDocument combineContexts(JsonNode context) { + List contexts = JsonNodeUtil.asNodeList(context); + JsonArrayBuilder contextArrayBuilder = Json.createArrayBuilder(); + contextArrayBuilder.add(CONTEXT_URI_STRING); // add OB context to the list + for (JsonNode contextNode : contexts) { + if (contextNode.isTextual()) { + contextArrayBuilder.add(contextNode.asText().strip()); + } else if (contextNode.isObject() && contextNode.hasNonNull("@context")) { + contextArrayBuilder.add(Json.createReader(new StringReader(contextNode.get("@context").toString())).readObject()); + + } + } + + JsonDocument contextsDocument = JsonDocument.of(Json.createObjectBuilder() + .add("@context", contextArrayBuilder.build()) + .build()); + return contextsDocument; + } + + public static final String ID = ExtensionProbe.class.getSimpleName(); + private static final String CONTEXT_URI_STRING = "https://w3id.org/openbadges/v2"; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index 92c4a51..a934d46 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -36,10 +36,10 @@ public class JsonLDCompactionProve extends Probe { try { // compact JSON JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString())); - CompactionApi compactApi = JsonLd.compact(jsonDocument, context); - compactApi.options(new JsonLdOptions((DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER))); + JsonObject compactedObject = JsonLd.compact(jsonDocument, context) + .options(new JsonLdOptions((DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER))) + .get(); - JsonObject compactedObject = compactApi.get(); ctx.addGeneratedObject(new JsonLdGeneratedObject(getId(crd), compactedObject.toString())); // Handle mismatch between URL node source and declared ID. diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 1136aea..18251b9 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -126,6 +126,8 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .put("https://w3id.org/security/suites/jws-2020/v1", Resources.getResource("contexts/suites-jws-2020.jsonld")) .put("https://openbadgespec.org/v2/context.json", Resources.getResource("contexts/ob-v2p0.json")) .put("https://w3id.org/openbadges/v2", Resources.getResource("contexts/obv2x.jsonld")) + .put("https://w3id.org/openbadges/extensions/exampleExtension/context.json", Resources.getResource("contexts/obv2x-extensions.json")) + .put("https://openbadgespec.org/extensions/exampleExtension/schema.json", Resources.getResource("catalog/openbadgespec.org/extensions/exampleExtension/schema.json")) .build(); @@ -133,6 +135,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24)) .build(new CacheLoader, Document>() { public Document load(final Tuple id) throws Exception { + System.out.println("CachingDocumentLoader " + id.t1 + ": " + bundled.containsKey(id.t1)); try (InputStream is = bundled.containsKey(id.t1) ? bundled.get(id.t1).openStream() : new URI(id.t1).toURL().openStream();) { diff --git a/inspector-vc/src/main/resources/contexts/obv2x-extensions.json b/inspector-vc/src/main/resources/contexts/obv2x-extensions.json new file mode 100644 index 0000000..d759111 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/obv2x-extensions.json @@ -0,0 +1,12 @@ +{ + "@context": { + "obi": "https://w3id.org/openbadges#", + "exampleProperty": "http://schema.org/text" + }, + "obi:validation": [ + { + "obi:validatesType": "obi:extensions/#ExampleExtension", + "obi:validationSchema": "https://openbadgespec.org/extensions/exampleExtension/schema.json" + } + ] + } \ No newline at end of file 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 index 0ef8631..9861d08 100644 --- 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 @@ -1,5 +1,8 @@ { - "@context": ["https://w3id.org/openbadges/v2","https://w3id.org/openbadges/extensions/exampleExtension/context.json"], + "@context": [ + "https://w3id.org/openbadges/v2", + "https://w3id.org/openbadges/extensions/exampleExtension/context.json" + ], "id": "http://example.org/assertion", "type": "Assertion", "recipient": { @@ -9,21 +12,21 @@ "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" }, "image": "https://example.org/beths-robot-badge.png", - "issuedOn": "2016-12-31T23:59:59Z", "badge": "https://example.org/robotics-badge.json", + "issuedOn": "2016-12-31T23:59:59Z", "verification": { "type": "hosted" }, - "evidence": { - "id": "_:b1", - "narrative": "Rocked the free world" - }, "extensions:exampleExtension": { "id": "_:b0", "type": [ "Extension", - "extensions:ExampleExtension" + "obi:extensions/#ExampleExtension" ], - "schema:text": "I'm a property, short and sweet" + "http://schema.org/text": "I'm a property, short and sweet" + }, + "evidence": { + "id": "_:b1", + "narrative": "Rocked the free world" } } \ 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 index af49df5..76194b3 100644 --- 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 @@ -1,5 +1,8 @@ { - "@context": ["https://w3id.org/openbadges/v2","https://w3id.org/openbadges/extensions/exampleExtension/context.json"], + "@context": [ + "https://w3id.org/openbadges/v2", + "https://w3id.org/openbadges/extensions/exampleExtension/context.json" + ], "id": "http://example.org/assertion", "type": "Assertion", "recipient": { @@ -9,21 +12,21 @@ "identity": "sha256$ecf5409f3f4b91ab60cc5ef4c02aef7032354375e70cf4d8e43f6a1d29891942" }, "image": "https://example.org/beths-robot-badge.png", - "issuedOn": "2016-12-31T23:59:59Z", "badge": "https://example.org/robotics-badge.json", + "issuedOn": "2016-12-31T23:59:59Z", "verification": { "type": "hosted" }, - "evidence": { - "id": "_:b1", - "narrative": "Rocked the free world" - }, "extensions:exampleExtension": { "id": "_:b0", "type": [ "Extension", - "extensions:ExampleExtension" + "obi:extensions/#ExampleExtension" ], - "schema:text": 1337 + "http://schema.org/text": 1337 + }, + "evidence": { + "id": "_:b1", + "narrative": "Rocked the free world" } } \ No newline at end of file From 766228bbad2c0c3eb1a7f94ef6fe7a5a20c579f7 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 15 Dec 2022 13:32:07 +0100 Subject: [PATCH 90/93] Finished extension validation --- .../vc/util/CachingDocumentLoader.java | 5 ++- .../contexts/obv2x-applylink-extensions.json | 13 +++++++ .../org/oneedtech/inspect/vc/OB20Tests.java | 9 +++++ .../org/oneedtech/inspect/vc/Samples.java | 2 ++ .../assertion-with-multiple-extensions.json | 35 +++++++++++++++++++ .../valid-issuer-extension.json | 2 +- 6 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 inspector-vc/src/main/resources/contexts/obv2x-applylink-extensions.json create mode 100644 inspector-vc/src/test/resources/ob20/assertion-with-multiple-extensions.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 18251b9..8b55b7d 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -6,7 +6,6 @@ import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -27,7 +26,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import foundation.identity.jsonld.ConfigurableDocumentLoader; -import jakarta.json.JsonStructure; /** * A com.apicatalog DocumentLoader with a threadsafe static cache. @@ -128,6 +126,8 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .put("https://w3id.org/openbadges/v2", Resources.getResource("contexts/obv2x.jsonld")) .put("https://w3id.org/openbadges/extensions/exampleExtension/context.json", Resources.getResource("contexts/obv2x-extensions.json")) .put("https://openbadgespec.org/extensions/exampleExtension/schema.json", Resources.getResource("catalog/openbadgespec.org/extensions/exampleExtension/schema.json")) + .put("https://w3id.org/openbadges/extensions/applyLinkExtension/context.json", Resources.getResource("contexts/obv2x-applylink-extensions.json")) + .put("https://openbadgespec.org/extensions/applyLinkExtension/schema.json", Resources.getResource("catalog/openbadgespec.org/extensions/applyLinkExtension/schema.json")) .build(); @@ -135,7 +135,6 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24)) .build(new CacheLoader, Document>() { public Document load(final Tuple id) throws Exception { - System.out.println("CachingDocumentLoader " + id.t1 + ": " + bundled.containsKey(id.t1)); try (InputStream is = bundled.containsKey(id.t1) ? bundled.get(id.t1).openStream() : new URI(id.t1).toURL().openStream();) { diff --git a/inspector-vc/src/main/resources/contexts/obv2x-applylink-extensions.json b/inspector-vc/src/main/resources/contexts/obv2x-applylink-extensions.json new file mode 100644 index 0000000..da70560 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/obv2x-applylink-extensions.json @@ -0,0 +1,13 @@ +{ + "@context": { + "obi": "https://w3id.org/openbadges#", + "extensions": "https://w3id.org/openbadges/extensions#", + "url": "extensions:applyLink" + }, + "obi:validation": [ + { + "obi:validatesType": "extensions:ApplyLink", + "obi:validationSchema": "https://openbadgespec.org/extensions/applyLinkExtension/schema.json" + } + ] + } \ No newline at end of file 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 1ef7c47..2cf8d99 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 @@ -277,6 +277,15 @@ public class OB20Tests { }); } + @Test + void testMultipleExtensionNode() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_MULTIPLE_EXTENSIONS_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + @Test void testInvalidExtensionNode() { assertDoesNotThrow(()->{ 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 c217cec..f95ce38 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 @@ -95,6 +95,8 @@ public class Samples { 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); + // original: test_validate_extensions: test_validation_breaks_down_multiple_extensions + public final static Sample ASSERTION_WITH_MULTIPLE_EXTENSIONS_JSON = new Sample("ob20/assertion-with-multiple-extensions.json", true); } public static final class PNG { diff --git a/inspector-vc/src/test/resources/ob20/assertion-with-multiple-extensions.json b/inspector-vc/src/test/resources/ob20/assertion-with-multiple-extensions.json new file mode 100644 index 0000000..51a4b48 --- /dev/null +++ b/inspector-vc/src/test/resources/ob20/assertion-with-multiple-extensions.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://w3id.org/openbadges/v2", + "https://w3id.org/openbadges/extensions/exampleExtension/context.json", + "https://w3id.org/openbadges/extensions/applyLinkExtension/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", + "badge": "https://example.org/robotics-badge.json", + "issuedOn": "2016-12-31T23:59:59Z", + "verification": { + "type": "hosted" + }, + "extensions:exampleExtension": { + "id": "_:b0", + "type": [ + "Extension", + "obi:extensions/#ExampleExtension", + "extensions:ApplyLink" + ], + "http://schema.org/text": "I'm a property, short and sweet", + "url": "http://www.1edtech.org" + }, + "evidence": { + "id": "_:b1", + "narrative": "Rocked the free world" + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json index 139dac8..ebc8f1d 100644 --- a/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json +++ b/inspector-vc/src/test/resources/ob20/rdf-validation/valid-issuer-extension.json @@ -199,7 +199,7 @@ "verify": "verification" }, "id": "http://example.com/badge1", - "type": ["Issuer", "Extension"], + "type": ["Issuer", "http://example.com/CoolClass"], "name": "Chumley", "url": "https://example.org", "email": "contact@example.org" From 682c9d9d4d1863a39c2e3d8a7f1ab0ccf4ccca07 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 15 Dec 2022 13:32:26 +0100 Subject: [PATCH 91/93] Better report messages --- .../org/oneedtech/inspect/vc/Credential.java | 1 + .../oneedtech/inspect/vc/OB20Inspector.java | 2 +- .../jsonld/probe/JsonLDCompactionProve.java | 2 +- .../vc/probe/ContextPropertyProbe.java | 2 +- .../inspect/vc/probe/PropertyProbe.java | 4 ++-- .../vc/probe/StringValuePropertyProbe.java | 4 ++-- .../inspect/vc/probe/TypePropertyProbe.java | 2 +- .../ValidationImagePropertyProbe.java | 8 +++---- .../ValidationIssuerPropertyProbe.java | 8 +++---- .../validation/ValidationPropertyProbe.java | 22 +++++++++---------- .../ValidationPropertyProbeFactory.java | 16 ++++++++------ .../ValidationRdfTypePropertyProbe.java | 8 +++---- 12 files changed, 41 insertions(+), 38 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index cebf818..84e1c9b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -92,6 +92,7 @@ public abstract class Credential extends GeneratedObject { List getAllowedTypeValues(); boolean isAllowedTypeValuesRequired(); List getContextUris(); + String toString(); } public abstract static class Builder { 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 6c4a5cb..d812cb8 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 @@ -136,7 +136,7 @@ public class OB20Inspector extends VCInspector { List validations = assertion.getValidations(); for (Validation validation : validations) { probeCount++; - accumulator.add(ValidationPropertyProbeFactory.of(validation).run(assertionNode, ctx)); + accumulator.add(ValidationPropertyProbeFactory.of(assertion.getCredentialType().toString(), validation).run(assertionNode, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index a934d46..b1b8946 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -51,7 +51,7 @@ public class JsonLDCompactionProve extends Probe { return success(this, ctx); } catch (Exception e) { - return fatal("Error while parsing credential: " + e.getMessage(), ctx); + return fatal("Error while compacting JSON-LD: " + crd.getJson() + ". Caused by: " + e.getMessage(), ctx); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index c6f9192..f0ec2ab 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -19,7 +19,7 @@ public class ContextPropertyProbe extends StringValuePropertyProbe { private final CredentialEnum type; public ContextPropertyProbe(CredentialEnum type) { - super(ID, "@context"); + super(ID, type.toString(), "@context"); this.type = checkNotNull(type); setValueValidations(this::validate); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java index cd1232a..cde8cb5 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/PropertyProbe.java @@ -12,8 +12,8 @@ public class PropertyProbe extends Probe { private final String propertyName; private BiFunction validations; - public PropertyProbe(String id, String propertyName) { - super(id); + public PropertyProbe(String id, String typeName, String propertyName) { + super(id, typeName, propertyName); this.propertyName = propertyName; this.validations = this::defaultValidation; } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java index 5a3ff3b..de249c9 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/StringValuePropertyProbe.java @@ -12,8 +12,8 @@ import com.fasterxml.jackson.databind.JsonNode; public class StringValuePropertyProbe extends PropertyProbe { private BiFunction, RunContext, ReportItems> valueValidations; - public StringValuePropertyProbe(String id, String propertyName) { - super(id, propertyName); + public StringValuePropertyProbe(String id, String credentialType, String propertyName) { + super(id, credentialType, propertyName); this.valueValidations = this::defaultValidation; super.setValidations(this::nodeValidation); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java index b53f5bd..7814f46 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -18,7 +18,7 @@ public class TypePropertyProbe extends StringValuePropertyProbe { private final CredentialEnum expected; public TypePropertyProbe(CredentialEnum expected) { - super(ID, "type"); + super(ID, expected.toString(), "type"); this.expected = checkNotNull(expected); this.setValueValidations(this::validate); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java index b5cdbcd..e4123b4 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java @@ -20,12 +20,12 @@ import com.fasterxml.jackson.databind.JsonNode; */ public class ValidationImagePropertyProbe extends ValidationPropertyProbe { - public ValidationImagePropertyProbe(Validation validation) { - super(ID, validation); + public ValidationImagePropertyProbe(String credentialType, Validation validation) { + this(credentialType, validation, true); } - public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) { - super(ID, validation, fullValidate); + public ValidationImagePropertyProbe(String credentialType, Validation validation, boolean fullValidate) { + super(ID, credentialType, validation, fullValidate); } @Override diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java index a9ebb17..8eeb931 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationIssuerPropertyProbe.java @@ -16,12 +16,12 @@ import com.fasterxml.jackson.databind.JsonNode; */ public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe { - public ValidationIssuerPropertyProbe(Validation validation) { - super(ID, validation); + public ValidationIssuerPropertyProbe(String credentialType, Validation validation) { + this(credentialType, validation, true); } - public ValidationIssuerPropertyProbe(Validation validation, boolean fullValidate) { - super(ID, validation, fullValidate); + public ValidationIssuerPropertyProbe(String credentialType, Validation validation, boolean fullValidate) { + super(ID, credentialType, validation, fullValidate); } @Override diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index 8cb907d..b7fdc56 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -37,22 +37,22 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader; */ public class ValidationPropertyProbe extends PropertyProbe { protected final Validation validation; - protected final boolean fullValidate; // TODO: fullValidate + protected final boolean fullValidate; - public ValidationPropertyProbe(Validation validation) { - this(ID, validation, true); + public ValidationPropertyProbe(String credentialType, Validation validation) { + this(ID, credentialType, validation, true); } - public ValidationPropertyProbe(String id, Validation validation) { - this(id, validation, true); + public ValidationPropertyProbe(String id, String credentialType, Validation validation) { + this(id, credentialType, validation, true); } - public ValidationPropertyProbe(Validation validation, boolean fullValidate) { - this(ID, validation, fullValidate); + public ValidationPropertyProbe(String credentialType, Validation validation, boolean fullValidate) { + this(ID, credentialType, validation, fullValidate); } - public ValidationPropertyProbe(String id, Validation validation, boolean fullValidate) { - super(id + "<" + validation.getName() + ">", validation.getName()); + public ValidationPropertyProbe(String id, String credentialType, Validation validation, boolean fullValidate) { + super(id, credentialType, validation.getName()); this.validation = validation; this.fullValidate = fullValidate; setValidations(this::validate); @@ -170,7 +170,7 @@ public class ValidationPropertyProbe extends PropertyProbe { private ReportItems validatePrerequisites(JsonNode node, RunContext ctx) { List results = validation.getPrerequisites().stream() - .map(v -> ValidationPropertyProbeFactory.of(v, validation.isFullValidate())) + .map(v -> ValidationPropertyProbeFactory.of(validation.getName(), v, validation.isFullValidate())) .map(probe -> { try { return probe.run(node, ctx); @@ -186,7 +186,7 @@ public class ValidationPropertyProbe extends PropertyProbe { private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) { List results = validation.getExpectedTypes().stream() .flatMap(type -> type.getValidations().stream()) - .map(v -> ValidationPropertyProbeFactory.of(v, validation.isFullValidate())) + .map(v -> ValidationPropertyProbeFactory.of(validation.getName(), v, validation.isFullValidate())) .map(probe -> { try { return probe.run(node, ctx); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java index a88266b..917ef2b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbeFactory.java @@ -3,6 +3,8 @@ package org.oneedtech.inspect.vc.probe.validation; import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; import org.oneedtech.inspect.vc.Assertion.ValueType; +import org.oneedtech.inspect.vc.Credential.CredentialEnum; +import org.oneedtech.inspect.vc.Assertion; import org.oneedtech.inspect.vc.Validation; /** @@ -10,21 +12,21 @@ import org.oneedtech.inspect.vc.Validation; * @author xaracil */ public class ValidationPropertyProbeFactory { - public static ValidationPropertyProbe of(Validation validation) { - return of(validation, true); + public static ValidationPropertyProbe of(String type, Validation validation) { + return of(type, validation, true); } - public static ValidationPropertyProbe of(Validation validation, boolean fullValidate) { + public static ValidationPropertyProbe of(String type, Validation validation, boolean fullValidate) { checkNotNull(validation.getType()); if (validation.getType() == ValueType.RDF_TYPE) { - return new ValidationRdfTypePropertyProbe(validation, fullValidate); + return new ValidationRdfTypePropertyProbe(type, validation, fullValidate); } if (validation.getType() == ValueType.IMAGE) { - return new ValidationImagePropertyProbe(validation); + return new ValidationImagePropertyProbe(type, validation); } if (validation.getType() == ValueType.ISSUER) { - return new ValidationIssuerPropertyProbe(validation); + return new ValidationIssuerPropertyProbe(type, validation); } - return new ValidationPropertyProbe(validation, fullValidate); + return new ValidationPropertyProbe(type, validation, fullValidate); } } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java index 60bbc15..5443752 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationRdfTypePropertyProbe.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.databind.node.TextNode; * @author xaracil */ public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe { - public ValidationRdfTypePropertyProbe(Validation validation) { - super(ID, validation); + public ValidationRdfTypePropertyProbe(String credentialType, Validation validation ) { + this(credentialType, validation, true); } - public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) { - super(ID, validation, fullValidate); + public ValidationRdfTypePropertyProbe(String credentialType, Validation validation, boolean fullValidate) { + super(ID, credentialType, validation, fullValidate); } @Override From e7de6c6ebc705118c5d46fa056fb06ad127e3da2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 15 Dec 2022 13:33:41 +0100 Subject: [PATCH 92/93] Added message about redirection --- .../org/oneedtech/inspect/vc/util/CachingDocumentLoader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index 8b55b7d..a4ba642 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -135,6 +135,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { .initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24)) .build(new CacheLoader, Document>() { public Document load(final Tuple id) throws Exception { + // TODO: this loading will fail if the document is redirected (HTTP 301) try (InputStream is = bundled.containsKey(id.t1) ? bundled.get(id.t1).openStream() : new URI(id.t1).toURL().openStream();) { From 1677a0b359d93054fdc721814c9edf46746cf1a2 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 15 Dec 2022 15:41:24 +0100 Subject: [PATCH 93/93] Added a UriFactory for allowing local resources in testing --- .../inspect/vc/OB20EndorsementInspector.java | 12 +++++++ .../oneedtech/inspect/vc/OB20Inspector.java | 30 +++++----------- .../org/oneedtech/inspect/vc/VCInspector.java | 6 ++++ .../vc/jsonld/probe/GraphFetcherProbe.java | 20 +++-------- .../jsonld/probe/JsonLDCompactionProve.java | 1 - .../probe/AssertionRevocationListProbe.java | 23 +++--------- .../probe/VerificationDependenciesProbe.java | 26 +++----------- .../vc/probe/VerificationJWTProbe.java | 23 +++--------- .../ValidationImagePropertyProbe.java | 4 ++- .../validation/ValidationPropertyProbe.java | 18 ++-------- .../resource/DefaultUriResourceFactory.java | 19 ++++++++++ .../vc/resource/UriResourceFactory.java | 13 +++++++ .../vc/resource/TestUriResourceFactory.java | 35 +++++++++++++++++++ .../inspect/vc/util/TestOB20Inspector.java | 7 ++++ 14 files changed, 124 insertions(+), 113 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/DefaultUriResourceFactory.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/UriResourceFactory.java create mode 100644 inspector-vc/src/test/java/org/oneedtech/inspect/vc/resource/TestUriResourceFactory.java 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 index 7bde48b..5e7d303 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB20EndorsementInspector.java @@ -7,6 +7,7 @@ import static org.oneedtech.inspect.core.probe.RunContext.Key.JSON_DOCUMENT_LOAD 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.probe.RunContext.Key.URI_RESOURCE_FACTORY; 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; @@ -33,6 +34,7 @@ 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 org.oneedtech.inspect.vc.resource.UriResourceFactory; import com.apicatalog.jsonld.loader.DocumentLoader; import com.fasterxml.jackson.databind.JsonNode; @@ -45,10 +47,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class OB20EndorsementInspector extends VCInspector implements SubInspector { private DocumentLoader documentLoader; + private UriResourceFactory uriResourceFactory; protected OB20EndorsementInspector(OB20EndorsementInspector.Builder builder) { super(builder); this.documentLoader = builder.documentLoader; + this.uriResourceFactory = builder.uriResourceFactory; } @Override @@ -78,6 +82,7 @@ public class OB20EndorsementInspector extends VCInspector implements SubInspecto .put(SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) .put(JSON_DOCUMENT_LOADER, documentLoader) .put(JWT_CREDENTIAL_NODE_NAME, Assertion.JWT_NODE_NAME) + .put(URI_RESOURCE_FACTORY, uriResourceFactory) .build(); parentObjects.entrySet().stream().forEach(entry -> { @@ -122,6 +127,7 @@ public class OB20EndorsementInspector extends VCInspector implements SubInspecto public static class Builder extends VCInspector.Builder { private DocumentLoader documentLoader; + private UriResourceFactory uriResourceFactory; @SuppressWarnings("unchecked") @Override @@ -133,6 +139,12 @@ public class OB20EndorsementInspector extends VCInspector implements SubInspecto this.documentLoader = documentLoader; return this; } + + public Builder uriResourceFactory(UriResourceFactory uriResourceFactory) { + this.uriResourceFactory = uriResourceFactory; + 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 d812cb8..57f939a 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 @@ -8,8 +8,6 @@ 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; @@ -48,6 +46,7 @@ import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe; import org.oneedtech.inspect.vc.probe.VerificationJWTProbe; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import com.apicatalog.jsonld.loader.DocumentLoader; @@ -55,8 +54,6 @@ 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 @@ -82,6 +79,7 @@ public class OB20Inspector extends VCInspector { ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); DocumentLoader documentLoader = getDocumentLoader(); + UriResourceFactory uriResourceFactory = getUriResourceFactory(documentLoader); RunContext ctx = new RunContext.Builder() .put(this) @@ -93,6 +91,7 @@ public class OB20Inspector extends VCInspector { .put(Key.SVG_CREDENTIAL_QNAME, SvgParser.QNames.OB20) .put(Key.JSON_DOCUMENT_LOADER, documentLoader) .put(Key.JWT_CREDENTIAL_NODE_NAME, Assertion.JWT_NODE_NAME) + .put(Key.URI_RESOURCE_FACTORY, uriResourceFactory) .build(); List accumulator = new ArrayList<>(); @@ -189,7 +188,10 @@ public class OB20Inspector extends VCInspector { } // 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(); + OB20EndorsementInspector endorsementInspector = new OB20EndorsementInspector.Builder() + .documentLoader(documentLoader) + .uriResourceFactory(uriResourceFactory) + .build(); // get endorsements for all JSON_LD objects in the graph List endorsements = jsonLdGeneratedObjects.stream().flatMap(node -> { @@ -201,7 +203,7 @@ public class OB20Inspector extends VCInspector { for(JsonNode node : endorsements) { probeCount++; // get endorsement json from context - UriResource uriResource = resolveUriResource(ctx, node.asText()); + UriResource uriResource = uriResourceFactory.of(node.asText()); JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); if (resolved == null) { throw new IllegalArgumentException("endorsement " + node.toString() + " not found in graph"); @@ -244,20 +246,4 @@ public class OB20Inspector extends VCInspector { */ 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 3e29fd7..60dd4ac 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 @@ -20,6 +20,8 @@ import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.code.Tuple; import org.oneedtech.inspect.vc.jsonld.probe.ExtensionProbe; +import org.oneedtech.inspect.vc.resource.DefaultUriResourceFactory; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -82,6 +84,10 @@ public abstract class VCInspector extends Inspector { return new CachingDocumentLoader(); } + protected UriResourceFactory getUriResourceFactory(DocumentLoader documentLoader) { + return new DefaultUriResourceFactory(); + } + protected List> getExtensionProbes(JsonNode node, String entryPath) { List> probes = new ArrayList<>(); if (!node.isObject()) { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java index 988d3fb..b5958e1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/GraphFetcherProbe.java @@ -26,6 +26,7 @@ import org.oneedtech.inspect.vc.Assertion.ValueType; import org.oneedtech.inspect.vc.Validation; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; @@ -126,9 +127,11 @@ public class GraphFetcherProbe extends Probe { private ReportItems fetchNode(RunContext ctx, ReportItems result, JsonNode idNode) throws URISyntaxException, Exception, JsonProcessingException, JsonMappingException { - UriResource uriResource = resolveUriResource(ctx, idNode.asText().strip()); + System.out.println("fetchNode " + idNode.asText().strip()); + UriResource uriResource = ((UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY)).of(idNode.asText().strip()); JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); if (resolved == null) { + System.out.println("parsing and loading " + idNode.asText().strip()); result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { Assertion fetchedAssertion = (Assertion) ctx.getGeneratedObject(uriResource.getID()); @@ -200,21 +203,6 @@ public class GraphFetcherProbe extends Probe { return matcher.matches(); } - 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; - } - public static final String ID = GraphFetcherProbe.class.getSimpleName(); public static final String URN_REGEX = "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'"; protected final static Logger logger = LogManager.getLogger(GraphFetcherProbe.class); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java index b1b8946..1aeb5f5 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/probe/JsonLDCompactionProve.java @@ -12,7 +12,6 @@ import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import com.apicatalog.jsonld.JsonLd; import com.apicatalog.jsonld.JsonLdOptions; -import com.apicatalog.jsonld.api.CompactionApi; import com.apicatalog.jsonld.document.JsonDocument; import com.apicatalog.jsonld.loader.DocumentLoader; 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 b951cb0..d91abe3 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 @@ -15,6 +15,7 @@ import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -41,9 +42,10 @@ public class AssertionRevocationListProbe extends Probe { public ReportItems run(JsonLdGeneratedObject jsonLdGeneratedObject, RunContext ctx) throws Exception { ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); + UriResourceFactory uriResourceFactory = (UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY); // get badge - UriResource badgeUriResource = resolveUriResource(ctx, getBadgeClaimId(jsonNode)); + UriResource badgeUriResource = uriResourceFactory.of(getBadgeClaimId(jsonNode)); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); @@ -51,7 +53,7 @@ public class AssertionRevocationListProbe extends Probe { JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) .readTree(badgeObject.getJson()); - UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + UriResource issuerUriResource = uriResourceFactory.of(badgeNode.get("issuer").asText().strip()); JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(issuerUriResource)); JsonNode issuerNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) @@ -63,7 +65,7 @@ public class AssertionRevocationListProbe extends Probe { return success(ctx); } - UriResource revocationListUriResource = resolveUriResource(ctx, revocationListIdNode.asText().strip()); + UriResource revocationListUriResource = uriResourceFactory.of(revocationListIdNode.asText().strip()); JsonLdGeneratedObject revocationListObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(revocationListUriResource)); JsonNode revocationListNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) @@ -88,21 +90,6 @@ public class AssertionRevocationListProbe extends Probe { return success(ctx); } - 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; - } - /** * Return the ID of the node with name propertyName * @param jsonNode node 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 38f34be..8011742 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 @@ -13,14 +13,12 @@ import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; -import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import foundation.identity.jsonld.ConfigurableDocumentLoader; - /** * Verification probe for Open Badges 2.0 * Maps to "ASSERTION_VERIFICATION_DEPENDENCIES" task in python implementation @@ -45,13 +43,14 @@ public class VerificationDependenciesProbe extends Probe public ReportItems run(JsonLdGeneratedObject jsonLdGeneratedObject, RunContext ctx) throws Exception { ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); JsonNode jsonNode = (mapper).readTree(jsonLdGeneratedObject.getJson()); + UriResourceFactory uriResourceFactory = (UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY); JsonNode verificationNode = jsonNode.get("verification"); checkNotNull(verificationNode); String type = null; if (verificationNode.isTextual()) { // get verification from graph - UriResource verificationUriResource = resolveUriResource(ctx, verificationNode.asText().strip()); + UriResource verificationUriResource = uriResourceFactory.of(verificationNode.asText().strip()); JsonLdGeneratedObject verificationObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(verificationUriResource)); JsonNode verificationRootNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) @@ -63,14 +62,14 @@ public class VerificationDependenciesProbe extends Probe if ("HostedBadge".equals(type)) { // get badge - UriResource badgeUriResource = resolveUriResource(ctx, getBadgeClaimId(jsonNode)); + UriResource badgeUriResource = uriResourceFactory.of(getBadgeClaimId(jsonNode)); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) .readTree(badgeObject.getJson()); // get issuer from badge - UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + UriResource issuerUriResource = uriResourceFactory.of(badgeNode.get("issuer").asText().strip()); JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(issuerUriResource)); @@ -139,21 +138,6 @@ public class VerificationDependenciesProbe extends Probe return issuerUri.getAuthority(); } - 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; - } - /** * Return the ID of the node with name propertyName * @param jsonNode node diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java index c75fbb5..d342f31 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/VerificationJWTProbe.java @@ -15,6 +15,7 @@ import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -44,16 +45,17 @@ public class VerificationJWTProbe extends Probe { public ReportItems run(JsonLdGeneratedObject assertion, RunContext ctx) throws Exception { ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER); JsonNode assertionNode = (mapper).readTree(assertion.getJson()); + UriResourceFactory uriResourceFactory = (UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY); // get badge from assertion - UriResource badgeUriResource = resolveUriResource(ctx, assertionNode.get("badge").asText().strip()); + UriResource badgeUriResource = uriResourceFactory.of(assertionNode.get("badge").asText().strip()); JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(badgeUriResource)); JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) .readTree(badgeObject.getJson()); // get issuer from badge - UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip()); + UriResource issuerUriResource = uriResourceFactory.of(badgeNode.get("issuer").asText().strip()); JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(issuerUriResource)); @@ -71,7 +73,7 @@ public class VerificationJWTProbe extends Probe { } // get creator from id - UriResource creatorUriResource = resolveUriResource(ctx, creatorId); + UriResource creatorUriResource = uriResourceFactory.of(creatorId); JsonLdGeneratedObject creatorObject = (JsonLdGeneratedObject) ctx.getGeneratedObject( JsonLDCompactionProve.getId(creatorUriResource)); JsonNode creatorNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER)) @@ -106,21 +108,6 @@ public class VerificationJWTProbe extends Probe { return success(ctx); } - 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; - } - private static final List allowedTypes = List.of("id", "email", "url", "telephone"); public static final String ID = VerificationJWTProbe.class.getSimpleName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java index e4123b4..30d3432 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationImagePropertyProbe.java @@ -5,10 +5,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; 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.util.resource.MimeType; import org.oneedtech.inspect.util.resource.UriResource; import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; import com.fasterxml.jackson.databind.JsonNode; @@ -56,7 +58,7 @@ public class ValidationImagePropertyProbe extends ValidationPropertyProbe { } } else if (!url.isEmpty()) { try { - UriResource uriResource = resolveUriResource(ctx, url); + UriResource uriResource = ((UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY)).of(url); // TODO: load resource from cache // TODO: check accept type -> 'Accept': 'application/ld+json, application/json, image/png, image/svg+xml' uriResource.asByteSource(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java index b7fdc56..d3590f2 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/validation/ValidationPropertyProbe.java @@ -22,6 +22,7 @@ import org.oneedtech.inspect.vc.Validation; import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject; import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve; import org.oneedtech.inspect.vc.probe.PropertyProbe; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -126,7 +127,7 @@ public class ValidationPropertyProbe extends PropertyProbe { } // get node from context - UriResource uriResource = resolveUriResource(ctx, childNode.asText()); + UriResource uriResource = ((UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY)).of(childNode.asText().strip()); JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource)); if (resolved == null) { if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) { @@ -153,21 +154,6 @@ public class ValidationPropertyProbe extends PropertyProbe { return result.size() > 0 ? result : success(ctx); } - 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; - } - private ReportItems validatePrerequisites(JsonNode node, RunContext ctx) { List results = validation.getPrerequisites().stream() .map(v -> ValidationPropertyProbeFactory.of(validation.getName(), v, validation.isFullValidate())) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/DefaultUriResourceFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/DefaultUriResourceFactory.java new file mode 100644 index 0000000..2c117d1 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/DefaultUriResourceFactory.java @@ -0,0 +1,19 @@ +package org.oneedtech.inspect.vc.resource; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.oneedtech.inspect.util.resource.UriResource; + +/** + * Default factory for URIResources + * @author xaracil + */ +public class DefaultUriResourceFactory implements UriResourceFactory { + + @Override + public UriResource of(String uri) throws URISyntaxException { + return new UriResource(new URI(uri)); + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/UriResourceFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/UriResourceFactory.java new file mode 100644 index 0000000..b5861f4 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/resource/UriResourceFactory.java @@ -0,0 +1,13 @@ +package org.oneedtech.inspect.vc.resource; + +import java.net.URISyntaxException; + +import org.oneedtech.inspect.util.resource.UriResource; + +/** + * Factory interface for URI resources + * @author xaracil + */ +public interface UriResourceFactory { + public UriResource of(String uri) throws URISyntaxException; +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/resource/TestUriResourceFactory.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/resource/TestUriResourceFactory.java new file mode 100644 index 0000000..b663bf5 --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/resource/TestUriResourceFactory.java @@ -0,0 +1,35 @@ +package org.oneedtech.inspect.vc.resource; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.apicatalog.jsonld.loader.DocumentLoader; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; + +/** + * UriResource factory for test, resolving local references + * @author xaracil + */ +public class TestUriResourceFactory implements UriResourceFactory { + + final DocumentLoader documentLoader; + + public TestUriResourceFactory(DocumentLoader documentLoader) { + this.documentLoader = documentLoader; + } + + @Override + public UriResource of(String uriString) throws URISyntaxException { + URI uri = new URI(uriString); + if (documentLoader instanceof CachingDocumentLoader) { + URI resolvedUri = ((CachingDocumentLoader.HttpLoader) ConfigurableDocumentLoader.getDefaultHttpLoader()).resolve(uri); + uri = resolvedUri; + } + return new UriResource(uri); + } + +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java index 2728550..c98b273 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/TestOB20Inspector.java @@ -8,6 +8,8 @@ import java.util.Map; import org.oneedtech.inspect.util.resource.ResourceType; import org.oneedtech.inspect.util.spec.Specification; import org.oneedtech.inspect.vc.OB20Inspector; +import org.oneedtech.inspect.vc.resource.TestUriResourceFactory; +import org.oneedtech.inspect.vc.resource.UriResourceFactory; import com.apicatalog.jsonld.loader.DocumentLoader; @@ -32,6 +34,11 @@ public class TestOB20Inspector extends OB20Inspector { return new CachingDocumentLoader(localDomains); } + @Override + protected UriResourceFactory getUriResourceFactory(DocumentLoader documentLoader) { + return new TestUriResourceFactory(documentLoader); + } + public static class TestBuilder extends OB20Inspector.Builder { final Map localDomains;