Merge pull request #69 from imsglc/feature/upload_uri_in_api

OB 3.0 / CLR 2.0 cert suite
This commit is contained in:
Xavi Aracil 2023-03-03 05:00:09 -06:00 committed by GitHub
commit 5f9335155c
31 changed files with 1392 additions and 93 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.1edtech</groupId>
<artifactId>inspector</artifactId>
<version>0.9.10</version>
<version>0.9.11</version>
</parent>
<artifactId>inspector-vc</artifactId>
<dependencies>

View File

@ -148,6 +148,14 @@ public class Assertion extends Credential {
public List<Validation> getValidations() {
return validationMap.get(this);
}
@Override
public Map<String, List<String>> getContextAliases() {
return Collections.emptyMap();
}
@Override
public Map<String, List<String>> getContextVersionPatterns() {
return Collections.emptyMap();
}
}
public enum ValueType {

View File

@ -92,6 +92,8 @@ public abstract class Credential extends GeneratedObject {
List<String> getAllowedTypeValues();
boolean isAllowedTypeValuesRequired();
List<String> getContextUris();
Map<String, List<String>> getContextAliases();
Map<String, List<String>> getContextVersionPatterns();
String toString();
}

View File

@ -188,8 +188,8 @@ public class EndorsementInspector extends VCInspector implements SubInspector {
//credentialSubject
probeCount++;
accumulator.add(new CredentialSubjectProbe().run(endorsement.getJson(), ctx));
accumulator.add(new CredentialSubjectProbe("EndorsementSubject").run(endorsement.getJson(), ctx));
//signatures, proofs
probeCount++;
if(endorsement.getProofType() == EXTERNAL){

View File

@ -6,6 +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.VerifiableCredential.REFRESH_SERVICE_MIME_TYPES;
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;
@ -43,7 +44,9 @@ import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe;
import org.oneedtech.inspect.vc.probe.ExpirationProbe;
import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
import org.oneedtech.inspect.vc.probe.IssuerProbe;
import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe;
import org.oneedtech.inspect.vc.probe.EvidenceProbe;
import org.oneedtech.inspect.vc.probe.RevocationListProbe;
import org.oneedtech.inspect.vc.probe.ExternalProofProbe;
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
@ -170,7 +173,17 @@ public class OB30Inspector extends VCInspector implements SubInspector {
//credentialSubject
probeCount++;
accumulator.add(new CredentialSubjectProbe().run(ob.getJson(), ctx));
accumulator.add(new CredentialSubjectProbe("AchievementSubject", true).run(ob.getJson(), ctx));
// evidence
probeCount++;
accumulator.add(new EvidenceProbe().run(ob.getJson(), ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
// issuer
probeCount++;
accumulator.add(new IssuerProbe().run(ob.getJson(), ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
//signatures, proofs
probeCount++;
@ -188,9 +201,11 @@ public class OB30Inspector extends VCInspector implements SubInspector {
if(resource.getContext().get(REFRESHED) != TRUE) {
Optional<String> newID = checkRefreshService(ob, ctx);
if(newID.isPresent()) {
return this.run(
new UriResource(new URI(newID.get()))
.setContext(new ResourceContext(REFRESHED, TRUE)));
// If the refresh is not successful, continue the verification process using the original OpenBadgeCredential.
UriResource uriResource = new UriResource(new URI(newID.get()), null, REFRESH_SERVICE_MIME_TYPES);
if (uriResource.exists()) {
return this.run(uriResource.setContext(new ResourceContext(REFRESHED, TRUE)));
}
}
}

View File

@ -9,10 +9,10 @@ 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.MimeType;
import org.oneedtech.inspect.util.resource.Resource;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
@ -53,17 +53,24 @@ public class VerifiableCredential extends Credential {
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();
private static final Map<String, List<String>> contextAliasesMap = new ImmutableMap.Builder<String, List<String>>()
.put("https://purl.imsglobal.org/spec/ob/v3p0/context.json",
List.of("https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld"))
.build();
private static final Map<String, List<String>> contextVersioningPatternMap = new ImmutableMap.Builder<String, List<String>>()
.put("https://purl.imsglobal.org/spec/ob/v3p0/context.json",
List.of("https:\\/\\/purl\\.imsglobal\\.org\\/spec\\/ob\\/v3p0\\/context(-\\d+\\.\\d+\\.\\d+)*\\.json"))
.build();
public enum Type implements CredentialEnum {
AchievementCredential(Collections.emptyList()),
OpenBadgeCredential(List.of("OpenBadgeCredential", "AchievementCredential")), //treated as an alias of AchievementCredential
@ -119,6 +126,14 @@ public class VerifiableCredential extends Credential {
.findFirst()
.orElseThrow(()-> new IllegalArgumentException(this.name() + " not recognized")));
}
@Override
public Map<String, List<String>> getContextAliases() {
return contextAliasesMap;
}
@Override
public Map<String, List<String>> getContextVersionPatterns() {
return contextVersioningPatternMap;
}
}
public enum ProofType {
@ -145,4 +160,5 @@ public class VerifiableCredential extends Credential {
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";
public static final List<MimeType> REFRESH_SERVICE_MIME_TYPES = List.of(MimeType.JSON, MimeType.JSON_LD, MimeType.TEXT_PLAIN);
}

View File

@ -0,0 +1,39 @@
package org.oneedtech.inspect.vc;
import java.util.List;
import org.oneedtech.inspect.vc.jsonld.JsonLDObjectUtils;
import com.danubetech.verifiablecredentials.VerifiableCredential;
import foundation.identity.jsonld.ConfigurableDocumentLoader;
import info.weboftrust.ldsignatures.LdProof;
/**
* Holder for W3C's Verifiable Credential
*/
public class W3CVCHolder {
private VerifiableCredential credential;
public W3CVCHolder(VerifiableCredential credential) {
this.credential = credential;
ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader();
documentLoader.setEnableHttp(true);
documentLoader.setEnableHttps(true);
credential.setDocumentLoader(documentLoader);
}
/**
* Get the list of proofs in the credential.
* {@link VerifiableCredential} contains the method getLdProof(), but only works with one proof. This methods
* returns a list of all proofs defined in the credential.
* @return proofs defined in the credential
*/
public List<LdProof> getProofs() {
return JsonLDObjectUtils.getListFromJsonLDObject(LdProof.class, credential);
}
public VerifiableCredential getCredential() {
return credential;
}
}

View File

@ -0,0 +1,45 @@
package org.oneedtech.inspect.vc.jsonld;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import foundation.identity.jsonld.JsonLDObject;
public class JsonLDObjectUtils {
@SuppressWarnings("unchecked")
public static <C extends JsonLDObject> List<C> getListFromJsonLDObject(Class<C> cl, JsonLDObject jsonLdObject) {
String term = JsonLDObject.getDefaultJsonLDPredicate(cl);
List<Map<String, Object>> jsonObjects = jsonLdGetJsonObjectList(jsonLdObject.getJsonObject(), term);
if (jsonObjects == null) return null;
try {
Method method = cl.getMethod("fromMap", Map.class);
return jsonObjects.stream().map(jsonObject -> {
try {
return (C) method.invoke(null, jsonObject);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new Error(e);
}
}).collect(Collectors.toList());
} catch (NoSuchMethodException | SecurityException e) {
throw new Error(e);
}
}
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> jsonLdGetJsonObjectList(Map<String, Object> jsonObject, String term) {
Object entry = jsonObject.get(term);
if (entry == null) return null;
if (entry instanceof Map<?, ?>) {
return Collections.singletonList((Map<String, Object>) entry);
} else if (entry instanceof List<?> && ((List<Object>) entry).stream().allMatch(e -> e instanceof Map<?, ?>)) {
return (List<Map<String, Object>>) (List<Map<String,Object>>) entry;
} else {
throw new IllegalArgumentException("Cannot get json object '" + term + "' from " + jsonObject);
}
}
}

View File

@ -36,7 +36,7 @@ public class ContextPropertyProbe extends StringValuePropertyProbe {
int pos = 0;
for (String uri : contextUris) {
if ((nodeValues.size() < pos + 1) || !nodeValues.get(pos).equals(uri)) {
if ((nodeValues.size() < pos + 1) || !contains(uri, nodeValues.get(pos))) {
return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx);
}
pos++;
@ -46,5 +46,25 @@ public class ContextPropertyProbe extends StringValuePropertyProbe {
return success(ctx);
}
private boolean contains(String uri, String nodeValue) {
// check equal case
if (nodeValue.equals(uri)) {
return true;
}
// check aliases
if (type.getContextAliases().containsKey(uri)) {
if (type.getContextAliases().get(uri).stream().anyMatch(alias -> nodeValue.equals(alias))) {
return true;
}
}
// check versioning
if (type.getContextVersionPatterns().containsKey(uri)) {
if (type.getContextVersionPatterns().get(uri).stream().anyMatch(version -> nodeValue.matches(version))) {
return true;
}
}
return false;
}
public static final String ID = ContextPropertyProbe.class.getSimpleName();
}

View File

@ -1,21 +1,36 @@
package org.oneedtech.inspect.vc.probe;
import java.util.List;
import org.oneedtech.inspect.core.probe.Probe;
import org.oneedtech.inspect.core.probe.RunContext;
import org.oneedtech.inspect.core.report.ReportItems;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
/**
* A Probe that checks credential subject specifics not capturable by schemata.
*
*
* @author mgylling
*/
public class CredentialSubjectProbe extends Probe<JsonNode> {
public CredentialSubjectProbe() {
/**
* Required type to be present.
*/
private final String requiredType;
private boolean achivementRequired;
public CredentialSubjectProbe(String requiredType) {
this(requiredType, false);
}
public CredentialSubjectProbe(String requiredType, boolean achivementRequired) {
super(ID);
this.requiredType = requiredType;
this.achivementRequired = achivementRequired;
}
@Override
@ -24,18 +39,86 @@ public class CredentialSubjectProbe extends Probe<JsonNode> {
JsonNode subject = root.get("credentialSubject");
if(subject == null) return notRun("no credentialSubject node found", ctx); //error reported by schema
/*
* Check that we have either .id or .identifier populated
/**
* Check that type contains AchievementSubject
*/
if (!JsonNodeUtil.asStringList(subject.get("type")).contains(requiredType)) {
return error("credentialSubject is not of type \"" + requiredType + "\"", ctx);
}
/*
* Check that we have either .id or .identifier populated
*/
if (idAndIdentifierEmpty(subject)) {
return error("no id in credentialSubject", ctx);
}
/**
* if .identifier is provider, check its type
*/
if (subject.hasNonNull("identifier")) {
List<JsonNode> identifiers = JsonNodeUtil.asNodeList(subject.get("identifier"));
for (JsonNode identifier : identifiers) {
// check that type contains "IdentityObject"
if (!JsonNodeUtil.asStringList(identifier.get("type")).contains("IdentityObject")) {
return error("identifier in credentialSubject is not of type \"IdentityObject\"", ctx);
}
}
}
/*
* Check results
*/
if (subject.hasNonNull("result")) {
List<JsonNode> results = JsonNodeUtil.asNodeList(subject.get("result"));
for (JsonNode result : results) {
// check that type contains "Result"
if (!JsonNodeUtil.asStringList(result.get("type")).contains("Result")) {
return error("result in credentialSubject is not of type \"Result\"", ctx);
}
}
}
/*
* Check achievement result description
*/
if (subject.hasNonNull("achievement")) {
JsonNode achievement = subject.get("achievement");
if (achievement.hasNonNull("resultDescription")) {
List<JsonNode> resultDescriptions = JsonNodeUtil.asNodeList(achievement.get("resultDescription"));
for (JsonNode resultDescription : resultDescriptions) {
// check that type contains "ResultDescription"
if (!JsonNodeUtil.asStringList(resultDescription.get("type")).contains("ResultDescription")) {
return error("resultDescription in achievement of credentialSubject is not of type \"ResultDescription\"", ctx);
}
}
}
} else if (achivementRequired) {
return error("missing required achievement in credentialSubject", ctx);
}
/**
* Check that source type contains "Profile"
*/
if (subject.hasNonNull("source")) {
JsonNode source = subject.get("source");
// check that type contains "Profile"
if (!JsonNodeUtil.asStringList(source.get("type")).contains("Profile")) {
return error("source in credentialSubject is not of type \"Profile\"", ctx);
}
}
return success(ctx);
}
private boolean idAndIdentifierEmpty(JsonNode root) {
JsonNode id = root.get("id");
if (id != null && id.textValue().strip().length() > 0) return success(ctx);
JsonNode identifier = root.get("identifier");
if(identifier != null && identifier instanceof ArrayNode
&& ((ArrayNode)identifier).size() > 0) return success(ctx);
return error("no id in credentialSubject", ctx);
if (id != null && id.textValue().strip().length() > 0) return false;
JsonNode identifier = root.get("identifier");
if(identifier != null && identifier instanceof ArrayNode
&& ((ArrayNode)identifier).size() > 0) return false;
return true;
}
public static final String ID = CredentialSubjectProbe.class.getSimpleName();

View File

@ -2,13 +2,14 @@ package org.oneedtech.inspect.vc.probe;
import java.io.StringReader;
import java.net.URI;
import java.util.List;
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.VerifiableCredential;
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
import org.oneedtech.inspect.vc.W3CVCHolder;
import com.apicatalog.jsonld.StringUtils;
import com.apicatalog.jsonld.document.Document;
@ -17,7 +18,6 @@ import com.apicatalog.multibase.Multibase;
import com.apicatalog.multicodec.Multicodec;
import com.apicatalog.multicodec.Multicodec.Codec;
import foundation.identity.jsonld.ConfigurableDocumentLoader;
import info.weboftrust.ldsignatures.LdProof;
import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier;
import jakarta.json.JsonObject;
@ -41,18 +41,23 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
@Override
public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception {
// TODO: What there are multiple proofs?
W3CVCHolder credentiaHolder = new W3CVCHolder(com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())));
com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString()));
ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader();
documentLoader.setEnableHttp(true);
documentLoader.setEnableHttps(true);
vc.setDocumentLoader(documentLoader);
LdProof proof = vc.getLdProof();
if (proof == null) {
List<LdProof> proofs = credentiaHolder.getProofs();
if (proofs == null || proofs.size() == 0) {
return error("The verifiable credential is missing a proof.", ctx);
}
// get proof of standard type and purpose
Optional<LdProof> selectedProof = proofs.stream().filter(proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod"))
.findFirst();
if (!selectedProof.isPresent()) {
return error("No proof with type \"Ed25519Signature2020\" or proof purpose \"assertionMethod\" found", ctx);
}
LdProof proof = selectedProof.get();
if (!proof.isType("Ed25519Signature2020")) {
return error("Unknown proof type: " + proof.getType(), ctx);
}
@ -92,7 +97,7 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
}
} else if (method.getScheme().equals("http") || method.getScheme().equals("https")) {
try {
Document keyDocument = vc.getDocumentLoader().loadDocument(method, new DocumentLoaderOptions());
Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(method, new DocumentLoaderOptions());
Optional<JsonStructure> keyStructure = keyDocument.getJsonContent();
if (keyStructure.isEmpty()) {
return error("Key document not found at " + method, ctx);
@ -135,8 +140,8 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
}
if (controller != null) {
if (!controller.equals(vc.getIssuer().toString())) {
return error("Key controller does not match issuer: " + vc.getIssuer(), ctx);
if (!controller.equals(credentiaHolder.getCredential().getIssuer().toString())) {
return error("Key controller does not match issuer: " + credentiaHolder.getCredential().getIssuer(), ctx);
}
}
@ -146,7 +151,7 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey);
try {
boolean verify = verifier.verify(vc);
boolean verify = verifier.verify(credentiaHolder.getCredential(), proof);
if (!verify) {
return error("Embedded proof verification failed.", ctx);
}

View File

@ -0,0 +1,38 @@
package org.oneedtech.inspect.vc.probe;
import java.util.List;
import org.oneedtech.inspect.core.probe.Probe;
import org.oneedtech.inspect.core.probe.RunContext;
import org.oneedtech.inspect.core.report.ReportItems;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
public class EvidenceProbe extends Probe<JsonNode> {
public EvidenceProbe() {
super(ID);
}
@Override
public ReportItems run(JsonNode root, RunContext ctx) throws Exception {
if (root.hasNonNull("evidence")) {
/*
* evidence is an array, so check type of each element
*/
List<JsonNode> evidences = JsonNodeUtil.asNodeList(root.get("evidence"));
for (JsonNode evidence : evidences) {
// check that type contains "Evidence"
if (!JsonNodeUtil.asStringList(evidence.get("type")).contains("Evidence")) {
return error("evidence is not of type \"Evidence\"", ctx);
}
}
}
return success(ctx);
}
public static final String ID = EvidenceProbe.class.getSimpleName();
}

View File

@ -50,7 +50,7 @@ public class ExternalProofProbe extends Probe<VerifiableCredential> {
try {
verifySignature(crd, ctx);
} catch (Exception e) {
return fatal("Error verifying jwt signature: " + e.getMessage(), ctx);
return fatal("Error verifying jwt signature: " + e.getMessage() + (e.getCause() != null ? ". Reason: " + e.getCause().getMessage() : ""), ctx);
}
return success(ctx);
}
@ -75,7 +75,9 @@ public class ExternalProofProbe extends Probe<VerifiableCredential> {
JsonNode alg = headerObj.get("alg");
if(alg == null || !alg.textValue().equals("RS256")) { throw new Exception("alg must be present and must be 'RS256'"); }
//TODO: decoded jwt will check timestamps, but shall we explicitly break these out?
// decoded jwt will check timestamps, but shall we explicitly break these out?
// JWT verifier throws and exception with the cause when claims are invalid. Adding that cause
// to the probe result can avoid having to explicitly check the claims.
//Option 1, fetch directly from header
JsonNode jwk = headerObj.get("jwk");

View File

@ -12,6 +12,7 @@ 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.VerifiableCredential;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
@ -42,7 +43,7 @@ public class InlineJsonSchemaProbe extends Probe<JsonNode> {
JsonNode credentialSchemaNode = root.get("credentialSchema");
if(credentialSchemaNode == null) return success(ctx);
ArrayNode schemas = (ArrayNode) credentialSchemaNode; //TODO guard this cast
List<JsonNode> schemas = JsonNodeUtil.asNodeList(credentialSchemaNode);
for(JsonNode schemaNode : schemas) {
JsonNode typeNode = schemaNode.get("type");

View File

@ -0,0 +1,66 @@
package org.oneedtech.inspect.vc.probe;
import java.net.URI;
import java.util.List;
import org.oneedtech.inspect.core.probe.Probe;
import org.oneedtech.inspect.core.probe.RunContext;
import org.oneedtech.inspect.core.report.ReportItems;
import org.oneedtech.inspect.util.resource.UriResource;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
public class IssuerProbe extends Probe<JsonNode> {
public IssuerProbe() {
super(ID);
}
@Override
public ReportItems run(JsonNode root, RunContext ctx) throws Exception {
JsonNode issuer = root.get("issuer");
if(issuer == null) return error("no issuer node found", ctx);
// check that type contains "Profile"
if (!JsonNodeUtil.asStringList(issuer.get("type")).contains("Profile")) {
return error("issuer is not of type \"Profile\"", ctx);
}
// check url is accessible
if (issuer.hasNonNull("url")) {
try {
UriResource urlResource = new UriResource(new URI(issuer.get("url").asText().strip()));
if (!urlResource.exists()) {
return warning("url \"" + issuer.get("url").asText().strip() + "\" in issuer is not accessible", ctx);
}
} catch (Exception e) {
return warning("url \"" + issuer.get("url").asText().strip() + "\" in issuer is not accessible", ctx);
}
}
// check other identifier
if (issuer.hasNonNull("otherIdentifier")) {
List<JsonNode> otherIdentifiers = JsonNodeUtil.asNodeList(issuer.get("otherIdentifier"));
for (JsonNode otherIdentifier : otherIdentifiers) {
// check that type contains "IdentityObject"
if (!JsonNodeUtil.asStringList(otherIdentifier.get("type")).contains("IdentityObject")) {
return error("otherIdentifier in issuer is not of type \"IdentityObject\"", ctx);
}
}
}
// check parent issuer
if (issuer.hasNonNull("parentOrg")) {
JsonNode parentOrg = issuer.get("parentOrg");
// check that type contains "Profile"
if (!JsonNodeUtil.asStringList(parentOrg.get("type")).contains("Profile")) {
return error("parentOrg in issuer is not of type \"Profile\"", ctx);
}
}
return success(ctx);
}
public static final String ID = IssuerProbe.class.getSimpleName();
}

View File

@ -3,6 +3,7 @@ package org.oneedtech.inspect.vc.probe;
import static org.oneedtech.inspect.core.probe.RunContext.Key.JACKSON_OBJECTMAPPER;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.List;
@ -10,6 +11,7 @@ import java.util.List;
import org.oneedtech.inspect.core.probe.Probe;
import org.oneedtech.inspect.core.probe.RunContext;
import org.oneedtech.inspect.core.report.ReportItems;
import org.oneedtech.inspect.util.resource.MimeType;
import org.oneedtech.inspect.vc.Credential;
import org.oneedtech.inspect.vc.VerifiableCredential;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
@ -45,7 +47,9 @@ public class RevocationListProbe extends Probe<Credential> {
if(listID != null) {
try {
URL url = new URI(listID.asText().strip()).toURL();
try (InputStream is = url.openStream()) {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Accept", MimeType.JSON.toString());
try (InputStream is = connection.getInputStream()) {
JsonNode revocList = ((ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER)).readTree(is.readAllBytes());
/* To check if a credential has been revoked, the verifier issues a GET request

View File

@ -108,6 +108,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader {
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/ob_v3p0.jsonld",Resources.getResource("contexts/ob-v3p0.json"))
.put("https://purl.imsglobal.org/spec/ob/v3p0/context.json",Resources.getResource("contexts/ob-v3p0.json"))
.put("https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",Resources.getResource("contexts/ob-v3p0-extensions.json"))
.put("https://www.w3.org/ns/did/v1", Resources.getResource("contexts/did-v1.jsonld"))

View File

@ -11,60 +11,72 @@ import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe;
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.CredentialSubjectProbe;
import org.oneedtech.inspect.vc.probe.ExpirationProbe;
import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
import org.oneedtech.inspect.vc.probe.IssuerProbe;
import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe;
import org.oneedtech.inspect.vc.probe.EvidenceProbe;
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
import com.google.common.collect.Iterables;
public class OB30Tests {
private static OB30Inspector validator;
private static OB30Inspector validator;
private static boolean verbose = true;
@BeforeAll
static void setup() {
validator = new OB30Inspector.Builder()
.set(Behavior.TEST_INCLUDE_SUCCESS, true)
@BeforeAll
static void setup() {
validator = new OB30Inspector.Builder()
.set(Behavior.TEST_INCLUDE_SUCCESS, true)
.set(Behavior.VALIDATOR_FAIL_FAST, true)
.build();
.build();
}
@Test
void testSimpleJsonValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
void testSimpleDidMethodJsonValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_METHOD_JSON.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
void testSimpleMultipleProofsJsonValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_MULTIPLE_PROOF_JSON.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
}
@Test
void testSimplePNGPlainValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
void testSimplePNGJWTValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
@ -72,17 +84,17 @@ public class OB30Tests {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
void testSimpleJsonSVGJWTValid() {
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
assertValid(report);
});
}
@Test
@ -94,9 +106,9 @@ public class OB30Tests {
assertInvalid(report);
assertFatalCount(report, 1);
assertHasProbeID(report, TypePropertyProbe.ID, true);
});
});
}
@Test
void testSimpleJsonInvalidProofMethod() {
// add some garbage chars to the verification method fragment
@ -107,9 +119,9 @@ public class OB30Tests {
assertInvalid(report);
assertErrorCount(report, 1);
assertHasProbeID(report, EmbeddedProofProbe.ID, true);
});
});
}
@Test
void testSimpleJsonInvalidProofMethodNoScheme() {
// The verificationMethod is not a URI (no scheme)
@ -119,9 +131,9 @@ public class OB30Tests {
assertInvalid(report);
assertErrorCount(report, 1);
assertHasProbeID(report, EmbeddedProofProbe.ID, true);
});
});
}
@Test
void testSimpleJsonInvalidProofMethodUnknownScheme() {
// The verificationMethod is not a URI (no scheme)
@ -131,9 +143,9 @@ public class OB30Tests {
assertInvalid(report);
assertErrorCount(report, 1);
assertHasProbeID(report, EmbeddedProofProbe.ID, true);
});
});
}
@Test
void testSimpleJsonInvalidProofMethodUnknownDidMethod() {
// The verificationMethod is an unknown DID Method
@ -143,9 +155,9 @@ public class OB30Tests {
assertInvalid(report);
assertErrorCount(report, 1);
assertHasProbeID(report, EmbeddedProofProbe.ID, true);
});
});
}
@Test
void testSimpleJsonInvalidProofValue() {
//add some garbage chars to proofValue
@ -155,9 +167,9 @@ public class OB30Tests {
assertInvalid(report);
assertErrorCount(report, 1);
assertHasProbeID(report, EmbeddedProofProbe.ID, true);
});
});
}
@Test
void testSimpleJsonExpired() {
//"expirationDate": "2020-01-20T00:00:00Z",
@ -166,20 +178,40 @@ public class OB30Tests {
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
assertHasProbeID(report, ExpirationProbe.ID, true);
});
});
}
@Test
void testSimpleJsonContextError() {
void testSimpleJsonContextError() {
//removed one of the reqd context uris
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ERR_CONTEXT.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
assertHasProbeID(report, ContextPropertyProbe.ID, true);
});
});
}
@Test
void testSimpleJsonContextAlias() {
//removed one of the reqd context uris
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ALIAS_CONTEXT.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertValid(report);
});
}
@Test
void testSimpleJsonContextVersion() {
//removed one of the reqd context uris
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_VERSION_CONTEXT.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertHasValidProbeID(report, ContextPropertyProbe.ID);
});
}
@Test
void testSimpleJsonSchemaError() throws Exception {
//issuer removed
@ -187,10 +219,118 @@ public class OB30Tests {
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ISSUER.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
assertHasProbeID(report, JsonSchemaProbe.ID, true);
});
assertHasProbeID(report, JsonSchemaProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidCredentialSubjectType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, CredentialSubjectProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidCredentialSubjectIdentifierType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_IDENTIFIER_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, CredentialSubjectProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidCredentialSubjectResultType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_RESULT_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, CredentialSubjectProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidCredentialSubjectAchievementResultDescriptionType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, CredentialSubjectProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidCredentialSubjectProfileType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, CredentialSubjectProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidEvidenceType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_EVIDENCE_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, EvidenceProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidIssuerType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, IssuerProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidIssuerParentOrgType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_PARENTORG_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, IssuerProbe.ID, true);
});
}
@Test
void testSimpleJsonInvalidIssuerOtherIdentifierType() {
//add a dumb value to .type and remove the ob type
assertDoesNotThrow(()->{
Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_OTHERIDENTIFIER_TYPE.asFileResource());
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
// assertFatalCount(report, 1);
assertHasProbeID(report, IssuerProbe.ID, true);
});
}
@Disabled //TODO IssuanceVerifierProbe is not run because FATAL: InvalidSignature terminates
@Test
void testSimpleJsonNotIssued() {
@ -201,9 +341,9 @@ public class OB30Tests {
if(verbose) PrintHelper.print(report, true);
assertInvalid(report);
assertHasProbeID(report, IssuanceProbe.ID, true);
});
});
}
@Test
void testCompleteJsonInvalidInlineSchemaRef() throws Exception {
//404 inline schema ref, and 404 refresh uri
@ -212,9 +352,9 @@ public class OB30Tests {
if(verbose) PrintHelper.print(report, true);
assertFalse(report.asBoolean());
assertTrue(Iterables.size(report.getErrors()) > 0);
assertTrue(Iterables.size(report.getExceptions()) > 0);
assertHasProbeID(report, InlineJsonSchemaProbe.ID, true);
});
assertTrue(Iterables.size(report.getExceptions()) > 0);
assertHasProbeID(report, InlineJsonSchemaProbe.ID, true);
});
}
}

View File

@ -12,8 +12,18 @@ public class Samples {
public final static Sample COMPLETE_JSON = new Sample("ob30/complete.json", false);
public final static Sample SIMPLE_JSON = new Sample("ob30/simple.json", true);
public final static Sample SIMPLE_DID_METHOD_JSON = new Sample("ob30/simple-did-method.json", true);
public final static Sample SIMPLE_MULTIPLE_PROOF_JSON = new Sample("ob30/simple-multiple-proofs.json", true);
public final static Sample SIMPLE_JSON_NOPROOF = new Sample("ob30/simple-noproof.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_TYPE = new Sample("ob30/simple-err-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_TYPE = new Sample("ob30/simple-err-credential-subject-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_IDENTIFIER_TYPE = new Sample("ob30/simple-err-credential-subject-identifier-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_RESULT_TYPE = new Sample("ob30/simple-err-credential-subject-result-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE = new Sample("ob30/simple-err-credential-subject-achievement-result-description-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE = new Sample("ob30/simple-err-credential-subject-profile-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_EVIDENCE_TYPE = new Sample("ob30/simple-err-evidence-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_TYPE = new Sample("ob30/simple-err-issuer-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_OTHERIDENTIFIER_TYPE = new Sample("ob30/simple-err-issuer-otheridentifier-type.json", false);
public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_PARENTORG_TYPE = new Sample("ob30/simple-err-issuer-parentorg-type.json", false);
public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false);
public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false);
public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false);
@ -25,6 +35,8 @@ public class Samples {
public final static Sample SIMPLE_JSON_ERR_CONTEXT = new Sample("ob30/simple-err-context.json", false);
public final static Sample ENDORSEMENT_ERR_SCHEMA_STATUS_REFRESH = new Sample("ob30/endorsement-err-schema-status-refresh.json", false);
public final static Sample ENDORSEMENT_VALID = new Sample("ob30/endorsement-valid.json", false);
public final static Sample SIMPLE_JSON_ALIAS_CONTEXT = new Sample("ob30/simple-context-alias.json", true);
public final static Sample SIMPLE_JSON_VERSION_CONTEXT = new Sample("ob30/simple-context-version.json", true);
}
public static final class PNG {
public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true);

View File

@ -0,0 +1,47 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,47 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,188 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork",
"resultDescription": [
{
"id": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c",
"type": "ResultDescription",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project"
}
],
"allowedValue": [
"D",
"C",
"B",
"A"
],
"name": "Final Project Grade",
"requiredValue": "C",
"resultType": "LetterGrade"
},
{
"id": "urn:uuid:a70ddc6a-4c4a-4bd8-8277-cb97c79f40c5",
"type": "ResultDescription",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project"
}
],
"allowedValue": [
"D",
"C",
"B",
"A"
],
"name": "Final Project Grade",
"requiredLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a",
"resultType": "RubricCriterionLevel",
"rubricCriterionLevel": [
{
"id": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a",
"type": "RubricCriterionLevel",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFRubricCriterionLevel",
"targetUrl": "https://1edtech.edu/catalog/degree/project/rubric/levels/mastered"
}
],
"description": "The author demonstrated...",
"level": "Mastered",
"name": "Mastery",
"points": "4"
},
{
"id": "urn:uuid:6b84b429-31ee-4dac-9d20-e5c55881f80e",
"type": "RubricCriterionLevel",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFRubricCriterionLevel",
"targetUrl": "https://1edtech.edu/catalog/degree/project/rubric/levels/basic"
}
],
"description": "The author demonstrated...",
"level": "Basic",
"name": "Basic",
"points": "4"
}
]
},
{
"id": "urn:uuid:b07c0387-f2d6-4b65-a3f4-f4e4302ea8f7",
"type": "InvalidResultDescription",
"name": "Project Status",
"resultType": "Status"
}
]
},
"result": [
{
"type": [
"Result"
],
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project/result/1"
}
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c",
"value": "A"
},
{
"type": [
"Result"
],
"achievedLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project/result/1"
}
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c"
},
{
"type": [
"Result"
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c",
"status": "Completed"
}
]
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,52 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"identifier": [{
"type": "InvalidIdentityObject",
"hashed": true,
"identityHash": "asdjhsadas",
"identityType": "lisSourcedId"
}],
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,52 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
},
"source": {
"id": "https://school.edu/issuers/201234",
"type": "InvalidProfile",
"name": "1EdTech College of Arts"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,92 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
},
"result": [
{
"type": [
"Result"
],
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project/result/1"
}
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c",
"value": "A"
},
{
"type": [
"InvalidResult"
],
"achievedLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a",
"alignment": [
{
"type": "Alignment",
"targetCode": "project",
"targetDescription": "Project description",
"targetName": "Final Project",
"targetFramework": "1EdTech University Program and Course Catalog",
"targetType": "CFItem",
"targetUrl": "https://1edtech.edu/catalog/degree/project/result/1"
}
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c"
},
{
"type": [
"Result"
],
"resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c",
"status": "Completed"
}
]
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,47 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"InvalidAchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,66 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"evidence": [
{
"id": "https://1edtech.edu/credentials/3732/evidence/1",
"type": "Evidence",
"narrative": "# Final Project Report \n This project was ...",
"name": "Final Project Report",
"description": "This is the final project report.",
"genre": "Research",
"audience": "Department"
},
{
"id": "https://github.com/somebody/project",
"type": "InvalidEvidence",
"name": "Final Project Code",
"description": "This is the source code for the final project app.",
"genre": "Research",
"audience": "Department"
}
],
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,59 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp",
"otherIdentifier": [
{
"type": "IdentifierEntry",
"identifier": "12345",
"identifierType": "sourcedId"
},
{
"type": "InvalidIdentifierEntry",
"identifier": "67890",
"identifierType": "nationalIdentityNumber"
}
]
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,54 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp",
"parentOrg": {
"id": "https://example.com/issuers/876543",
"type": [
"InvalidProfile"
],
"name": "Example Parent Corp"
}
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,47 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"InvalidProfile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}

View File

@ -0,0 +1,51 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.com/credentials/3527",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "https://example.com/issuers/876543",
"type": [
"Profile"
],
"name": "Example Corp"
},
"issuanceDate": "2010-01-01T00:00:00Z",
"name": "Teamwork Badge",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"type": [
"AchievementSubject"
],
"achievement": {
"id": "https://example.com/achievements/21st-century-skills/teamwork",
"type": [
"Achievement"
],
"criteria": {
"narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
},
"description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
"name": "Teamwork"
}
},
"proof": [{
"type": "SomeProofType",
"created": "2022-11-16T18:54:22Z",
"proofPurpose": "assertionMethod"
},
{
"type": "Ed25519Signature2020",
"created": "2022-11-16T18:54:22Z",
"verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv",
"proofPurpose": "assertionMethod",
"proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK"
}
]
}