From 23a34bd47073cf04bfdd443e50b4945f3923a4a0 Mon Sep 17 00:00:00 2001 From: Markus Gylling Date: Thu, 1 Sep 2022 11:05:44 +0200 Subject: [PATCH] cleanup clr step #1 --- .../org/oneedtech/inspect/vc/Credential.java | 10 + .../inspect/vc/EmbeddedVCInspector.java | 175 ------------------ .../inspect/vc/EndorsementInspector.java | 22 +-- .../oneedtech/inspect/vc/OB30Inspector.java | 62 ++----- .../org/oneedtech/inspect/vc/VCInspector.java | 28 +++ .../vc/probe/ContextPropertyProbe.java | 10 +- .../org/oneedtech/inspect/vc/OB30Tests.java | 3 +- 7 files changed, 68 insertions(+), 242 deletions(-) delete mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/EmbeddedVCInspector.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index e88ab1b..d18a983 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -60,6 +60,11 @@ public class Credential extends GeneratedObject { return Optional.ofNullable(jwt); } + public ProofType getProofType() { + if(jwt == null) return ProofType.EMBEDDED; + return ProofType.EXTERNAL; + } + /** * Get the canonical schema for this credential if such exists. */ @@ -105,6 +110,11 @@ public class Credential extends GeneratedObject { } } + public enum ProofType { + EXTERNAL, + EMBEDDED + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EmbeddedVCInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EmbeddedVCInspector.java deleted file mode 100644 index 4a5a16f..0000000 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EmbeddedVCInspector.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.oneedtech.inspect.vc; - -import static java.lang.Boolean.TRUE; - -import static org.oneedtech.inspect.core.probe.RunContext.Key.*; -import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; -import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -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.probe.json.JsonSchemaProbe; -import org.oneedtech.inspect.core.report.Report; -import org.oneedtech.inspect.core.report.ReportItems; -import org.oneedtech.inspect.schema.SchemaKey; -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.probe.CredentialParseProbe; -import org.oneedtech.inspect.vc.probe.ExpirationVerifierProbe; -import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; -import org.oneedtech.inspect.vc.probe.IssuanceVerifierProbe; -import org.oneedtech.inspect.vc.probe.RevocationListProbe; -import org.oneedtech.inspect.vc.probe.SignatureVerifierProbe; -import org.oneedtech.inspect.vc.probe.TypePropertyProbe; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * An inspector for EndorsementCredential objects. - * @author mgylling - */ -public class EmbeddedVCInspector extends VCInspector implements SubInspector { - - protected > EmbeddedVCInspector(B builder) { - super(builder); - } - - @Override - public Report run(Resource resource, Map 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 - */ - Credential verifiableCredential = (Credential) parentObjects.get(VC_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(VC_KEY, verifiableCredential) - .build(); - - List 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)) return abort(ctx, accumulator, probeCount); - - //we expect the above to place a generated object in the context - Credential crd = ctx.getGeneratedObject(Credential.ID); - - //type property - probeCount++; - accumulator.add(new TypePropertyProbe(Type.ClrCredential).run(crd.getJson(), ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - - //canonical schema and inline schema - SchemaKey schema = crd.getSchemaKey().orElseThrow(); - for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { - probeCount++; - accumulator.add(probe.run(crd.getJson(), ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - } - - //signatures, proofs - probeCount++; - if(crd.getJwt().isPresent()){ - //The credential originally contained in a JWT, validate the jwt and external proof. - accumulator.add(new SignatureVerifierProbe().run(crd, ctx)); - } else { - //The credential not contained in a jwt, must have an internal proof. - //TODO: @Miles Need to fix the issuer, Same as with outer CLR - //Swap -> "verificationMethod": "https://example.edu/issuers/565049#z6MkwA1498JfoCS3y4y3zggBDAosQEoCi5gsYH2PMXh1cFWK", - //To be like -> "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj", - //...but also work properly which old record seems not be doing... - - /* - accumulator.add(new ProofVerifierProbe().run(crd, ctx)); - */ - - } - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - - //check refresh service if we are not already refreshed (check just like in external CLR) - probeCount++; - if(resource.getContext().get(REFRESHED) != TRUE) { - Optional newID = checkRefreshService(crd, ctx); - if(newID.isPresent()) { - //TODO resource.type - return this.run( - new UriResource(new URI(newID.get())) - .setContext(new ResourceContext(REFRESHED, TRUE))); - } - } - - //revocation, expiration and issuance (check just like in external CLR) - for(Probe probe : List.of(new RevocationListProbe(), - new ExpirationVerifierProbe(), new IssuanceVerifierProbe())) { - probeCount++; - accumulator.add(probe.run(crd, 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 Report run(R resource) { - throw new IllegalStateException("must use #run(resource, map)"); - } - - public static class Builder extends VCInspector.Builder { - @SuppressWarnings("unchecked") - @Override - public EmbeddedVCInspector build() { - return new EmbeddedVCInspector(this); - } - } - - public static final String VC_KEY = "VC_KEY"; - private static final String REFRESHED = "is.refreshed.credential"; - - /** - * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the - * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL - * provided, then start the verification process over using the response as input. If the request fails, - * the credential is invalid. - */ - private Optional checkRefreshService(Credential crd, RunContext ctx) { - JsonNode refreshServiceNode = crd.getJson().get("refreshService"); - if(refreshServiceNode != null) { - JsonNode serviceTypeNode = refreshServiceNode.get("type"); - if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { - JsonNode serviceURINode = refreshServiceNode.get("id"); - if(serviceURINode != null) { - return Optional.of(serviceURINode.asText()); - } - } - } - return Optional.empty(); - } -} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index 0b45345..94b3a9b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -137,25 +137,5 @@ public class EndorsementInspector extends VCInspector implements SubInspector { } public static final String ENDORSEMENT_KEY = "ENDORSEMENT_KEY"; - private static final String REFRESHED = "is.refreshed.credential"; - - /** - * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the - * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL - * provided, then start the verification process over using the response as input. If the request fails, - * the credential is invalid. - */ - private Optional checkRefreshService(Credential crd, RunContext ctx) { - JsonNode refreshServiceNode = crd.getJson().get("refreshService"); - if(refreshServiceNode != null) { - JsonNode serviceTypeNode = refreshServiceNode.get("type"); - if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { - JsonNode serviceURINode = refreshServiceNode.get("id"); - if(serviceURINode != null) { - return Optional.of(serviceURINode.asText()); - } - } - } - return Optional.empty(); - } + } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 7ccae9b..57e583e 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -4,7 +4,7 @@ import static java.lang.Boolean.TRUE; 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.Type.OpenBadgeCredential; +import static org.oneedtech.inspect.vc.Credential.ProofType.EXTERNAL; import static org.oneedtech.inspect.vc.EndorsementInspector.ENDORSEMENT_KEY; import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.oneedtech.inspect.core.Inspector; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.probe.RunContext.Key; @@ -31,9 +32,9 @@ 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.probe.CredentialSubjectProbe; 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.ExpirationVerifierProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceVerifierProbe; @@ -54,9 +55,9 @@ import com.google.common.collect.ImmutableList; public class OB30Inspector extends VCInspector { protected final List> userProbes; - protected OB30Inspector(OB30Inspector.Builder builder) { + protected OB30Inspector(OB30Inspector.Builder builder) { super(builder); - this.userProbes = ImmutableList.copyOf(builder.probes); + this.userProbes = ImmutableList.copyOf(builder.probes); } //https://docs.google.com/document/d/1_imUl2K-5tMib0AUxwA9CWb0Ap1b3qif0sXydih68J0/edit# @@ -70,7 +71,7 @@ public class OB30Inspector extends VCInspector { JsonSchemaCache.reset(); CachingDocumentLoader.reset(); } - + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); @@ -91,44 +92,43 @@ public class OB30Inspector extends VCInspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); //we expect the above to place a generated object in the context - Credential crd = ctx.getGeneratedObject(Credential.ID); + Credential ob = ctx.getGeneratedObject(Credential.ID); //context and type properties Credential.Type type = Type.OpenBadgeCredential; for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { probeCount++; - accumulator.add(probe.run(crd.getJson(), ctx)); + accumulator.add(probe.run(ob.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } //canonical schema and inline schemata - SchemaKey schema = crd.getSchemaKey().orElseThrow(); + SchemaKey schema = ob.getSchemaKey().orElseThrow(); for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { probeCount++; - accumulator.add(probe.run(crd.getJson(), ctx)); + accumulator.add(probe.run(ob.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } //credentialSubject - accumulator.add(new CredentialSubjectProbe().run(crd.getJson(), ctx)); + accumulator.add(new CredentialSubjectProbe().run(ob.getJson(), ctx)); //signatures, proofs probeCount++; - if(crd.getJwt().isPresent()){ + if(ob.getProofType() == EXTERNAL){ //The credential originally contained in a JWT, validate the jwt and external proof. - accumulator.add(new SignatureVerifierProbe().run(crd, ctx)); + accumulator.add(new SignatureVerifierProbe().run(ob, ctx)); } else { //The credential not contained in a jwt, must have an internal proof. - accumulator.add(new ProofVerifierProbe().run(crd, ctx)); + accumulator.add(new ProofVerifierProbe().run(ob, 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 newID = checkRefreshService(crd, ctx); + Optional newID = checkRefreshService(ob, ctx); if(newID.isPresent()) { - //TODO resource.type return this.run( new UriResource(new URI(newID.get())) .setContext(new ResourceContext(REFRESHED, TRUE))); @@ -139,14 +139,14 @@ public class OB30Inspector extends VCInspector { for(Probe probe : List.of(new RevocationListProbe(), new ExpirationVerifierProbe(), new IssuanceVerifierProbe())) { probeCount++; - accumulator.add(probe.run(crd, ctx)); + accumulator.add(probe.run(ob, ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } //embedded endorsements EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); - List endorsements = asNodeList(crd.getJson(), "$..endorsement", jsonPath); + List endorsements = asNodeList(ob.getJson(), "$..endorsement", jsonPath); for(JsonNode node : endorsements) { probeCount++; Credential endorsement = new Credential(resource, node); @@ -154,7 +154,7 @@ public class OB30Inspector extends VCInspector { } //embedded jwt endorsements - endorsements = asNodeList(crd.getJson(), "$..endorsementJwt", jsonPath); + endorsements = asNodeList(ob.getJson(), "$..endorsementJwt", jsonPath); for(JsonNode node : endorsements) { probeCount++; String jwt = node.asText(); @@ -166,7 +166,7 @@ public class OB30Inspector extends VCInspector { //finally, run any user-added probes for(Probe probe : userProbes) { probeCount++; - accumulator.add(probe.run(crd, ctx)); + accumulator.add(probe.run(ob, ctx)); } } catch (Exception e) { @@ -175,29 +175,7 @@ public class OB30Inspector extends VCInspector { return new Report(ctx, new ReportItems(accumulator), probeCount); } - - /** - * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the - * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL - * provided, then start the verification process over using the response as input. If the request fails, - * the credential is invalid. - */ - private Optional checkRefreshService(Credential crd, RunContext ctx) { - JsonNode refreshServiceNode = crd.getJson().get("refreshService"); - if(refreshServiceNode != null) { - JsonNode serviceTypeNode = refreshServiceNode.get("type"); - if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { - JsonNode serviceURINode = refreshServiceNode.get("id"); - if(serviceURINode != null) { - return Optional.of(serviceURINode.asText()); - } - } - } - return Optional.empty(); - } - - private static final String REFRESHED = "is.refreshed.credential"; - + public static class Builder extends VCInspector.Builder { @SuppressWarnings("unchecked") @Override diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java index a07157d..255c54b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java @@ -2,6 +2,7 @@ package org.oneedtech.inspect.vc; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.oneedtech.inspect.core.Inspector; import org.oneedtech.inspect.core.probe.Outcome; @@ -10,6 +11,8 @@ import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.core.report.ReportItems; +import com.fasterxml.jackson.databind.JsonNode; + /** * Abstract base for verifiable credentials inspectors/verifiers. * @author mgylling @@ -25,12 +28,37 @@ public abstract class VCInspector extends Inspector { } protected boolean broken(List accumulator) { + if(getBehavior(Inspector.Behavior.VALIDATOR_FAIL_FAST) == Boolean.FALSE) { + return false; + } for(ReportItems items : accumulator) { if(items.contains(Outcome.FATAL, Outcome.EXCEPTION, Outcome.NOT_RUN)) return true; } return false; } + /** + * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the + * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL + * provided, then start the verification process over using the response as input. If the request fails, + * the credential is invalid. + */ + protected Optional checkRefreshService(Credential crd, RunContext ctx) { + JsonNode refreshServiceNode = crd.getJson().get("refreshService"); + if(refreshServiceNode != null) { + JsonNode serviceTypeNode = refreshServiceNode.get("type"); + if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { + JsonNode serviceURINode = refreshServiceNode.get("id"); + if(serviceURINode != null) { + return Optional.of(serviceURINode.asText()); + } + } + } + return Optional.empty(); + } + + protected static final String REFRESHED = "is.refreshed.credential"; + public abstract static class Builder> extends Inspector.Builder { final List> probes; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index 91fbb66..a72f26b 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -37,7 +37,7 @@ public class ContextPropertyProbe extends Probe { ArrayNode contextNode = (ArrayNode) root.get("@context"); if (contextNode == null) { - return fatal("No @context property", ctx); + return notRun("No @context property", ctx); } List expected = values.get(values.keySet() @@ -59,10 +59,14 @@ public class ContextPropertyProbe extends Probe { } private final static Map, List> values = new ImmutableMap.Builder, List>() + // TODO uris will change .put(Set.of(OpenBadgeCredential, AchievementCredential, EndorsementCredential), List.of("https://www.w3.org/2018/credentials/v1", - "https://imsglobal.github.io/openbadges-specification/context.json")) // TODO will change - // (https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld) + "https://imsglobal.github.io/openbadges-specification/context.json")) + .put(Set.of(ClrCredential), + List.of("https://www.w3.org/2018/credentials/v1", + "https://dc.imsglobal.org/draft/clr/v2p0/context", + "https://imsglobal.github.io/openbadges-specification/context.json")) .build(); public static final String ID = ContextPropertyProbe.class.getSimpleName(); diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index 4471d18..1d2218b 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -26,7 +26,8 @@ public class OB30Tests { @BeforeAll static void setup() { validator = new OB30Inspector.Builder() - .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.VALIDATOR_FAIL_FAST, true) .build(); }