Added image validation

This commit is contained in:
Xavi Aracil 2022-11-30 10:52:38 +01:00
parent d3667e580c
commit 60e0187052
4 changed files with 77 additions and 81 deletions

View File

@ -146,7 +146,9 @@ public class Assertion extends Credential {
TEXT(PrimitiveValueValidator::validateText), TEXT(PrimitiveValueValidator::validateText),
TEXT_OR_NUMBER(PrimitiveValueValidator::validateTextOrNumber), TEXT_OR_NUMBER(PrimitiveValueValidator::validateTextOrNumber),
URL(PrimitiveValueValidator::validateUrl), URL(PrimitiveValueValidator::validateUrl),
URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority); URL_AUTHORITY(PrimitiveValueValidator::validateUrlAuthority),
IMAGE(null);
private final Function<JsonNode, Boolean> validationFunction; private final Function<JsonNode, Boolean> 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("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("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("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( .put(Type.BadgeClass, List.of(
new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(), new Validation.Builder().name("id").type(ValueType.IRI).required(true).build(),

View File

@ -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<MimeType> allowedMimeTypes = List.of(MimeType.IMAGE_PNG, MimeType.IMAGE_SVG);
}

View File

@ -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); return error("Node " + node.toString() + " has " + validation.getName() +" property value `" + childNode.toString() + "` that appears not to be in URI format", ctx);
} else { } else {
// fetch // fetch
UriResource uriResource = resolveUriResource(ctx, childNode); UriResource uriResource = resolveUriResource(ctx, childNode.asText());
result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx))); result = new ReportItems(List.of(result, new CredentialParseProbe().run(uriResource, ctx)));
if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) { if (!result.contains(Outcome.FATAL, Outcome.EXCEPTION)) {
@ -152,8 +152,8 @@ public class ValidationPropertyProbe extends PropertyProbe {
return result.size() > 0 ? result : success(ctx); return result.size() > 0 ? result : success(ctx);
} }
private UriResource resolveUriResource(RunContext ctx, JsonNode childNode) throws URISyntaxException { protected UriResource resolveUriResource(RunContext ctx, String url) throws URISyntaxException {
URI uri = new URI(childNode.asText()); URI uri = new URI(url);
UriResource initialUriResource = new UriResource(uri); UriResource initialUriResource = new UriResource(uri);
UriResource uriResource = initialUriResource; UriResource uriResource = initialUriResource;
@ -182,7 +182,7 @@ public class ValidationPropertyProbe extends PropertyProbe {
return new ReportItems(results); return new ReportItems(results);
} }
private void flattenEmbeddedResource() { private void flattenEmbeddedResource(JsonNode node, RunContext ctx) {
/* /*
try: try:
node_id = task_meta['node_id'] 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<scheme>^data):(?P<mimetypes>[^,]{0,}?)?(?P<encoding>base64)?,(?P<data>.*$)', 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(); public static final String ID = ValidationPropertyProbe.class.getSimpleName();
} }

View File

@ -19,6 +19,9 @@ public class ValidationPropertyProbeFactory {
if (validation.getType() == ValueType.RDF_TYPE) { if (validation.getType() == ValueType.RDF_TYPE) {
return new ValidationRdfTypePropertyProbe(validation, fullValidate); return new ValidationRdfTypePropertyProbe(validation, fullValidate);
} }
if (validation.getType() == ValueType.IMAGE) {
return new ValidationImagePropertyProbe(validation);
}
return new ValidationPropertyProbe(validation, fullValidate); return new ValidationPropertyProbe(validation, fullValidate);
} }
} }