From 60e0187052052c07aaf10ca16ba35051d1e53150 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 30 Nov 2022 10:52:38 +0100 Subject: [PATCH] Added image validation --- .../org/oneedtech/inspect/vc/Assertion.java | 7 +- .../probe/ValidationImagePropertyProbe.java | 65 +++++++++++++++ .../vc/probe/ValidationPropertyProbe.java | 83 +------------------ .../probe/ValidationPropertyProbeFactory.java | 3 + 4 files changed, 77 insertions(+), 81 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index df46f7c..74125f3 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -146,7 +146,9 @@ public class Assertion extends Credential { TEXT(PrimitiveValueValidator::validateText), TEXT_OR_NUMBER(PrimitiveValueValidator::validateTextOrNumber), URL(PrimitiveValueValidator::validateUrl), - URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority); + URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority), + + IMAGE(null); private final Function validationFunction; @@ -173,7 +175,8 @@ public class Assertion extends Credential { new Validation.Builder().name("expires").type(ValueType.DATETIME).required(false).build(), new Validation.Builder().name("image").type(ValueType.ID).required(false).allowRemoteUrl(true).expectedType(Type.Image).fetch(false).allowDataUri(false).build(), new Validation.Builder().name("narrative").type(ValueType.MARKDOWN_TEXT).required(false).build(), - new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build() + new Validation.Builder().name("evidence").type(ValueType.ID).allowRemoteUrl(true).expectedType(Type.Evidence).many(true).fetch(false).required(false).build(), + new Validation.Builder().name("image").type(ValueType.IMAGE).required(false).many(false).allowDataUri(false).build() )) .put(Type.BadgeClass, List.of( new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java new file mode 100644 index 0000000..d9781ac --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationImagePropertyProbe.java @@ -0,0 +1,65 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.resource.MimeType; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.vc.Validation; +import org.oneedtech.inspect.vc.util.PrimitiveValueValidator; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ValidationImagePropertyProbe extends ValidationPropertyProbe { + + public ValidationImagePropertyProbe(Validation validation) { + super(validation); + } + + public ValidationImagePropertyProbe(Validation validation, boolean fullValidate) { + super(validation, fullValidate); + } + + @Override + protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) { + return notRun("Could not load and validate image in node " + node.toString(), ctx); + } + + @Override + protected ReportItems validate(JsonNode node, RunContext ctx) { + if (node.isArray()) { + return error("many images not allowed", ctx); + } + String url = node.isObject() ? node.get("id").asText() : node.asText(); + if (PrimitiveValueValidator.validateDataUri(node)) { + if (!validation.isAllowDataUri()) { + return error("Image in node " + node + " may not be a data URI.", ctx); + } + + // check mime types + final Pattern pattern = Pattern.compile("(^data):([^,]{0,}?)?(base64)?,(.*$)"); + final Matcher matcher = pattern.matcher(url); + if (matcher.matches()) { + MimeType mimeType = new MimeType(matcher.toMatchResult().group(2)); + if (!allowedMimeTypes.contains(mimeType)) { + return error("Data URI image does not declare any of the allowed PNG or SVG mime types in " + node.asText(), ctx); + } + } + } else if (!url.isEmpty()) { + try { + UriResource uriResource = resolveUriResource(ctx, url); + // TODO: load resource from cache + // TODO: check accept type -> 'Accept': 'application/ld+json, application/json, image/png, image/svg+xml' + uriResource.asByteSource(); + } catch (Throwable t) { + return fatal(t.getMessage(), ctx); + } + } + return success(ctx); + } + + private static final List allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java index b9edbd1..ba242a6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbe.java @@ -122,7 +122,7 @@ public class ValidationPropertyProbe extends PropertyProbe { return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx); } else { // fetch - UriResource uriResource = resolveUriResource(ctx, childNode); + UriResource uriResource = resolveUriResource(ctx, childNode.asText()); result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { @@ -152,8 +152,8 @@ public class ValidationPropertyProbe extends PropertyProbe { return result.size() > 0 ? result : success(ctx); } - private UriResource resolveUriResource(RunContext ctx, JsonNode childNode) throws URISyntaxException { - URI uri = new URI(childNode.asText()); + protected UriResource resolveUriResource(RunContext ctx, String url) throws URISyntaxException { + URI uri = new URI(url); UriResource initialUriResource = new UriResource(uri); UriResource uriResource = initialUriResource; @@ -182,7 +182,7 @@ public class ValidationPropertyProbe extends PropertyProbe { return new ReportItems(results); } - private void flattenEmbeddedResource() { + private void flattenEmbeddedResource(JsonNode node, RunContext ctx) { /* try: node_id = task_meta['node_id'] @@ -249,80 +249,5 @@ public class ValidationPropertyProbe extends PropertyProbe { */ } - private void validateImage() { - /* -def validate_image(state, task_meta, **options): - try: - node_id = task_meta.get('node_id') - node_path = task_meta.get('node_path') - prop_name = task_meta.get('prop_name', 'image') - node_class = task_meta.get('node_class') - required = bool(task_meta.get('required', False)) - if node_id: - node = get_node_by_id(state, node_id) - node_path = [node_id] - else: - node = get_node_by_path(state, node_path) - - if options.get('cache_backend'): - session = requests_cache.CachedSession( - backend=options['cache_backend'], expire_after=options.get('cache_expire_after', 300)) - else: - session = requests.Session() - except (IndexError, TypeError, KeyError): - raise TaskPrerequisitesError() - - actions = [] - - image_val = node.get(prop_name) - - if image_val is None: - return task_result(not required, "Could not load and validate image in node {}".format(abv_node(node_id, node_path))) - if isinstance(image_val, six.string_types): - url = image_val - elif isinstance(image_val, dict): - url = image_val.get('id') - elif isinstance(image_val, list): - return task_result(False, "many images not allowed") - else: - raise TypeError("Could not interpret image property value {}".format( - abbreviate_value(image_val) - )) - if is_data_uri(url): - if task_meta.get('allow_data_uri', False) is False: - return task_result(False, "Image in node {} may not be a data URI.".format(abv_node(node_id, node_path))) - try: - mimetypes = re.match(r'(?P^data):(?P[^,]{0,}?)?(?Pbase64)?,(?P.*$)', url).group( - 'mimetypes') - if 'image/png' not in mimetypes and 'image/svg+xml' not in mimetypes: - raise ValueError("Disallowed filetype") - except (AttributeError, ValueError,): - return task_result( - False, "Data URI image does not declare any of the allowed PNG or SVG mime types in {}".format( - abv_node(node_id, node_path)) - ) - elif url: - existing_file = state.get('input', {}).get('original_json', {}).get(url) - if existing_file: - return task_result(True, "Image resource already stored for url {}".format(abbreviate_value(url))) - else: - try: - result = session.get( - url, headers={'Accept': 'application/ld+json, application/json, image/png, image/svg+xml'} - ) - result.raise_for_status() - content_type = result.headers['content-type'] - encoded_body = base64.b64encode(result.content) - data_uri = "data:{};base64,{}".format(content_type, encoded_body) - - except (requests.ConnectionError, requests.HTTPError, KeyError): - return task_result(False, "Could not fetch image at {}".format(url)) - else: - actions.append(store_original_resource(url, data_uri)) - - return task_result(True, "Validated image for node {}".format(abv_node(node_id, node_path)), actions) - - */ - } public static final String ID = ValidationPropertyProbe.class.getSimpleName(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java index 0156d32..896adb6 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ValidationPropertyProbeFactory.java @@ -19,6 +19,9 @@ public class ValidationPropertyProbeFactory { if (validation.getType() == ValueType.RDF_TYPE) { return new ValidationRdfTypePropertyProbe(validation, fullValidate); } + if (validation.getType() == ValueType.IMAGE) { + return new ValidationImagePropertyProbe(validation); + } return new ValidationPropertyProbe(validation, fullValidate); } }