Merge pull request #66 from imsglc/add-standalone-endorsement-support
Add standalone endorsement support
This commit is contained in:
commit
6bb159dc13
@ -1,6 +1,7 @@
|
|||||||
package org.oneedtech.inspect.vc;
|
package org.oneedtech.inspect.vc;
|
||||||
|
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import static org.oneedtech.inspect.core.Inspector.Behavior.RESET_CACHES_ON_RUN;
|
||||||
import static org.oneedtech.inspect.core.probe.RunContext.Key.*;
|
import static org.oneedtech.inspect.core.probe.RunContext.Key.*;
|
||||||
import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException;
|
import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException;
|
||||||
import static org.oneedtech.inspect.util.code.Defensives.checkNotNull;
|
import static org.oneedtech.inspect.util.code.Defensives.checkNotNull;
|
||||||
@ -19,14 +20,19 @@ import org.oneedtech.inspect.core.probe.GeneratedObject;
|
|||||||
import org.oneedtech.inspect.core.probe.Probe;
|
import org.oneedtech.inspect.core.probe.Probe;
|
||||||
import org.oneedtech.inspect.core.probe.RunContext;
|
import org.oneedtech.inspect.core.probe.RunContext;
|
||||||
import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator;
|
import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator;
|
||||||
|
import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe;
|
||||||
import org.oneedtech.inspect.core.report.Report;
|
import org.oneedtech.inspect.core.report.Report;
|
||||||
import org.oneedtech.inspect.core.report.ReportItems;
|
import org.oneedtech.inspect.core.report.ReportItems;
|
||||||
|
import org.oneedtech.inspect.schema.JsonSchemaCache;
|
||||||
|
import org.oneedtech.inspect.schema.SchemaKey;
|
||||||
import org.oneedtech.inspect.util.json.ObjectMapperCache;
|
import org.oneedtech.inspect.util.json.ObjectMapperCache;
|
||||||
import org.oneedtech.inspect.util.resource.Resource;
|
import org.oneedtech.inspect.util.resource.Resource;
|
||||||
import org.oneedtech.inspect.util.resource.UriResource;
|
import org.oneedtech.inspect.util.resource.UriResource;
|
||||||
import org.oneedtech.inspect.util.resource.context.ResourceContext;
|
import org.oneedtech.inspect.util.resource.context.ResourceContext;
|
||||||
import org.oneedtech.inspect.vc.VerifiableCredential.Type;
|
import org.oneedtech.inspect.vc.VerifiableCredential.Type;
|
||||||
import org.oneedtech.inspect.vc.probe.ContextPropertyProbe;
|
import org.oneedtech.inspect.vc.probe.ContextPropertyProbe;
|
||||||
|
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
|
||||||
|
import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe;
|
import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.ExpirationProbe;
|
import org.oneedtech.inspect.vc.probe.ExpirationProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.ExternalProofProbe;
|
import org.oneedtech.inspect.vc.probe.ExternalProofProbe;
|
||||||
@ -34,9 +40,11 @@ import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
|
|||||||
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
|
import org.oneedtech.inspect.vc.probe.IssuanceProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.RevocationListProbe;
|
import org.oneedtech.inspect.vc.probe.RevocationListProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
|
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
|
||||||
|
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An inspector for EndorsementCredential objects.
|
* An inspector for EndorsementCredential objects.
|
||||||
@ -44,8 +52,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
*/
|
*/
|
||||||
public class EndorsementInspector extends VCInspector implements SubInspector {
|
public class EndorsementInspector extends VCInspector implements SubInspector {
|
||||||
|
|
||||||
|
protected final List<Probe<VerifiableCredential>> userProbes;
|
||||||
|
|
||||||
protected <B extends VCInspector.Builder<?>> EndorsementInspector(B builder) {
|
protected <B extends VCInspector.Builder<?>> EndorsementInspector(B builder) {
|
||||||
super(builder);
|
super(builder);
|
||||||
|
this.userProbes = ImmutableList.copyOf(builder.probes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -127,8 +138,98 @@ public class EndorsementInspector extends VCInspector implements SubInspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R extends Resource> Report run(R resource) {
|
public Report run(Resource resource) {
|
||||||
throw new IllegalStateException("must use #run(resource, map)");
|
super.check(resource);
|
||||||
|
|
||||||
|
if (getBehavior(RESET_CACHES_ON_RUN) == TRUE) {
|
||||||
|
JsonSchemaCache.reset();
|
||||||
|
CachingDocumentLoader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectMapper mapper = ObjectMapperCache.get(DEFAULT);
|
||||||
|
JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper);
|
||||||
|
|
||||||
|
RunContext ctx = new RunContext.Builder()
|
||||||
|
.put(this)
|
||||||
|
.put(resource)
|
||||||
|
.put(JACKSON_OBJECTMAPPER, mapper)
|
||||||
|
.put(JSONPATH_EVALUATOR, jsonPath)
|
||||||
|
.put(GENERATED_OBJECT_BUILDER, new VerifiableCredential.Builder())
|
||||||
|
.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
|
||||||
|
VerifiableCredential endorsement = ctx.getGeneratedObject(VerifiableCredential.ID);
|
||||||
|
|
||||||
|
//context and type properties
|
||||||
|
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));
|
||||||
|
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//canonical schema and inline schema
|
||||||
|
SchemaKey schema = endorsement.getSchemaKey().orElseThrow();
|
||||||
|
for(Probe<JsonNode> probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) {
|
||||||
|
probeCount++;
|
||||||
|
accumulator.add(probe.run(endorsement.getJson(), ctx));
|
||||||
|
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//credentialSubject
|
||||||
|
probeCount++;
|
||||||
|
accumulator.add(new CredentialSubjectProbe().run(endorsement.getJson(), ctx));
|
||||||
|
|
||||||
|
//signatures, proofs
|
||||||
|
probeCount++;
|
||||||
|
if(endorsement.getProofType() == EXTERNAL){
|
||||||
|
//The credential originally contained in a JWT, validate the jwt and external proof.
|
||||||
|
accumulator.add(new ExternalProofProbe().run(endorsement, ctx));
|
||||||
|
} else {
|
||||||
|
accumulator.add(new EmbeddedProofProbe().run(endorsement, ctx));
|
||||||
|
}
|
||||||
|
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||||
|
|
||||||
|
//check refresh service if we are not already refreshed
|
||||||
|
probeCount++;
|
||||||
|
if(resource.getContext().get(REFRESHED) != TRUE) {
|
||||||
|
Optional<String> newID = checkRefreshService(endorsement, ctx);
|
||||||
|
if(newID.isPresent()) {
|
||||||
|
return this.run(
|
||||||
|
new UriResource(new URI(newID.get()))
|
||||||
|
.setContext(new ResourceContext(REFRESHED, TRUE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//revocation, expiration and issuance
|
||||||
|
for(Probe<Credential> probe : List.of(new RevocationListProbe(),
|
||||||
|
new ExpirationProbe(), new IssuanceProbe())) {
|
||||||
|
probeCount++;
|
||||||
|
accumulator.add(probe.run(endorsement, ctx));
|
||||||
|
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//finally, run any user-added probes
|
||||||
|
for(Probe<VerifiableCredential> probe : userProbes) {
|
||||||
|
probeCount++;
|
||||||
|
accumulator.add(probe.run(endorsement, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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<EndorsementInspector.Builder> {
|
public static class Builder extends VCInspector.Builder<EndorsementInspector.Builder> {
|
||||||
|
@ -52,4 +52,6 @@ public class CredentialParseProbe extends Probe<Resource> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String ID = CredentialParseProbe.class.getSimpleName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
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 org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.oneedtech.inspect.core.Inspector.Behavior;
|
||||||
|
import org.oneedtech.inspect.core.report.Report;
|
||||||
|
import org.oneedtech.inspect.test.PrintHelper;
|
||||||
|
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
|
||||||
|
|
||||||
|
public class Endorsement30Tests {
|
||||||
|
private static EndorsementInspector validator;
|
||||||
|
private static boolean verbose = false;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
validator = new EndorsementInspector.Builder()
|
||||||
|
.set(Behavior.TEST_INCLUDE_SUCCESS, true)
|
||||||
|
.set(Behavior.VALIDATOR_FAIL_FAST, false)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEndorsementWithoutErrors() {
|
||||||
|
assertDoesNotThrow(()->{
|
||||||
|
Report report = validator.run(Samples.OB30.JSON.ENDORSEMENT_VALID.asFileResource());
|
||||||
|
if(verbose) PrintHelper.print(report, true);
|
||||||
|
assertValid(report);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEndorsementWithErrors() {
|
||||||
|
assertDoesNotThrow(()->{
|
||||||
|
Report report = validator.run(Samples.OB30.JSON.ENDORSEMENT_ERR_SCHEMA_STATUS_REFRESH.asFileResource());
|
||||||
|
if(verbose) PrintHelper.print(report, true);
|
||||||
|
assertInvalid(report);
|
||||||
|
assertFatalCount(report, 1);
|
||||||
|
// Parse probe fails because refresh points to invalid URL so nothing to parse
|
||||||
|
assertHasProbeID(report, CredentialParseProbe.ID, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,8 @@ public class Samples {
|
|||||||
public final static Sample SIMPLE_JSON_ISSUED = new Sample("ob30/simple-err-issued.json", false);
|
public final static Sample SIMPLE_JSON_ISSUED = new Sample("ob30/simple-err-issued.json", false);
|
||||||
public final static Sample SIMPLE_JSON_ISSUER = new Sample("ob30/simple-err-issuer.json", false);
|
public final static Sample SIMPLE_JSON_ISSUER = new Sample("ob30/simple-err-issuer.json", false);
|
||||||
public final static Sample SIMPLE_JSON_ERR_CONTEXT = new Sample("ob30/simple-err-context.json", false);
|
public 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 static final class PNG {
|
public static final class PNG {
|
||||||
public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true);
|
public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true);
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
|
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
|
||||||
|
"https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",
|
||||||
|
"https://w3id.org/security/suites/ed25519-2020/v1"
|
||||||
|
],
|
||||||
|
"id": "http://1edtech.edu/endorsementcredential/3732",
|
||||||
|
"type": [
|
||||||
|
"VerifiableCredential",
|
||||||
|
"EndorsementCredential"
|
||||||
|
],
|
||||||
|
"issuer": {
|
||||||
|
"id": "https://state.gov/issuers/565049",
|
||||||
|
"type": "Profile",
|
||||||
|
"name": "State Department of Education"
|
||||||
|
},
|
||||||
|
"issuanceDate": "2010-01-01T00:00:00Z",
|
||||||
|
"expirationDate": "2030-01-01T00:00:00Z",
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "https://1edtech.edu/issuers/565049",
|
||||||
|
"type": "EndorsementSubject",
|
||||||
|
"endorsementComment": "1EdTech University is in good standing"
|
||||||
|
},
|
||||||
|
"credentialSchema": [
|
||||||
|
{
|
||||||
|
"id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_endorsementcredential_schema.json",
|
||||||
|
"type": "1EdTechJsonSchemaValidator2019"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "https://state.gov/schema/endorsementcredential.json",
|
||||||
|
"type": "1EdTechJsonSchemaValidator2019"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialStatus": {
|
||||||
|
"id": "https://state.gov/credentials/3732/revocations",
|
||||||
|
"type": "1EdTechRevocationList"
|
||||||
|
},
|
||||||
|
"refreshService": {
|
||||||
|
"id": "http://state.gov/credentials/3732",
|
||||||
|
"type": "1EdTechCredentialRefresh"
|
||||||
|
},
|
||||||
|
"proof": [
|
||||||
|
{
|
||||||
|
"type": "Ed25519Signature2020",
|
||||||
|
"created": "2022-12-15T16:53:56Z",
|
||||||
|
"verificationMethod": "https://state.gov/issuers/565049#z6MkmQ49FhpMN7V11B7Qzc6WC6Q3ymVW4xmHUsHBR2MWMMo1",
|
||||||
|
"proofPurpose": "assertionMethod",
|
||||||
|
"proofValue": "z36uHeAeKagDGaXMSqQX1eyKg4rFsWHzYLHxXfypkysPsvTtwN3Z8VZQtn7VQovX2GjkEWgGaW7hQ3UkNKbR84nUn"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
38
inspector-vc/src/test/resources/ob30/endorsement-valid.json
Normal file
38
inspector-vc/src/test/resources/ob30/endorsement-valid.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
|
"https://purl.imsglobal.org/spec/ob/v3p0/context.json",
|
||||||
|
"https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",
|
||||||
|
"https://w3id.org/security/suites/ed25519-2020/v1"
|
||||||
|
],
|
||||||
|
"id": "http://1edtech.edu/endorsementcredential/3732",
|
||||||
|
"type": [
|
||||||
|
"VerifiableCredential",
|
||||||
|
"EndorsementCredential"
|
||||||
|
],
|
||||||
|
"issuer": {
|
||||||
|
"id": "https://state.gov/issuers/565049",
|
||||||
|
"type": "Profile",
|
||||||
|
"name": "State Department of Education"
|
||||||
|
},
|
||||||
|
"issuanceDate": "2010-01-01T00:00:00Z",
|
||||||
|
"expirationDate": "2030-01-01T00:00:00Z",
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "https://1edtech.edu/issuers/565049",
|
||||||
|
"type": "EndorsementSubject",
|
||||||
|
"endorsementComment": "1EdTech University is in good standing"
|
||||||
|
},
|
||||||
|
"credentialSchema": [
|
||||||
|
{
|
||||||
|
"id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_endorsementcredential_schema.json",
|
||||||
|
"type": "1EdTechJsonSchemaValidator2019"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proof": {
|
||||||
|
"type": "Ed25519Signature2020",
|
||||||
|
"created": "2022-12-20T20:56:10Z",
|
||||||
|
"proofPurpose": "assertionMethod",
|
||||||
|
"verificationMethod": "did:key:z6MkgG59UvkBM1Lfv2D9JBu6yHn7aKurbwQgs1h5NCYJEk35",
|
||||||
|
"proofValue": "zLJF9oRBcpVQ9HUt8BRfPevvEGwL74bSyBKDpGzjNpb3KtMQ8JhQDPq3C4sqojneNz74YFWkjLSntC93iZGVscta"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user