Merge branch 'feature/open-badges-20' into dec22-release
# Conflicts: # inspector-util/src/main/java/org/oneedtech/inspect/util/spec/Specification.java # inspector-web/src/main/resources/validators.json
This commit is contained in:
commit
0932ca87a8
@ -0,0 +1,349 @@
|
||||
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;
|
||||
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.Validation.MessageLevel;
|
||||
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;
|
||||
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 Credential {
|
||||
|
||||
final Assertion.Type assertionType;
|
||||
|
||||
protected Assertion(Resource resource, JsonNode data, String jwt, Map<CredentialEnum, SchemaKey> 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialEnum getCredentialType() {
|
||||
return assertionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("super", super.toString())
|
||||
.add("assertionType", assertionType)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public List<Validation> getValidations() {
|
||||
return validationMap.get(assertionType);
|
||||
}
|
||||
|
||||
private static final Map<CredentialEnum, SchemaKey> schemas = new ImmutableMap.Builder<CredentialEnum, SchemaKey>()
|
||||
.put(Type.Assertion, Catalog.OB_21_ASSERTION_JSON)
|
||||
.build();
|
||||
|
||||
public static class Builder extends Credential.Builder<Assertion> {
|
||||
|
||||
@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, ISSUED_ON_PROPERTY_NAME, EXPIRES_AT_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
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")),
|
||||
External(Collections.emptyList(), false),
|
||||
Unknown(Collections.emptyList());
|
||||
|
||||
public static List<Type> primaryObjects = List.of(Assertion, BadgeClass, Issuer, Profile, Endorsement);
|
||||
|
||||
private final List<String> allowedTypeValues;
|
||||
private final boolean allowedTypeValuesRequired;
|
||||
|
||||
Type(List<String> typeValues) {
|
||||
this(typeValues, true);
|
||||
}
|
||||
|
||||
Type(List<String> typeValues, boolean allowedTypeValuesRequired) {
|
||||
this.allowedTypeValues = typeValues;
|
||||
this.allowedTypeValuesRequired = allowedTypeValuesRequired;
|
||||
}
|
||||
|
||||
public static Assertion.Type valueOf (JsonNode typeNode) {
|
||||
if(typeNode != null) {
|
||||
List<String> values = JsonNodeUtil.asStringList(typeNode);
|
||||
for (String value : values) {
|
||||
Type found = Arrays.stream(Type.values())
|
||||
.filter(type -> value.equals(type.toString()))
|
||||
.findFirst()
|
||||
.orElse(Unknown);
|
||||
if (found != Unknown) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check external type
|
||||
if (validateIri(typeNode)) {
|
||||
return External;
|
||||
}
|
||||
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRequiredTypeValues() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllowedTypeValues() {
|
||||
return allowedTypeValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getContextUris() {
|
||||
return List.of("https://w3id.org/openbadges/v2") ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedTypeValuesRequired() {
|
||||
return allowedTypeValuesRequired;
|
||||
}
|
||||
|
||||
public List<Validation> getValidations() {
|
||||
return validationMap.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ValueType {
|
||||
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),
|
||||
|
||||
IMAGE(null),
|
||||
ISSUER(null);
|
||||
|
||||
private final Function<JsonNode, Boolean> validationFunction;
|
||||
|
||||
private ValueType(Function<JsonNode, Boolean> validationFunction) {
|
||||
this.validationFunction = validationFunction;
|
||||
}
|
||||
|
||||
public Function<JsonNode, Boolean> getValidationFunction() {
|
||||
return validationFunction;
|
||||
}
|
||||
|
||||
public static List<ValueType> 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<Type, List<Validation>> validationMap = new ImmutableMap.Builder<Type, List<Validation>>()
|
||||
.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).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).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).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(
|
||||
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).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).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).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("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(),
|
||||
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).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).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).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).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").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()
|
||||
))
|
||||
.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).build()
|
||||
))
|
||||
.put(Type.Evidence, List.of(
|
||||
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).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).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).build()
|
||||
))
|
||||
.put(Type.Image, List.of(
|
||||
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("id").type(ValueType.IMAGE).allowDataUri(true).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).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).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(),
|
||||
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(
|
||||
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).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).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).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(
|
||||
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()
|
||||
))
|
||||
.put(Type.VerificationObject, List.of())
|
||||
.put(Type.VerificationObjectAssertion, List.of(
|
||||
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).many(true).defaultType(Type.VerificationObject).build(),
|
||||
new Validation.Builder().name("verificationProperty").type(ValueType.COMPACT_IRI).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())
|
||||
.put(Type.Unknown, Collections.emptyList())
|
||||
.build();
|
||||
|
||||
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
|
||||
}
|
@ -1,50 +1,48 @@
|
||||
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.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.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
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;
|
||||
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.
|
||||
* @author mgylling
|
||||
* 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 class Credential extends GeneratedObject {
|
||||
public abstract class Credential extends GeneratedObject {
|
||||
final Resource resource;
|
||||
final JsonNode jsonData;
|
||||
final Credential.Type credentialType;
|
||||
final String jwt;
|
||||
final String issuedOnPropertyName;
|
||||
final String expiresAtPropertyName;
|
||||
final Map<CredentialEnum, SchemaKey> schemas;
|
||||
|
||||
public Credential(Resource resource, JsonNode data, String jwt) {
|
||||
super(ID, GeneratedObject.Type.INTERNAL);
|
||||
protected Credential(String id, Resource resource, JsonNode data, String jwt, Map<CredentialEnum, SchemaKey> 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()));
|
||||
|
||||
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() {
|
||||
@ -55,79 +53,81 @@ public class Credential extends GeneratedObject {
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
public Credential.Type getCredentialType() {
|
||||
return credentialType;
|
||||
}
|
||||
|
||||
public Optional<String> getJwt() {
|
||||
return Optional.ofNullable(jwt);
|
||||
}
|
||||
|
||||
public ProofType getProofType() {
|
||||
return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL;
|
||||
public String getIssuedOnPropertyName() {
|
||||
return issuedOnPropertyName;
|
||||
}
|
||||
|
||||
|
||||
private static final Map<Credential.Type, SchemaKey> schemas = new ImmutableMap.Builder<Credential.Type, SchemaKey>()
|
||||
.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)
|
||||
.build();
|
||||
public String getExpiresAtPropertyName() {
|
||||
return expiresAtPropertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonical schema for this credential if such exists.
|
||||
*/
|
||||
public Optional<SchemaKey> getSchemaKey() {
|
||||
return Optional.ofNullable(schemas.get(credentialType));
|
||||
return Optional.ofNullable(schemas.get(getCredentialType()));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
AchievementCredential,
|
||||
OpenBadgeCredential, //treated as an alias of AchievementCredential
|
||||
ClrCredential,
|
||||
EndorsementCredential,
|
||||
VerifiablePresentation,
|
||||
VerifiableCredential, //this is an underspecifier in our context
|
||||
Unknown;
|
||||
|
||||
public static Credential.Type valueOf (ArrayNode typeArray) {
|
||||
if(typeArray != null) {
|
||||
Iterator<JsonNode> iter = typeArray.iterator();
|
||||
while(iter.hasNext()) {
|
||||
String value = iter.next().asText();
|
||||
if(value.equals("AchievementCredential") || value.equals("OpenBadgeCredential")) {
|
||||
return AchievementCredential;
|
||||
} else if(value.equals("ClrCredential")) {
|
||||
return ClrCredential;
|
||||
} else if(value.equals("VerifiablePresentation")) {
|
||||
return VerifiablePresentation;
|
||||
} else if(value.equals("EndorsementCredential")) {
|
||||
return EndorsementCredential;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProofType {
|
||||
EXTERNAL,
|
||||
EMBEDDED
|
||||
}
|
||||
public abstract CredentialEnum getCredentialType();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("resource", resource.getID())
|
||||
.add("resourceType", resource.getType())
|
||||
.add("credentialType", credentialType)
|
||||
.add("json", jsonData)
|
||||
.add("jwt", jwt)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static final String ID = Credential.class.getCanonicalName();
|
||||
public static final List<ResourceType> RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT);
|
||||
public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY";
|
||||
|
||||
public interface CredentialEnum {
|
||||
List<String> getRequiredTypeValues();
|
||||
List<String> getAllowedTypeValues();
|
||||
boolean isAllowedTypeValuesRequired();
|
||||
List<String> getContextUris();
|
||||
String toString();
|
||||
}
|
||||
|
||||
public abstract static class Builder<B extends Credential> {
|
||||
private Resource resource;
|
||||
private JsonNode jsonData;
|
||||
private String jwt;
|
||||
|
||||
public abstract B build();
|
||||
|
||||
public Builder<B> resource(Resource resource) {
|
||||
this.resource = resource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<B> jsonData(JsonNode node) {
|
||||
this.jsonData = node;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<B> jwt(String jwt) {
|
||||
this.jwt = jwt;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Resource getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
protected JsonNode getJsonData() {
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
protected String getJwt() {
|
||||
return jwt;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ 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.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;
|
||||
@ -60,13 +60,14 @@ public class EndorsementInspector extends VCInspector implements SubInspector {
|
||||
*
|
||||
*/
|
||||
|
||||
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(resource)
|
||||
.put(JACKSON_OBJECTMAPPER, mapper)
|
||||
.put(JSONPATH_EVALUATOR, jsonPath)
|
||||
.build();
|
||||
@ -76,7 +77,7 @@ public class EndorsementInspector extends VCInspector implements SubInspector {
|
||||
try {
|
||||
|
||||
//context and type properties
|
||||
Credential.Type type = Type.EndorsementCredential;
|
||||
VerifiableCredential.Type type = Type.EndorsementCredential;
|
||||
for(Probe<JsonNode> probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) {
|
||||
probeCount++;
|
||||
accumulator.add(probe.run(endorsement.getJson(), ctx));
|
||||
|
@ -0,0 +1,150 @@
|
||||
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.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;
|
||||
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 org.oneedtech.inspect.vc.resource.UriResourceFactory;
|
||||
|
||||
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;
|
||||
private UriResourceFactory uriResourceFactory;
|
||||
|
||||
protected OB20EndorsementInspector(OB20EndorsementInspector.Builder builder) {
|
||||
super(builder);
|
||||
this.documentLoader = builder.documentLoader;
|
||||
this.uriResourceFactory = builder.uriResourceFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Report run(Resource resource, Map<String, GeneratedObject> 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)
|
||||
.put(URI_RESOURCE_FACTORY, uriResourceFactory)
|
||||
.build();
|
||||
|
||||
parentObjects.entrySet().stream().forEach(entry -> {
|
||||
if (!entry.getKey().equals(CREDENTIAL_KEY)) {
|
||||
ctx.addGeneratedObject(entry.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
List<ReportItems> accumulator = new ArrayList<>();
|
||||
int probeCount = 0;
|
||||
try {
|
||||
|
||||
JsonNode endorsementNode = endorsement.getJson();
|
||||
// verification and revocation
|
||||
if (endorsement.getCredentialType() == Type.Endorsement) {
|
||||
for(Probe<JsonLdGeneratedObject> 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<Credential> 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 <R extends Resource> Report run(R resource) {
|
||||
throw new IllegalStateException("must use #run(resource, map)");
|
||||
}
|
||||
|
||||
public static class Builder extends VCInspector.Builder<OB20EndorsementInspector.Builder> {
|
||||
private DocumentLoader documentLoader;
|
||||
private UriResourceFactory uriResourceFactory;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public OB20EndorsementInspector build() {
|
||||
return new OB20EndorsementInspector(this);
|
||||
}
|
||||
|
||||
public Builder documentLoader(DocumentLoader documentLoader) {
|
||||
this.documentLoader = documentLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder uriResourceFactory(UriResourceFactory uriResourceFactory) {
|
||||
this.uriResourceFactory = uriResourceFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
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;
|
||||
import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY;
|
||||
import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.oneedtech.inspect.core.Inspector;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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.resource.UriResourceFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A verifier for Open Badges 2.0.
|
||||
* @author xaracil
|
||||
*/
|
||||
public class OB20Inspector extends VCInspector {
|
||||
|
||||
protected <B extends VCInspector.Builder<?>> OB20Inspector(B builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.oneedtech.inspect.core.Inspector#run(org.oneedtech.inspect.util.resource.Resource)
|
||||
*/
|
||||
@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);
|
||||
DocumentLoader documentLoader = getDocumentLoader();
|
||||
UriResourceFactory uriResourceFactory = getUriResourceFactory(documentLoader);
|
||||
|
||||
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)
|
||||
.put(Key.JSON_DOCUMENT_LOADER, documentLoader)
|
||||
.put(Key.JWT_CREDENTIAL_NODE_NAME, Assertion.JWT_NODE_NAME)
|
||||
.put(Key.URI_RESOURCE_FACTORY, uriResourceFactory)
|
||||
.build();
|
||||
|
||||
List<ReportItems> 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
|
||||
Assertion assertion = ctx.getGeneratedObject(resource.getID());
|
||||
|
||||
//context and type properties
|
||||
CredentialEnum type = assertion.getCredentialType();
|
||||
for(Probe<JsonNode> 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(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(JsonLDCompactionProve.getId(assertion));
|
||||
accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx));
|
||||
if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount);
|
||||
|
||||
// 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<Validation> validations = assertion.getValidations();
|
||||
for (Validation validation : validations) {
|
||||
probeCount++;
|
||||
accumulator.add(ValidationPropertyProbeFactory.of(assertion.getCredentialType().toString(), validation).run(assertionNode, ctx));
|
||||
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||
}
|
||||
|
||||
// verification and revocation
|
||||
if (assertion.getCredentialType() == Type.Assertion) {
|
||||
for(Probe<JsonLdGeneratedObject> 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// expiration and issuance
|
||||
for(Probe<Credential> probe : List.of(
|
||||
new ExpirationProbe(), new IssuanceProbe())) {
|
||||
probeCount++;
|
||||
accumulator.add(probe.run(assertion, ctx));
|
||||
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||
}
|
||||
|
||||
// get all json-ld generated objects for both extension and endorsements validation
|
||||
List<JsonNode> jsonLdGeneratedObjects = ctx.getGeneratedObjects().values().stream()
|
||||
.filter(generatedObject -> generatedObject instanceof JsonLdGeneratedObject)
|
||||
.map(obj -> {
|
||||
|
||||
try {
|
||||
return mapper.readTree(((JsonLdGeneratedObject) obj).getJson());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Couldn't not parse " + obj.getId() + ": contains invalid JSON");
|
||||
}
|
||||
})
|
||||
.collect(toList());
|
||||
|
||||
// validate extensions
|
||||
List<Tuple<ExtensionProbe, JsonNode>> extensionProbeTuples = jsonLdGeneratedObjects.stream()
|
||||
.flatMap(node -> getExtensionProbes(node, "id").stream())
|
||||
.collect(toList());
|
||||
for (Tuple<ExtensionProbe, JsonNode> extensionProbeTuple : extensionProbeTuples) {
|
||||
probeCount++;
|
||||
accumulator.add(extensionProbeTuple.t1.run(extensionProbeTuple.t2, 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)
|
||||
.uriResourceFactory(uriResourceFactory)
|
||||
.build();
|
||||
|
||||
// get endorsements for all JSON_LD objects in the graph
|
||||
List<JsonNode> 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(toList());
|
||||
|
||||
for(JsonNode node : endorsements) {
|
||||
probeCount++;
|
||||
// get endorsement json from context
|
||||
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");
|
||||
}
|
||||
|
||||
Assertion endorsement = new Assertion.Builder().resource(resource).jsonData(mapper.readTree(resolved.getJson())).build();
|
||||
// pass graph to subinspector
|
||||
Map<String, GeneratedObject> 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));
|
||||
}
|
||||
|
||||
return new Report(ctx, new ReportItems(accumulator), probeCount);
|
||||
}
|
||||
|
||||
public static class Builder extends VCInspector.Builder<OB20Inspector.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() {
|
||||
set(Specification.OB20);
|
||||
set(ResourceType.OPENBADGE);
|
||||
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";
|
||||
}
|
||||
}
|
@ -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.Credential.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,9 @@ 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;
|
||||
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
|
||||
import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe;
|
||||
@ -56,7 +58,7 @@ import com.google.common.collect.ImmutableList;
|
||||
* @author mgylling
|
||||
*/
|
||||
public class OB30Inspector extends VCInspector implements SubInspector {
|
||||
protected final List<Probe<Credential>> userProbes;
|
||||
protected final List<Probe<VerifiableCredential>> userProbes;
|
||||
|
||||
protected OB30Inspector(OB30Inspector.Builder builder) {
|
||||
super(builder);
|
||||
@ -91,6 +93,10 @@ 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 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<ReportItems> accumulator = new ArrayList<>();
|
||||
@ -103,15 +109,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<Credential> probe : userProbes) {
|
||||
for(Probe<VerifiableCredential> probe : userProbes) {
|
||||
probeCount++;
|
||||
accumulator.add(probe.run(ob, ctx));
|
||||
}
|
||||
@ -126,15 +132,19 @@ public class OB30Inspector extends VCInspector implements SubInspector {
|
||||
@Override
|
||||
public Report run(Resource resource, Map<String, GeneratedObject> 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);
|
||||
VerifiableCredential.Builder credentialBuilder = new VerifiableCredential.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<ReportItems> accumulator = new ArrayList<>();
|
||||
@ -143,7 +153,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<JsonNode> probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) {
|
||||
probeCount++;
|
||||
accumulator.add(probe.run(ob.getJson(), ctx));
|
||||
@ -198,7 +208,7 @@ public class OB30Inspector extends VCInspector implements SubInspector {
|
||||
List<JsonNode> endorsements = asNodeList(ob.getJson(), "$..endorsement", jsonPath);
|
||||
for(JsonNode node : endorsements) {
|
||||
probeCount++;
|
||||
Credential endorsement = new Credential(resource, node);
|
||||
VerifiableCredential endorsement = credentialBuilder.resource(resource).jsonData(node).build();
|
||||
accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement)));
|
||||
}
|
||||
|
||||
@ -208,7 +218,7 @@ public class OB30Inspector extends VCInspector implements SubInspector {
|
||||
probeCount++;
|
||||
String jwt = node.asText();
|
||||
JsonNode vcNode = fromJwt(jwt, ctx);
|
||||
Credential endorsement = new Credential(resource, vcNode, jwt);
|
||||
VerifiableCredential endorsement = credentialBuilder.resource(resource).jsonData(node).jwt(jwt).build();
|
||||
accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement)));
|
||||
}
|
||||
|
||||
|
@ -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,14 @@ 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.resource.DefaultUriResourceFactory;
|
||||
import org.oneedtech.inspect.vc.resource.UriResourceFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -47,7 +62,7 @@ public abstract class VCInspector extends Inspector {
|
||||
* provided, then start the verification process over using the response as input. If the request fails,
|
||||
* the credential is invalid.
|
||||
*/
|
||||
protected Optional<String> checkRefreshService(Credential crd, RunContext ctx) {
|
||||
protected Optional<String> checkRefreshService(VerifiableCredential crd, RunContext ctx) {
|
||||
JsonNode refreshServiceNode = crd.getJson().get("refreshService");
|
||||
if(refreshServiceNode != null) {
|
||||
JsonNode serviceTypeNode = refreshServiceNode.get("type");
|
||||
@ -61,17 +76,69 @@ 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 UriResourceFactory getUriResourceFactory(DocumentLoader documentLoader) {
|
||||
return new DefaultUriResourceFactory();
|
||||
}
|
||||
|
||||
protected List<Tuple<ExtensionProbe, JsonNode>> getExtensionProbes(JsonNode node, String entryPath) {
|
||||
List<Tuple<ExtensionProbe, JsonNode>> probes = new ArrayList<>();
|
||||
if (!node.isObject()) {
|
||||
return probes;
|
||||
}
|
||||
|
||||
if (node.has("type")) {
|
||||
List<String> types = asStringList(node.get("type"));
|
||||
|
||||
// only validate extension types
|
||||
if (types.contains("Extension")) {
|
||||
List<String> typesToTest = types.stream().filter(type -> !type.equals("Extension")).collect(toList());
|
||||
// add an extension Probe
|
||||
probes.add(new Tuple<ExtensionProbe,JsonNode>(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<JsonNode> childNodes = JsonNodeUtil.asNodeList(entry.getValue());
|
||||
List<Tuple<ExtensionProbe, JsonNode>> 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<B extends VCInspector.Builder<B>> extends Inspector.Builder<B> {
|
||||
final List<Probe<Credential>> probes;
|
||||
final List<Probe<VerifiableCredential>> probes;
|
||||
|
||||
public Builder() {
|
||||
super();
|
||||
this.probes = new ArrayList<>();
|
||||
}
|
||||
|
||||
public VCInspector.Builder<B> add(Probe<Credential> probe) {
|
||||
public VCInspector.Builder<B> add(Probe<VerifiableCredential> probe) {
|
||||
probes.add(probe);
|
||||
return this;
|
||||
}
|
||||
|
@ -0,0 +1,221 @@
|
||||
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<String> mustContainOne;
|
||||
private final List<Validation> prerequisites;
|
||||
private final List<Assertion.Type> expectedTypes;
|
||||
private final boolean allowRemoteUrl;
|
||||
private final boolean allowDataUri;
|
||||
private final boolean fetch;
|
||||
private final String defaultType;
|
||||
private final boolean fullValidate;
|
||||
private MessageLevel messageLevel;
|
||||
private final boolean allowFlattenEmbeddedResource;
|
||||
|
||||
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;
|
||||
this.messageLevel = builder.messageLevel;
|
||||
this.allowFlattenEmbeddedResource = builder.allowFlattenEmbeddedResource;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Assertion.ValueType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public boolean isMany() {
|
||||
return many;
|
||||
}
|
||||
|
||||
public List<String> getMustContainOne() {
|
||||
return mustContainOne;
|
||||
}
|
||||
|
||||
public List<Validation> getPrerequisites() {
|
||||
return prerequisites;
|
||||
}
|
||||
|
||||
public List<Assertion.Type> 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 MessageLevel getMessageLevel() {
|
||||
return messageLevel;
|
||||
}
|
||||
|
||||
public boolean isAllowFlattenEmbeddedResource() {
|
||||
return allowFlattenEmbeddedResource;
|
||||
}
|
||||
|
||||
public enum MessageLevel {
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String name;
|
||||
private Assertion.ValueType type;
|
||||
private boolean required;
|
||||
private boolean many;
|
||||
private List<String> mustContainOne;
|
||||
private List<Validation> prerequisites;
|
||||
private List<Assertion.Type> expectedTypes;
|
||||
private boolean allowRemoteUrl;
|
||||
private boolean allowDataUri;
|
||||
private boolean fetch;
|
||||
private String defaultType;
|
||||
private boolean fullValidate;
|
||||
private MessageLevel messageLevel;
|
||||
private boolean allowFlattenEmbeddedResource;
|
||||
|
||||
public Builder() {
|
||||
this.mustContainOne = new ArrayList<>();
|
||||
this.prerequisites = new ArrayList<>();
|
||||
this.expectedTypes = new ArrayList<>();
|
||||
this.messageLevel = MessageLevel.Error;
|
||||
this.fullValidate = true; // by default, full validation
|
||||
}
|
||||
|
||||
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<String> elems) {
|
||||
this.mustContainOne = elems;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder mustContainOneType(List<Assertion.Type> types) {
|
||||
this.mustContainOne = types.stream().map(Assertion.Type::toString).collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder prerequisites(List<Validation> elems) {
|
||||
this.prerequisites = elems;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder prerequisite(Validation elem) {
|
||||
this.prerequisites = List.of(elem);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expectedTypes(List<Assertion.Type> 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 Builder messageLevel(MessageLevel messageLevel) {
|
||||
this.messageLevel = messageLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowFlattenEmbeddedResource(boolean allowFlattenEmbeddedResource) {
|
||||
this.allowFlattenEmbeddedResource = allowFlattenEmbeddedResource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Validation build() {
|
||||
return new Validation(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package org.oneedtech.inspect.vc;
|
||||
|
||||
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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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;
|
||||
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.
|
||||
* @author mgylling
|
||||
*/
|
||||
public class VerifiableCredential extends Credential {
|
||||
final VerifiableCredential.Type credentialType;
|
||||
|
||||
protected VerifiableCredential(Resource resource, JsonNode data, String jwt, Map<CredentialEnum, SchemaKey> schemas, String issuedOnPropertyName, String expiresAtPropertyName) {
|
||||
super(ID, resource, data, jwt, schemas, issuedOnPropertyName, expiresAtPropertyName);
|
||||
|
||||
JsonNode typeNode = jsonData.get("type");
|
||||
this.credentialType = VerifiableCredential.Type.valueOf(typeNode);
|
||||
}
|
||||
|
||||
public CredentialEnum getCredentialType() {
|
||||
return credentialType;
|
||||
}
|
||||
|
||||
public ProofType getProofType() {
|
||||
return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL;
|
||||
}
|
||||
|
||||
private static final Map<CredentialEnum, SchemaKey> schemas = new ImmutableMap.Builder<CredentialEnum, SchemaKey>()
|
||||
.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)
|
||||
.build();
|
||||
|
||||
private static final Map<Set<VerifiableCredential.Type>, List<String>> contextMap = new ImmutableMap.Builder<Set<VerifiableCredential.Type>, List<String>>()
|
||||
.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 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<String> allowedTypeValues;
|
||||
|
||||
Type(List<String> allowedTypeValues) {
|
||||
this.allowedTypeValues = allowedTypeValues;
|
||||
}
|
||||
|
||||
public static VerifiableCredential.Type valueOf (JsonNode typeNode) {
|
||||
if(typeNode != null) {
|
||||
List<String> values = JsonNodeUtil.asStringList(typeNode);
|
||||
for (String value : values) {
|
||||
if(value.equals("AchievementCredential") || value.equals("OpenBadgeCredential")) {
|
||||
return AchievementCredential;
|
||||
} else if(value.equals("ClrCredential")) {
|
||||
return ClrCredential;
|
||||
} else if(value.equals("VerifiablePresentation")) {
|
||||
return VerifiablePresentation;
|
||||
} else if(value.equals("EndorsementCredential")) {
|
||||
return EndorsementCredential;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRequiredTypeValues() {
|
||||
return List.of("VerifiableCredential");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllowedTypeValues() {
|
||||
return allowedTypeValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedTypeValuesRequired() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public List<String> getContextUris() {
|
||||
return contextMap.get(contextMap.keySet()
|
||||
.stream()
|
||||
.filter(s->s.contains(this))
|
||||
.findFirst()
|
||||
.orElseThrow(()-> new IllegalArgumentException(this.name() + " not recognized")));
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProofType {
|
||||
EXTERNAL,
|
||||
EMBEDDED
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("super", super.toString())
|
||||
.add("credentialType", credentialType)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static class Builder extends Credential.Builder<VerifiableCredential> {
|
||||
@Override
|
||||
public VerifiableCredential build() {
|
||||
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";
|
||||
public static final String JWT_NODE_NAME = "vc";
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.oneedtech.inspect.vc.jsonld;
|
||||
|
||||
import org.oneedtech.inspect.core.probe.GeneratedObject;
|
||||
|
||||
public class JsonLdGeneratedObject extends GeneratedObject {
|
||||
private String json;
|
||||
|
||||
public JsonLdGeneratedObject(String json) {
|
||||
this(ID, json);
|
||||
}
|
||||
|
||||
public JsonLdGeneratedObject(String id, String json) {
|
||||
super(id, GeneratedObject.Type.INTERNAL);
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
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();
|
||||
}
|
@ -0,0 +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<JsonNode> {
|
||||
private final List<String> typesToTest;
|
||||
|
||||
public ExtensionProbe(String entryPath, List<String> typesToTest) {
|
||||
super(ID, entryPath, typesToTest.stream().collect(joining()));
|
||||
this.typesToTest = typesToTest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportItems run(JsonNode node, RunContext ctx) throws Exception {
|
||||
ReportItems reportItems = new ReportItems();
|
||||
DocumentLoader documentLoader = (DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER);
|
||||
Set<URI> contexts = null;
|
||||
if (documentLoader instanceof CachingDocumentLoader) {
|
||||
contexts = new HashSet<>(((CachingDocumentLoader) documentLoader).getContexts());
|
||||
} else {
|
||||
contexts = Set.of();
|
||||
}
|
||||
|
||||
// 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<JsonNode> 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 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<JsonNode> 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";
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
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.resource.UriResourceFactory;
|
||||
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;
|
||||
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
|
||||
result = fetchNode(ctx, result, idNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<JsonNode> nodeList = JsonNodeUtil.asNodeList(node);
|
||||
for (JsonNode childNode : nodeList) {
|
||||
if (shouldFetch(childNode, validation)) {
|
||||
// get node from context
|
||||
result = fetchNode(ctx, result, childNode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
private ReportItems fetchNode(RunContext ctx, ReportItems result, JsonNode idNode)
|
||||
throws URISyntaxException, Exception, JsonProcessingException, JsonMappingException {
|
||||
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());
|
||||
|
||||
// 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
|
||||
* - 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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.oneedtech.inspect.vc.jsonld.probe;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
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;
|
||||
|
||||
import com.apicatalog.jsonld.JsonLd;
|
||||
import com.apicatalog.jsonld.JsonLdOptions;
|
||||
import com.apicatalog.jsonld.document.JsonDocument;
|
||||
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<Credential> {
|
||||
private final String context;
|
||||
|
||||
public JsonLDCompactionProve(String context) {
|
||||
super(ID);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportItems run(Credential crd, RunContext ctx) throws Exception {
|
||||
try {
|
||||
// compact JSON
|
||||
JsonDocument jsonDocument = JsonDocument.of(new StringReader(crd.getJson().toString()));
|
||||
JsonObject compactedObject = JsonLd.compact(jsonDocument, context)
|
||||
.options(new JsonLdOptions((DocumentLoader) ctx.get(Key.JSON_DOCUMENT_LOADER)))
|
||||
.get();
|
||||
|
||||
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
|
||||
&& !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 compacting JSON-LD: " + crd.getJson() + ". Caused by: " + e.getMessage(), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getId(Credential crd) {
|
||||
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();
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.oneedtech.inspect.vc.jsonld.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.jsonld.JsonLdGeneratedObject;
|
||||
|
||||
import foundation.identity.jsonld.JsonLDObject;
|
||||
import foundation.identity.jsonld.validation.Validation;
|
||||
|
||||
public class JsonLDValidationProbe extends Probe<Credential> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
@ -26,7 +26,11 @@ public final class JsonParser extends PayloadParser {
|
||||
checkTrue(resource.getType() == ResourceType.JSON);
|
||||
String json = resource.asByteSource().asCharSource(UTF_8).read();
|
||||
JsonNode node = fromString(json, ctx);
|
||||
return new Credential(resource, node);
|
||||
|
||||
return getBuilder(ctx)
|
||||
.resource(resource)
|
||||
.jsonData(node)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,11 @@ public final class JwtParser extends PayloadParser {
|
||||
checkTrue(resource.getType() == ResourceType.JWT);
|
||||
String jwt = resource.asByteSource().asCharSource(UTF_8).read();
|
||||
JsonNode node = fromJwt(jwt, ctx);
|
||||
return new Credential(resource, node, jwt);
|
||||
return getBuilder(ctx)
|
||||
.resource(resource)
|
||||
.jsonData(node)
|
||||
.jwt(jwt)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
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;
|
||||
@ -23,6 +26,11 @@ public abstract class PayloadParser {
|
||||
|
||||
public abstract Credential parse(Resource source, RunContext ctx) throws Exception;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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 {
|
||||
return ((ObjectMapper)context.get(RunContext.Key.JACKSON_OBJECTMAPPER)).readTree(json);
|
||||
}
|
||||
@ -42,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;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public final class PngParser extends PayloadParser {
|
||||
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);
|
||||
@ -48,7 +49,7 @@ public final class PngParser extends PayloadParser {
|
||||
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]));
|
||||
formatSearch = getOpenBadgeCredentialNodeText(metadata.getAsTree(names[i]), credentialKey);
|
||||
if(formatSearch != null) {
|
||||
vcString = formatSearch;
|
||||
break;
|
||||
@ -69,16 +70,20 @@ public final class PngParser extends PayloadParser {
|
||||
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();
|
||||
@ -88,7 +93,7 @@ public final class PngParser extends PayloadParser {
|
||||
//iterate over all children depth first and search for the credential node.
|
||||
Node child = node.getFirstChild();
|
||||
while (child != null) {
|
||||
String nodeValue = getOpenBadgeCredentialNodeText(child);
|
||||
String nodeValue = getOpenBadgeCredentialNodeText(child, credentialKey);
|
||||
if(nodeValue != null) {
|
||||
return nodeValue;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ 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;
|
||||
@ -32,6 +31,7 @@ public final class SvgParser extends PayloadParser {
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
@ -39,22 +39,22 @@ public final class SvgParser extends PayloadParser {
|
||||
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()) {
|
||||
JsonNode node = fromString(chars.getData(), ctx);
|
||||
return new Credential(resource, node);
|
||||
return getBuilder(ctx).resource(resource).jsonData(node).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,31 @@ public final class SvgParser extends PayloadParser {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
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.resource.UriResourceFactory;
|
||||
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<JsonLdGeneratedObject> {
|
||||
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
|
||||
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 = uriResourceFactory.of(getBadgeClaimId(jsonNode));
|
||||
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 = uriResourceFactory.of(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 = uriResourceFactory.of(revocationListIdNode.asText().strip());
|
||||
JsonLdGeneratedObject revocationListObject = (JsonLdGeneratedObject) ctx.getGeneratedObject(
|
||||
JsonLDCompactionProve.getId(revocationListUriResource));
|
||||
JsonNode revocationListNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER))
|
||||
.readTree(revocationListObject.getJson());
|
||||
|
||||
List<JsonNode> revocationList = JsonNodeUtil.asNodeList(revocationListNode.get("revokedAssertions"));
|
||||
List<JsonNode> 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<JsonNode> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
@ -1,76 +1,50 @@
|
||||
package org.oneedtech.inspect.vc.probe;
|
||||
|
||||
import static org.oneedtech.inspect.vc.Credential.Type.*;
|
||||
|
||||
import static org.oneedtech.inspect.util.code.Defensives.checkNotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.util.JsonNodeUtil;
|
||||
import org.oneedtech.inspect.vc.Credential.CredentialEnum;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* A Probe that verifies a credential's context property.
|
||||
*
|
||||
* @author mgylling
|
||||
*/
|
||||
public class ContextPropertyProbe extends Probe<JsonNode> {
|
||||
private final Credential.Type type;
|
||||
public class ContextPropertyProbe extends StringValuePropertyProbe {
|
||||
private final CredentialEnum type;
|
||||
|
||||
public ContextPropertyProbe(Credential.Type type) {
|
||||
super(ID);
|
||||
public ContextPropertyProbe(CredentialEnum type) {
|
||||
super(ID, type.toString(), "@context");
|
||||
this.type = checkNotNull(type);
|
||||
setValueValidations(this::validate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportItems run(JsonNode root, RunContext ctx) throws Exception {
|
||||
|
||||
ArrayNode contextNode = (ArrayNode) root.get("@context");
|
||||
if (contextNode == null) {
|
||||
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||
return notRun("No @context property", ctx);
|
||||
}
|
||||
|
||||
List<String> expected = values.get(values.keySet()
|
||||
.stream()
|
||||
.filter(s->s.contains(type))
|
||||
.findFirst()
|
||||
.orElseThrow(()-> new IllegalArgumentException(type.name() + " not recognized")));
|
||||
public ReportItems validate(List<String> nodeValues, RunContext ctx) {
|
||||
if (!nodeValues.isEmpty()) { // empty context uri node: inline context
|
||||
List<String> contextUris = type.getContextUris();
|
||||
checkNotNull(contextUris);
|
||||
|
||||
List<String> given = JsonNodeUtil.asStringList(contextNode);
|
||||
int pos = 0;
|
||||
for (String uri : expected) {
|
||||
if ((given.size() < pos + 1) || !given.get(pos).equals(uri)) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
private final static Map<Set<Credential.Type>, List<String>> values = new ImmutableMap.Builder<Set<Credential.Type>, List<String>>()
|
||||
.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();
|
||||
}
|
||||
|
@ -9,6 +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.Credential;
|
||||
import org.oneedtech.inspect.vc.VerifiableCredential;
|
||||
import org.oneedtech.inspect.vc.payload.PayloadParserFactory;
|
||||
|
||||
/**
|
||||
@ -38,7 +39,7 @@ public class CredentialParseProbe extends Probe<Resource> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,8 @@ 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 org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
||||
|
||||
import com.apicatalog.jsonld.StringUtils;
|
||||
import com.apicatalog.jsonld.document.Document;
|
||||
@ -15,9 +16,7 @@ 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;
|
||||
import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier;
|
||||
import jakarta.json.JsonObject;
|
||||
@ -28,7 +27,7 @@ import jakarta.json.JsonStructure;
|
||||
*
|
||||
* @author mgylling
|
||||
*/
|
||||
public class EmbeddedProofProbe extends Probe<Credential> {
|
||||
public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
|
||||
|
||||
public EmbeddedProofProbe() {
|
||||
super(ID);
|
||||
@ -39,15 +38,12 @@ public class EmbeddedProofProbe extends Probe<Credential> {
|
||||
* (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()));
|
||||
ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader();
|
||||
documentLoader.setEnableHttp(true);
|
||||
documentLoader.setEnableHttps(true);
|
||||
vc.setDocumentLoader(documentLoader);
|
||||
com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString()));
|
||||
vc.setDocumentLoader(new CachingDocumentLoader());
|
||||
|
||||
LdProof proof = vc.getLdProof();
|
||||
if (proof == null) {
|
||||
|
@ -25,7 +25,7 @@ public class ExpirationProbe extends Probe<Credential> {
|
||||
* 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());
|
||||
|
@ -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;
|
||||
@ -39,14 +39,14 @@ import com.google.common.base.Splitter;
|
||||
* A Probe that verifies credential external proof (jwt)
|
||||
* @author mlyon
|
||||
*/
|
||||
public class ExternalProofProbe extends Probe<Credential> {
|
||||
public class ExternalProofProbe extends Probe<VerifiableCredential> {
|
||||
|
||||
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) {
|
||||
@ -55,7 +55,7 @@ public class ExternalProofProbe extends Probe<Credential> {
|
||||
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");
|
||||
|
||||
|
@ -11,7 +11,7 @@ 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;
|
||||
|
@ -25,7 +25,7 @@ public class IssuanceProbe extends Probe<Credential> {
|
||||
* 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());
|
||||
|
@ -0,0 +1,42 @@
|
||||
package org.oneedtech.inspect.vc.probe;
|
||||
|
||||
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 com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class PropertyProbe extends Probe<JsonNode> {
|
||||
private final String propertyName;
|
||||
private BiFunction<JsonNode, RunContext, ReportItems> validations;
|
||||
|
||||
public PropertyProbe(String id, String typeName, String propertyName) {
|
||||
super(id, typeName, propertyName);
|
||||
this.propertyName = propertyName;
|
||||
this.validations = this::defaultValidation;
|
||||
}
|
||||
|
||||
public void setValidations(BiFunction<JsonNode, 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(root, ctx);
|
||||
}
|
||||
|
||||
return validations.apply(propertyNode, ctx);
|
||||
}
|
||||
|
||||
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||
return fatal("No " + propertyName + " property", ctx);
|
||||
}
|
||||
|
||||
private ReportItems defaultValidation(JsonNode node, RunContext ctx) {
|
||||
return success(ctx);
|
||||
}
|
||||
}
|
@ -11,6 +11,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.VerifiableCredential;
|
||||
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
@ -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<List<String>, RunContext, ReportItems> valueValidations;
|
||||
|
||||
public StringValuePropertyProbe(String id, String credentialType, String propertyName) {
|
||||
super(id, credentialType, propertyName);
|
||||
this.valueValidations = this::defaultValidation;
|
||||
super.setValidations(this::nodeValidation);
|
||||
}
|
||||
|
||||
public void setValueValidations(BiFunction<List<String>, RunContext, ReportItems> validations) {
|
||||
this.valueValidations = validations;
|
||||
}
|
||||
|
||||
private ReportItems nodeValidation(JsonNode node, RunContext ctx) {
|
||||
List<String> values = JsonNodeUtil.asStringList(node);
|
||||
return valueValidations.apply(values, ctx);
|
||||
}
|
||||
|
||||
private ReportItems defaultValidation(List<String> nodeValues, RunContext ctx) {
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -3,62 +3,61 @@ 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.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.util.JsonNodeUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import org.oneedtech.inspect.vc.Credential.CredentialEnum;
|
||||
|
||||
/**
|
||||
* A Probe that verifies a credential's type property.
|
||||
*
|
||||
* @author mgylling
|
||||
*/
|
||||
public class TypePropertyProbe extends Probe<JsonNode> {
|
||||
private final Credential.Type expected;
|
||||
public class TypePropertyProbe extends StringValuePropertyProbe {
|
||||
private final CredentialEnum expected;
|
||||
|
||||
public TypePropertyProbe(Credential.Type expected) {
|
||||
super(ID);
|
||||
public TypePropertyProbe(CredentialEnum expected) {
|
||||
super(ID, expected.toString(), "type");
|
||||
this.expected = checkNotNull(expected);
|
||||
this.setValueValidations(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<String> values = JsonNodeUtil.asStringList(typeNode);
|
||||
|
||||
if (!values.contains("VerifiableCredential")) {
|
||||
return fatal("The type property does not contain the entry 'VerifiableCredential'", ctx);
|
||||
public ReportItems validate(List<String> values, RunContext ctx) {
|
||||
List<String> requiredTypeValues = expected.getRequiredTypeValues();
|
||||
if (!requiredTypeValues.isEmpty()) {
|
||||
if (!requiredTypeValues.stream().allMatch(requiredValue -> values.contains(requiredValue))) {
|
||||
return fatal(formatMessage(requiredTypeValues), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (expected == Credential.Type.OpenBadgeCredential) {
|
||||
if (!values.contains("OpenBadgeCredential") && !values.contains("AchievementCredential")) {
|
||||
return fatal("The type property does not contain one of 'OpenBadgeCredential' or 'AchievementCredential'", ctx);
|
||||
if (expected.isAllowedTypeValuesRequired()) {
|
||||
List<String> allowedValues = expected.getAllowedTypeValues();
|
||||
if (allowedValues.isEmpty()) {
|
||||
return fatal("The type property is invalid", ctx);
|
||||
}
|
||||
} else if (expected == Credential.Type.ClrCredential) {
|
||||
if (!values.contains("ClrCredential")) {
|
||||
return fatal("The type property does not contain the entry 'ClrCredential'", ctx);
|
||||
if (!values.stream().anyMatch(v -> allowedValues.contains(v))) {
|
||||
return fatal(formatMessage(values), ctx);
|
||||
}
|
||||
} else if (expected == Credential.Type.EndorsementCredential) {
|
||||
if (!values.contains("EndorsementCredential")) {
|
||||
return fatal("The type property does not contain the entry 'EndorsementCredential'", ctx);
|
||||
}
|
||||
} else {
|
||||
// TODO implement
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
private String formatMessage(List<String> 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();
|
||||
}
|
||||
|
@ -0,0 +1,157 @@
|
||||
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.resource.UriResourceFactory;
|
||||
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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);
|
||||
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 = uriResourceFactory.of(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 = 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 = uriResourceFactory.of(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()) {
|
||||
String defaultAllowedOrigins = getDefaultAllowedOrigins(issuerId);
|
||||
if (defaultAllowedOrigins != null) {
|
||||
allowedOrigins = List.of(defaultAllowedOrigins);
|
||||
}
|
||||
} else {
|
||||
allowedOrigins = 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
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.resource.UriResourceFactory;
|
||||
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<JsonLdGeneratedObject> {
|
||||
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());
|
||||
UriResourceFactory uriResourceFactory = (UriResourceFactory) ctx.get(Key.URI_RESOURCE_FACTORY);
|
||||
|
||||
// get badge from assertion
|
||||
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 = uriResourceFactory.of(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 = uriResourceFactory.of(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<String> 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);
|
||||
}
|
||||
|
||||
private static final List<String> allowedTypes = List.of("id", "email", "url", "telephone");
|
||||
public static final String ID = VerificationJWTProbe.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();
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package org.oneedtech.inspect.vc.probe.validation;
|
||||
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Image validation for Open Badges 2.0
|
||||
* Maps to "IMAGE_VALIDATION" task in python implementation
|
||||
* @author xaracil
|
||||
*/
|
||||
public class ValidationImagePropertyProbe extends ValidationPropertyProbe {
|
||||
|
||||
public ValidationImagePropertyProbe(String credentialType, Validation validation) {
|
||||
this(credentialType, validation, true);
|
||||
}
|
||||
|
||||
public ValidationImagePropertyProbe(String credentialType, Validation validation, boolean fullValidate) {
|
||||
super(ID, credentialType, validation, fullValidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||
// Could not load and validate image in node
|
||||
return success(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 = ((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();
|
||||
} catch (Throwable t) {
|
||||
return fatal(t.getMessage(), ctx);
|
||||
}
|
||||
}
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
private static final List<MimeType> allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG);
|
||||
public static final String ID = ValidationImagePropertyProbe.class.getSimpleName();
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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;
|
||||
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(String credentialType, Validation validation) {
|
||||
this(credentialType, validation, true);
|
||||
}
|
||||
|
||||
public ValidationIssuerPropertyProbe(String credentialType, Validation validation, boolean fullValidate) {
|
||||
super(ID, credentialType, validation, fullValidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReportItems validate(JsonNode node, RunContext ctx) {
|
||||
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);
|
||||
}
|
||||
return success(ctx);
|
||||
}
|
||||
|
||||
private ReportItems buildResponse(String msg, RunContext ctx) {
|
||||
if (validation.getMessageLevel() == MessageLevel.Warning) {
|
||||
return warning(msg, ctx);
|
||||
}
|
||||
return error(msg, ctx);
|
||||
}
|
||||
|
||||
public static final String ID = ValidationIssuerPropertyProbe.class.getSimpleName();
|
||||
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
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;
|
||||
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.core.report.ReportUtil;
|
||||
import org.oneedtech.inspect.util.resource.UriResource;
|
||||
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.PropertyProbe;
|
||||
import org.oneedtech.inspect.vc.resource.UriResourceFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public ValidationPropertyProbe(String credentialType, Validation validation) {
|
||||
this(ID, credentialType, validation, true);
|
||||
}
|
||||
|
||||
public ValidationPropertyProbe(String id, String credentialType, Validation validation) {
|
||||
this(id, credentialType, validation, true);
|
||||
}
|
||||
|
||||
public ValidationPropertyProbe(String credentialType, Validation validation, boolean fullValidate) {
|
||||
this(ID, credentialType, validation, fullValidate);
|
||||
}
|
||||
|
||||
public ValidationPropertyProbe(String id, String credentialType, Validation validation, boolean fullValidate) {
|
||||
super(id, credentialType, validation.getName());
|
||||
this.validation = validation;
|
||||
this.fullValidate = fullValidate;
|
||||
setValidations(this::validate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||
if (fullValidate && validation.isRequired()) {
|
||||
return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx);
|
||||
} else {
|
||||
// optional property or not doing full validation
|
||||
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
|
||||
*/
|
||||
protected ReportItems validate(JsonNode node, RunContext ctx) {
|
||||
ReportItems result = new ReportItems();
|
||||
|
||||
// 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<String> values = JsonNodeUtil.asStringList(node);
|
||||
if (values == null ||values.isEmpty()) {
|
||||
return error("Required property " + validation.getName() + " value " + values + " is not acceptable", ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<JsonNode> 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<JsonNode, Boolean> 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 {
|
||||
// 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)));
|
||||
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);
|
||||
}
|
||||
|
||||
// get node from context
|
||||
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)) {
|
||||
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(resolvedNode, ctx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
return fatal(t.getMessage(), ctx);
|
||||
}
|
||||
|
||||
return result.size() > 0 ? result : success(ctx);
|
||||
}
|
||||
|
||||
private ReportItems validatePrerequisites(JsonNode node, RunContext ctx) {
|
||||
List<ReportItems> results = validation.getPrerequisites().stream()
|
||||
.map(v -> ValidationPropertyProbeFactory.of(validation.getName(), 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<ReportItems> results = validation.getExpectedTypes().stream()
|
||||
.flatMap(type -> type.getValidations().stream())
|
||||
.map(v -> ValidationPropertyProbeFactory.of(validation.getName(), 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();
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Factory for ValidationPropertyProbes
|
||||
* @author xaracil
|
||||
*/
|
||||
public class ValidationPropertyProbeFactory {
|
||||
public static ValidationPropertyProbe of(String type, Validation validation) {
|
||||
return of(type, validation, true);
|
||||
}
|
||||
|
||||
public static ValidationPropertyProbe of(String type, Validation validation, boolean fullValidate) {
|
||||
checkNotNull(validation.getType());
|
||||
if (validation.getType() == ValueType.RDF_TYPE) {
|
||||
return new ValidationRdfTypePropertyProbe(type, validation, fullValidate);
|
||||
}
|
||||
if (validation.getType() == ValueType.IMAGE) {
|
||||
return new ValidationImagePropertyProbe(type, validation);
|
||||
}
|
||||
if (validation.getType() == ValueType.ISSUER) {
|
||||
return new ValidationIssuerPropertyProbe(type, validation);
|
||||
}
|
||||
return new ValidationPropertyProbe(type, validation, fullValidate);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.oneedtech.inspect.vc.probe.validation;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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(String credentialType, Validation validation ) {
|
||||
this(credentialType, validation, true);
|
||||
}
|
||||
|
||||
public ValidationRdfTypePropertyProbe(String credentialType, Validation validation, boolean fullValidate) {
|
||||
super(ID, credentialType, 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@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<String> 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)));
|
||||
}
|
||||
|
||||
public static final String ID = ValidationRdfTypePropertyProbe.class.getSimpleName();
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -2,8 +2,12 @@ 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.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -21,17 +25,64 @@ 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 {
|
||||
private Set<URI> contexts;
|
||||
|
||||
public CachingDocumentLoader() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public CachingDocumentLoader(Map<URI, String> localDomains) {
|
||||
super();
|
||||
setEnableHttp(true);
|
||||
setEnableHttps(true);
|
||||
setDefaultHttpLoader(new HttpLoader(localDomains));
|
||||
this.contexts = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError {
|
||||
Document document = super.loadDocument(url, options);
|
||||
if (document == null) {
|
||||
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<URI> getContexts() {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public void resetContexts() {
|
||||
this.contexts.clear();
|
||||
}
|
||||
|
||||
public class HttpLoader implements DocumentLoader {
|
||||
final Map<URI, String> localDomains;
|
||||
|
||||
public HttpLoader(Map<URI, String> localDomains) {
|
||||
this.localDomains = localDomains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError {
|
||||
Tuple<String, DocumentLoaderOptions> tpl = new Tuple<>(url.toASCIIString(), options);
|
||||
try {
|
||||
// resolve url
|
||||
URI resolvedUrl = resolve(url);
|
||||
|
||||
Tuple<String, DocumentLoaderOptions> tpl = new Tuple<>(resolvedUrl.toASCIIString(), options);
|
||||
|
||||
return documentCache.get(tpl);
|
||||
} catch (Exception e) {
|
||||
logger.error("documentCache not able to load {}", url);
|
||||
@ -39,6 +90,22 @@ public class CachingDocumentLoader implements DocumentLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved given url. If the url is from one of local domain, a URL of the relative resource will be returned
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public 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<String, URL> bundled = ImmutableMap.<String, URL>builder()
|
||||
.put("https://purl.imsglobal.org/spec/clr/v2p0/context.json",Resources.getResource("contexts/clr-v2p0.json"))
|
||||
.put("https://purl.imsglobal.org/spec/ob/v3p0/context.json",Resources.getResource("contexts/ob-v3p0.json"))
|
||||
@ -55,6 +122,12 @@ public class CachingDocumentLoader implements DocumentLoader {
|
||||
.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"))
|
||||
.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();
|
||||
|
||||
@ -62,7 +135,8 @@ public class CachingDocumentLoader implements DocumentLoader {
|
||||
.initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24))
|
||||
.build(new CacheLoader<Tuple<String, DocumentLoaderOptions>, Document>() {
|
||||
public Document load(final Tuple<String, DocumentLoaderOptions> id) throws Exception {
|
||||
try (InputStream is = bundled.keySet().contains(id.t1)
|
||||
// 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();) {
|
||||
return JsonDocument.of(is);
|
||||
|
@ -34,6 +34,9 @@ public class JsonNodeUtil {
|
||||
|
||||
public static List<String> asStringList(JsonNode node) {
|
||||
if(!(node instanceof ArrayNode)) {
|
||||
if (node.isObject()) {
|
||||
return List.of();
|
||||
}
|
||||
return List.of(node.asText());
|
||||
} else {
|
||||
ArrayNode arrayNode = (ArrayNode)node;
|
||||
@ -55,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();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,212 @@
|
||||
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.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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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()) && uri.getSchemeSpecificPart().contains(",");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean validateDataUriOrUrl(JsonNode value) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
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) {
|
||||
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 = ObjectMapperCache.get(Config.DEFAULT); // TODO: get from RunContext
|
||||
JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); // TODO: get from RunContext
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
91
inspector-vc/src/main/resources/contexts/ob-v2p0.json
Normal file
91
inspector-vc/src/main/resources/contexts/ob-v2p0.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
91
inspector-vc/src/main/resources/contexts/obv2x.jsonld
Normal file
91
inspector-vc/src/main/resources/contexts/obv2x.jsonld
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
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;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
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 {
|
||||
private static OB20Inspector validator;
|
||||
private static boolean verbose = true;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws URISyntaxException {
|
||||
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)
|
||||
.set(OB20Inspector.Behavior.ALLOW_LOCAL_REDIRECTION, 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);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimplePNGPlainValid() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.PNG.SIMPLE_JSON_PNG.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimpleBadgeClassJsonValid() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JSON.SIMPLE_BADGECLASS.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimpleJWTValid() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JWT.SIMPLE_JWT.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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 testDataImageInBadge() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JSON.BADGE_WITH_DATA_IMAGE_JSON.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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 testEndorsementsInAssertion() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JSON.ASSERTION_WITH_ENDORSEMENTS.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompactIriInIssuer() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JSON.ISSUER_COMPACTIRI_VALIDATION.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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_VALID_EMPTY_CRITERIA_TYPE).forEach(resource -> {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(resource.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
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
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssertionWithLanguage() {
|
||||
assertDoesNotThrow(()->{
|
||||
Report report = validator.run(Samples.OB20.JSON.BASIC_WITH_LANGUAGE_JSON.asFileResource());
|
||||
if(verbose) PrintHelper.print(report, true);
|
||||
assertValid(report);
|
||||
});
|
||||
}
|
||||
|
||||
@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 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(()->{
|
||||
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
|
||||
static void setup() throws URISyntaxException {
|
||||
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)
|
||||
.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);
|
||||
});
|
||||
}
|
||||
|
||||
@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<String> localDomains = List.of("https://www.example.org/", "https://example.org/", "http://example.org/");
|
||||
}
|
@ -40,4 +40,77 @@ public class Samples {
|
||||
public final static Sample SIMPLE_JWT = new Sample("clr20/simple.jwt", true);
|
||||
}
|
||||
}
|
||||
|
||||
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_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
|
||||
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);
|
||||
// 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", 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", 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
|
||||
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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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 {
|
||||
// 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 {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -15,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;
|
||||
|
||||
@ -114,6 +116,10 @@ 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)
|
||||
.put(Key.JWT_CREDENTIAL_NODE_NAME, VerifiableCredential.JWT_NODE_NAME)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
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 {
|
||||
|
||||
@ -22,6 +26,22 @@ public class CachingDocumentLoaderTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLocalDomainCachedDocument() {
|
||||
Assertions.assertDoesNotThrow(()->{
|
||||
Map<URI, String> 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(()->{
|
||||
|
@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> goodValues = List.of("google.com", "nerds.example.com");
|
||||
List<String> 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<String> goodValues = List.of("id", "email", "telephone", "url");
|
||||
List<String> badValues = List.of("sloths");
|
||||
assertFunction(ValueType.COMPACT_IRI, goodValues, badValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasicText() throws JsonMappingException, JsonProcessingException {
|
||||
List<String> goodValues = List.of("string value");
|
||||
List<Integer> badValues = List.of(3, 4);
|
||||
assertFunction(ValueType.TEXT, goodValues, badValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTelephone() throws JsonMappingException, JsonProcessingException {
|
||||
List<String> goodValues = List.of("+64010", "+15417522845", "+18006664358", "+18006662344;ext=666");
|
||||
List<String> 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<String> goodValues = List.of("abc@localhost", "cool+uncool@example.org");
|
||||
List<String> badValues = List.of(" spacey@gmail.com", "steveman [at] gee mail dot com");
|
||||
assertFunction(ValueType.EMAIL, goodValues, badValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBoolean() throws JsonMappingException, JsonProcessingException {
|
||||
List<Boolean> goodValues = List.of(true, false);
|
||||
List<String> badValues = List.of(" spacey@gmail.com", "steveman [at] gee mail dot com");
|
||||
assertFunction(ValueType.BOOLEAN, goodValues, badValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDateTime() throws JsonMappingException, JsonProcessingException {
|
||||
List<String> 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<String> badValues = List.of("notadatetime", "1977-06-10T12:00:00");
|
||||
assertFunction(ValueType.DATETIME, goodValues, badValues);
|
||||
}
|
||||
|
||||
private void assertFunction(ValueType valueType, List<? extends Object> goodValues, List<? extends Object> badValues) throws JsonMappingException, JsonProcessingException {
|
||||
Function<JsonNode, Boolean> 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());
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
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.OB20Inspector;
|
||||
import org.oneedtech.inspect.vc.resource.TestUriResourceFactory;
|
||||
import org.oneedtech.inspect.vc.resource.UriResourceFactory;
|
||||
|
||||
import com.apicatalog.jsonld.loader.DocumentLoader;
|
||||
|
||||
/**
|
||||
* 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<URI, String> 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 DocumentLoader getDocumentLoader() {
|
||||
return new CachingDocumentLoader(localDomains);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UriResourceFactory getUriResourceFactory(DocumentLoader documentLoader) {
|
||||
return new TestUriResourceFactory(documentLoader);
|
||||
}
|
||||
|
||||
public static class TestBuilder extends OB20Inspector.Builder {
|
||||
final Map<URI, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"@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": {
|
||||
"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"
|
||||
},
|
||||
"endorsement": ["https://example.org/endorsement-1.json", "https://example.org/endorsement-2.json"]
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"@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",
|
||||
"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"
|
||||
],
|
||||
"http://schema.org/text": "I'm a property, short and sweet"
|
||||
},
|
||||
"evidence": {
|
||||
"id": "_:b1",
|
||||
"narrative": "Rocked the free world"
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"@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",
|
||||
"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"
|
||||
],
|
||||
"http://schema.org/text": 1337
|
||||
},
|
||||
"evidence": {
|
||||
"id": "_:b1",
|
||||
"narrative": "Rocked the free world"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
10
inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json
Normal file
10
inspector-vc/src/test/resources/ob20/assets/altbadgeurl.json
Normal file
@ -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": "http://example.com/badgecriteria.json",
|
||||
"issuer": "https://example.org/organization.json"
|
||||
}
|
206
inspector-vc/src/test/resources/ob20/assets/bad-issuer.json
Normal file
206
inspector-vc/src/test/resources/ob20/assets/bad-issuer.json
Normal file
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"]
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
208
inspector-vc/src/test/resources/ob20/assets/badgeclass1.json
Normal file
208
inspector-vc/src/test/resources/ob20/assets/badgeclass1.json
Normal file
@ -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.com/badgecriteria.json",
|
||||
"issuer": "http://example.org/issuer1.json",
|
||||
"image": "http://example.org/robotics-badge.png"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"narrative": "Do the important things."
|
||||
}
|
@ -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"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 789 B |
@ -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."
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"@context": "https://w3id.org/openbadges/v2",
|
||||
"id": "http://example.org/empty-revocation-list.json",
|
||||
"type": "RevocationList",
|
||||
"revokedAssertions": []
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"@context": "https://w3id.org/openbadges/v2",
|
||||
"id": "http://example.org/endorsement-1.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"
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"@context": "https://w3id.org/openbadges/v2",
|
||||
"id": "http://example.org/endorsement-2.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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"]
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"]
|
||||
}
|
||||
}
|
@ -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/"
|
||||
}
|
||||
}
|
@ -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", "example.org"]
|
||||
}
|
||||
}
|
206
inspector-vc/src/test/resources/ob20/assets/issuer1.json
Normal file
206
inspector-vc/src/test/resources/ob20/assets/issuer1.json
Normal file
@ -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.json",
|
||||
"type": "Issuer",
|
||||
"name": "Example Issuer",
|
||||
"email": "me@example.org",
|
||||
"url": "http://example.org"
|
||||
}
|
7
inspector-vc/src/test/resources/ob20/assets/key1.json
Normal file
7
inspector-vc/src/test/resources/ob20/assets/key1.json
Normal file
@ -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-----\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-----"
|
||||
}
|
7
inspector-vc/src/test/resources/ob20/assets/key2.json
Normal file
7
inspector-vc/src/test/resources/ob20/assets/key2.json
Normal file
@ -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-----"
|
||||
}
|
7
inspector-vc/src/test/resources/ob20/assets/key3.json
Normal file
7
inspector-vc/src/test/resources/ob20/assets/key3.json
Normal file
@ -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-----"
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"@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",
|
||||
"publicKey": "http://example.org/key1.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"
|
||||
]
|
||||
}
|
@ -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/badgecriteria.json",
|
||||
"issuer": "https://example.org/organization.json"
|
||||
}
|
BIN
inspector-vc/src/test/resources/ob20/assets/robotics-badge.png
Normal file
BIN
inspector-vc/src/test/resources/ob20/assets/robotics-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 789 B |
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
18
inspector-vc/src/test/resources/ob20/basic-assertion.json
Normal file
18
inspector-vc/src/test/resources/ob20/basic-assertion.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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", "http://example.com/CoolClass"],
|
||||
"name": "Chumley",
|
||||
"url": "https://example.org",
|
||||
"email": "contact@example.org"
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
BIN
inspector-vc/src/test/resources/ob20/simple-badge.png
Normal file
BIN
inspector-vc/src/test/resources/ob20/simple-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1 @@
|
||||
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmFkZ2UtZnJvbS1vcmdhbml6YXRpb24td2l0aC1lbXB0eS1yZXZvY2F0aW9uLWxpc3QuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkyLmpzb24ifX0.gLjqNnlBSJ3oWt84iPhVaFjYGR2bhrSLDnTHh5mcjujO_VxYFop5EJlGHV4C7UC6xnM01Yy4y8JxqCIvLLudVRrPtK3pYumWJvGnc43cNyjZq8IY6L1qrsklP_gJyV0P9IKUzh6kQDdSGx_1TwYY3qwKlLXqNuxqkjdcD0z3zlhYfCnEpk7GZAXP_1Np123g-R9i3OZ18cxcUrCo1W7fZGkH4NXayXvdQpGwKmyCiju0N3ZLbHtfcbdWyVLLDF1RJJuRoVRgETly4uqVu8aFJ9O62HuOJggF7eLTos1gg-M2IpCa7yuUDVqGCWIRun5tbuUbJv_0Kg68lCslpZ09aw
|
1
inspector-vc/src/test/resources/ob20/simple-revoked.jwt
Normal file
1
inspector-vc/src/test/resources/ob20/simple-revoked.jwt
Normal file
@ -0,0 +1 @@
|
||||
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UtcmV2b2tlZC5qc29uIiwicmVjaXBpZW50Ijp7InR5cGUiOiJlbWFpbCIsImhhc2hlZCI6dHJ1ZSwic2FsdCI6ImRlYWRzZWEiLCJpZGVudGl0eSI6InNoYTI1NiRlY2Y1NDA5ZjNmNGI5MWFiNjBjYzVlZjRjMDJhZWY3MDMyMzU0Mzc1ZTcwY2Y0ZDhlNDNmNmExZDI5ODkxOTQyIn0sImltYWdlIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9iZXRocy1yb2JvdC1iYWRnZS5wbmciLCJldmlkZW5jZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3Qtd29yay5odG1sIiwiaXNzdWVkT24iOiIyMDE2LTEyLTMxVDIzOjU5OjU5WiIsImJhZGdlIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9iYWRnZS1mcm9tLW9yZ2FuaXphdGlvbi13aXRoLXJldm9jYXRpb24tbGlzdC5qc29uIiwidmVyaWZpY2F0aW9uIjp7InR5cGUiOiJzaWduZWQiLCJjcmVhdG9yIjoiaHR0cDovL2V4YW1wbGUub3JnL2tleTMuanNvbiJ9fQ.RPJ0gudgtSYwjlZLnMmPHuL_SZmlW46cD3GXE8JQgOk02RwofMUt1w14ey8x7nWexXhvoBIA63qT4S0MfVuMeznAzNMO9_Upm0fworkR500257402NoXTnyzvF8Z8j_bCfoDZrdvljHFCieE1txmhDvRLvR5_y8KaZ6XC3fggokqMUQj6Is6LO4rnazqQixZWT8h_mPbE18_lUi0ocvHdrkwVWIeQi8_B-vgsUOANoBkX9ALBwHnqgfmfOwd-zGV39rBY4lj2nAxoHmY4VClqRj3Qqzoml7bd2eIhFtkJUmxfFuKdcfVhknWqw72MDGb6T-iCKYjuBNhYdxw6i1yQg
|
1
inspector-vc/src/test/resources/ob20/simple.jwt
Normal file
1
inspector-vc/src/test/resources/ob20/simple.jwt
Normal file
@ -0,0 +1 @@
|
||||
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvb3BlbmJhZGdlcy92MiIsInR5cGUiOiJBc3NlcnRpb24iLCJpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3RpY3MtYmFkZ2UuanNvbiIsInJlY2lwaWVudCI6eyJ0eXBlIjoiZW1haWwiLCJoYXNoZWQiOnRydWUsInNhbHQiOiJkZWFkc2VhIiwiaWRlbnRpdHkiOiJzaGEyNTYkZWNmNTQwOWYzZjRiOTFhYjYwY2M1ZWY0YzAyYWVmNzAzMjM1NDM3NWU3MGNmNGQ4ZTQzZjZhMWQyOTg5MTk0MiJ9LCJpbWFnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYmV0aHMtcm9ib3QtYmFkZ2UucG5nIiwiZXZpZGVuY2UiOiJodHRwczovL2V4YW1wbGUub3JnL2JldGhzLXJvYm90LXdvcmsuaHRtbCIsImlzc3VlZE9uIjoiMjAxNi0xMi0zMVQyMzo1OTo1OVoiLCJiYWRnZSI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvcm9ib3RpY3MtYmFkZ2UuanNvbiIsInZlcmlmaWNhdGlvbiI6eyJ0eXBlIjoic2lnbmVkIiwiY3JlYXRvciI6Imh0dHA6Ly9leGFtcGxlLm9yZy9rZXkxLmpzb24ifX0.bR5tt-GEneb7tdWfmDdCq2FCBRuvSnKxNIfMPVz4lXiLcC_MobAB1zC9VQTJ5wJMzyS7Z7SR6eqt5WjuV4gl-6cT1qm3Af4VaKMEpi5bc3w2EGMlw10YxYAIrFEmdAXod9jwA2hPd9k4ypuIIIyzThRAhxVY36vf6nZWkC11MNj0QJoAJ-q2JlaERaGnwSOf1h63jUIyWtsfs6ocXb_hiFIaxPKAqnJSnWeF672vo3lOG_0vM6Tk4Ug73DRzJOsJhGwSWWze_GmwgqtUcWS2b8HmpoEaS_1gXwqnhNxiNNN0a39P-MqP86cY6pb64WKcIVY0ZSnvb4mwzXzZ3cPOTg
|
@ -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"
|
||||
}
|
||||
}
|
@ -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": "http://example.org/altbadgeurl.json",
|
||||
"verification": {
|
||||
"type": "hosted"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user