Added image validation
This commit is contained in:
parent
d3667e580c
commit
60e0187052
@ -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(),
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user