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); + } + } +}