More validations and prerequisites
This commit is contained in:
parent
746300ab42
commit
7580a2c56b
@ -171,25 +171,33 @@ public class Assertion extends Credential {
|
|||||||
new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(),
|
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("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("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("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("issuedOn").type(ValueType.DATETIME).required(true).build(),
|
||||||
new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).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("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("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()
|
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(
|
.put(Type.BadgeClass, List.of(
|
||||||
new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(),
|
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("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("name").type(ValueType.TEXT).required(true).build(),
|
||||||
new Validation.Builder().name("description").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("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("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("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(
|
.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).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("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("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("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(
|
.put(Type.EndorsementClaim, List.of(
|
||||||
new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(),
|
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("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("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()
|
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(
|
.put(Type.Profile, List.of(
|
||||||
new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(),
|
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("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("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()
|
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(
|
.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("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.VerificationObject, List.of())
|
||||||
.put(Type.VerificationObjectAssertion, 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("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(
|
.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("type").type(ValueType.RDF_TYPE).required(false).many(true).defaultType(Type.VerificationObject).build(),
|
||||||
|
@ -23,6 +23,7 @@ import org.oneedtech.inspect.util.resource.ResourceType;
|
|||||||
import org.oneedtech.inspect.util.spec.Specification;
|
import org.oneedtech.inspect.util.spec.Specification;
|
||||||
import org.oneedtech.inspect.vc.Credential.CredentialEnum;
|
import org.oneedtech.inspect.vc.Credential.CredentialEnum;
|
||||||
import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject;
|
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.JsonLDCompactionProve;
|
||||||
import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe;
|
import org.oneedtech.inspect.vc.jsonld.probe.JsonLDValidationProbe;
|
||||||
import org.oneedtech.inspect.vc.payload.PngParser;
|
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.ExpirationProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
|
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
|
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.probe.validation.ValidationPropertyProbeFactory;
|
||||||
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
||||||
|
|
||||||
@ -122,12 +124,15 @@ public class OB20Inspector extends Inspector {
|
|||||||
accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx));
|
accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx));
|
||||||
if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount);
|
if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount);
|
||||||
|
|
||||||
|
// validation 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());
|
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<Validation> validations = assertion.getValidations();
|
List<Validation> validations = assertion.getValidations();
|
||||||
for (Validation validation : validations) {
|
for (Validation validation : validations) {
|
||||||
probeCount++;
|
probeCount++;
|
||||||
@ -142,6 +147,12 @@ public class OB20Inspector extends Inspector {
|
|||||||
accumulator.add(probe.run(assertion, ctx));
|
accumulator.add(probe.run(assertion, ctx));
|
||||||
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
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) {
|
} catch (Exception e) {
|
||||||
accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e));
|
accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e));
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ public class Validation {
|
|||||||
private final boolean required;
|
private final boolean required;
|
||||||
private final boolean many;
|
private final boolean many;
|
||||||
private final List<String> mustContainOne;
|
private final List<String> mustContainOne;
|
||||||
private final List<String> prerequisites;
|
private final List<Validation> prerequisites;
|
||||||
private final List<Assertion.Type> expectedTypes;
|
private final List<Assertion.Type> expectedTypes;
|
||||||
private final boolean allowRemoteUrl;
|
private final boolean allowRemoteUrl;
|
||||||
private final boolean allowDataUri;
|
private final boolean allowDataUri;
|
||||||
@ -21,6 +21,7 @@ public class Validation {
|
|||||||
private final String defaultType;
|
private final String defaultType;
|
||||||
private final boolean fullValidate;
|
private final boolean fullValidate;
|
||||||
private MessageLevel messageLevel;
|
private MessageLevel messageLevel;
|
||||||
|
private final boolean allowFlattenEmbeddedResource;
|
||||||
|
|
||||||
public Validation(Builder builder) {
|
public Validation(Builder builder) {
|
||||||
this.name = builder.name;
|
this.name = builder.name;
|
||||||
@ -36,6 +37,7 @@ public class Validation {
|
|||||||
this.defaultType = builder.defaultType;
|
this.defaultType = builder.defaultType;
|
||||||
this.fullValidate = builder.fullValidate;
|
this.fullValidate = builder.fullValidate;
|
||||||
this.messageLevel = builder.messageLevel;
|
this.messageLevel = builder.messageLevel;
|
||||||
|
this.allowFlattenEmbeddedResource = builder.allowFlattenEmbeddedResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -58,7 +60,7 @@ public class Validation {
|
|||||||
return mustContainOne;
|
return mustContainOne;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getPrerequisites() {
|
public List<Validation> getPrerequisites() {
|
||||||
return prerequisites;
|
return prerequisites;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +92,10 @@ public class Validation {
|
|||||||
return messageLevel;
|
return messageLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAllowFlattenEmbeddedResource() {
|
||||||
|
return allowFlattenEmbeddedResource;
|
||||||
|
}
|
||||||
|
|
||||||
public enum MessageLevel {
|
public enum MessageLevel {
|
||||||
Warning,
|
Warning,
|
||||||
Error
|
Error
|
||||||
@ -101,7 +107,7 @@ public class Validation {
|
|||||||
private boolean required;
|
private boolean required;
|
||||||
private boolean many;
|
private boolean many;
|
||||||
private List<String> mustContainOne;
|
private List<String> mustContainOne;
|
||||||
private List<String> prerequisites;
|
private List<Validation> prerequisites;
|
||||||
private List<Assertion.Type> expectedTypes;
|
private List<Assertion.Type> expectedTypes;
|
||||||
private boolean allowRemoteUrl;
|
private boolean allowRemoteUrl;
|
||||||
private boolean allowDataUri;
|
private boolean allowDataUri;
|
||||||
@ -109,6 +115,7 @@ public class Validation {
|
|||||||
private String defaultType;
|
private String defaultType;
|
||||||
private boolean fullValidate;
|
private boolean fullValidate;
|
||||||
private MessageLevel messageLevel;
|
private MessageLevel messageLevel;
|
||||||
|
private boolean allowFlattenEmbeddedResource;
|
||||||
|
|
||||||
public Builder() {
|
public Builder() {
|
||||||
this.mustContainOne = new ArrayList<>();
|
this.mustContainOne = new ArrayList<>();
|
||||||
@ -147,12 +154,12 @@ public class Validation {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder prerequisites(List<String> elems) {
|
public Builder prerequisites(List<Validation> elems) {
|
||||||
this.prerequisites = elems;
|
this.prerequisites = elems;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder prerequisite(String elem) {
|
public Builder prerequisite(Validation elem) {
|
||||||
this.prerequisites = List.of(elem);
|
this.prerequisites = List.of(elem);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -201,6 +208,11 @@ public class Validation {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder allowFlattenEmbeddedResource(boolean allowFlattenEmbeddedResource) {
|
||||||
|
this.allowFlattenEmbeddedResource = allowFlattenEmbeddedResource;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Validation build() {
|
public Validation build() {
|
||||||
return new Validation(this);
|
return new Validation(this);
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,14 @@ public class JsonLdGeneratedObject extends GeneratedObject {
|
|||||||
return json;
|
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();
|
public static final String ID = JsonLdGeneratedObject.class.getCanonicalName();
|
||||||
}
|
}
|
||||||
|
@ -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<JsonNode> {
|
||||||
|
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<Validation> 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<JsonNode> 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);
|
||||||
|
}
|
@ -18,7 +18,11 @@ import com.apicatalog.jsonld.loader.DocumentLoader;
|
|||||||
|
|
||||||
import jakarta.json.JsonObject;
|
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<Credential> {
|
public class JsonLDCompactionProve extends Probe<Credential> {
|
||||||
private final String context;
|
private final String context;
|
||||||
|
|
||||||
|
@ -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<JsonLdGeneratedObject> {
|
||||||
|
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<String> 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<String> 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();
|
||||||
|
|
||||||
|
}
|
@ -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<Assertion> {
|
||||||
|
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<String> 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<String> allowedTypes = List.of("id", "email", "url", "telephone");
|
||||||
|
public static final String ID = VerificationRecipientProbe.class.getSimpleName();
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -13,14 +13,19 @@ import org.oneedtech.inspect.vc.util.PrimitiveValueValidator;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
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 class ValidationImagePropertyProbe extends ValidationPropertyProbe {
|
||||||
|
|
||||||
public ValidationImagePropertyProbe(Validation validation) {
|
public ValidationImagePropertyProbe(Validation validation) {
|
||||||
super(validation);
|
super(ID, validation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) {
|
public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) {
|
||||||
super(validation, fullValidate);
|
super(ID, validation, fullValidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,4 +67,6 @@ public class ValidationImagePropertyProbe extends ValidationPropertyProbe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final List<MimeType> allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG);
|
private static final List<MimeType> allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG);
|
||||||
|
public static final String ID = ValidationImagePropertyProbe.class.getSimpleName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,19 @@ import org.oneedtech.inspect.vc.Validation.MessageLevel;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
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 class ValidationIssuerPropertyProbe extends ValidationPropertyProbe {
|
||||||
|
|
||||||
public ValidationIssuerPropertyProbe(Validation validation) {
|
public ValidationIssuerPropertyProbe(Validation validation) {
|
||||||
super(validation);
|
super(ID, validation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValidationIssuerPropertyProbe(Validation validation, boolean fullValidate) {
|
public ValidationIssuerPropertyProbe(Validation validation, boolean fullValidate) {
|
||||||
super(validation, fullValidate);
|
super(ID, validation, fullValidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -32,4 +37,7 @@ public class ValidationIssuerPropertyProbe extends ValidationPropertyProbe {
|
|||||||
}
|
}
|
||||||
return error(msg, ctx);
|
return error(msg, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String ID = ValidationIssuerPropertyProbe.class.getSimpleName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.ReportItems;
|
||||||
import org.oneedtech.inspect.core.report.ReportUtil;
|
import org.oneedtech.inspect.core.report.ReportUtil;
|
||||||
import org.oneedtech.inspect.util.resource.UriResource;
|
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.Assertion.ValueType;
|
||||||
import org.oneedtech.inspect.vc.Validation;
|
import org.oneedtech.inspect.vc.Validation;
|
||||||
import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject;
|
import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject;
|
||||||
import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve;
|
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.probe.PropertyProbe;
|
||||||
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
||||||
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
|
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
|
||||||
@ -32,17 +30,29 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import foundation.identity.jsonld.ConfigurableDocumentLoader;
|
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 {
|
public class ValidationPropertyProbe extends PropertyProbe {
|
||||||
protected final Validation validation;
|
protected final Validation validation;
|
||||||
protected final boolean fullValidate; // TODO: fullValidate
|
protected final boolean fullValidate; // TODO: fullValidate
|
||||||
|
|
||||||
public ValidationPropertyProbe(Validation validation) {
|
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) {
|
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.validation = validation;
|
||||||
this.fullValidate = fullValidate;
|
this.fullValidate = fullValidate;
|
||||||
setValidations(this::validate);
|
setValidations(this::validate);
|
||||||
@ -100,6 +110,11 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
for (JsonNode childNode : nodeList) {
|
||||||
if (childNode.isObject()) {
|
if (childNode.isObject()) {
|
||||||
result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx)));
|
result = new ReportItems(List.of(result, validateExpectedTypes(childNode, ctx)));
|
||||||
@ -114,7 +129,6 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
UriResource uriResource = resolveUriResource(ctx, childNode.asText());
|
UriResource uriResource = resolveUriResource(ctx, childNode.asText());
|
||||||
JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource));
|
JsonLdGeneratedObject resolved = (JsonLdGeneratedObject) ctx.getGeneratedObject(JsonLDCompactionProve.getId(uriResource));
|
||||||
if (resolved == null) {
|
if (resolved == null) {
|
||||||
if (!validation.isFetch()) {
|
|
||||||
if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) {
|
if (validation.isAllowRemoteUrl() && URL.getValidationFunction().apply(childNode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -124,25 +138,11 @@ 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);
|
return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx);
|
||||||
} else {
|
} else {
|
||||||
// fetch
|
ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER);
|
||||||
result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx)));
|
JsonNode resolvedNode = mapper.readTree(resolved.getJson());
|
||||||
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
|
// 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;
|
return uriResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReportItems validatePrerequisites(JsonNode node, RunContext ctx) {
|
||||||
|
List<ReportItems> 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) {
|
private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) {
|
||||||
List<ReportItems> results = validation.getExpectedTypes().stream()
|
List<ReportItems> results = validation.getExpectedTypes().stream()
|
||||||
.flatMap(type -> type.getValidations().stream())
|
.flatMap(type -> type.getValidations().stream())
|
||||||
|
@ -13,13 +13,18 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
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 class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe {
|
||||||
public ValidationRdfTypePropertyProbe(Validation validation) {
|
public ValidationRdfTypePropertyProbe(Validation validation) {
|
||||||
super(validation);
|
super(ID, validation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) {
|
public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) {
|
||||||
super(validation, fullValidate);
|
super(ID, validation, fullValidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -55,4 +60,6 @@ public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe {
|
|||||||
}
|
}
|
||||||
return new ReportItems(List.of(result, success(ctx)));
|
return new ReportItems(List.of(result, success(ctx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String ID = ValidationRdfTypePropertyProbe.class.getSimpleName();
|
||||||
}
|
}
|
||||||
|
@ -58,4 +58,12 @@ public class JsonNodeUtil {
|
|||||||
.collect(Collectors.toList());
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user