New validation probe for rdf types
This commit is contained in:
parent
0d6d97cd4f
commit
d3667e580c
@ -31,6 +31,7 @@ import org.oneedtech.inspect.vc.probe.ContextPropertyProbe;
|
|||||||
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
|
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
|
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
|
||||||
import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe;
|
import org.oneedtech.inspect.vc.probe.ValidationPropertyProbe;
|
||||||
|
import org.oneedtech.inspect.vc.probe.ValidationPropertyProbeFactory;
|
||||||
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
|
||||||
|
|
||||||
import com.apicatalog.jsonld.loader.DocumentLoader;
|
import com.apicatalog.jsonld.loader.DocumentLoader;
|
||||||
@ -119,11 +120,12 @@ public class OB20Inspector extends Inspector {
|
|||||||
accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx));
|
accumulator.add(new JsonLDValidationProbe(jsonLdGeneratedObject).run(assertion, ctx));
|
||||||
if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount);
|
if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount);
|
||||||
|
|
||||||
// Validates the Open Badge
|
// Validates the Open Badge, from the compacted form
|
||||||
|
JsonNode assertionNode = mapper.readTree(jsonLdGeneratedObject.getJson());
|
||||||
List<Validation> validations = assertion.getValidations();
|
List<Validation> validations = assertion.getValidations();
|
||||||
for (Validation validation : validations) {
|
for (Validation validation : validations) {
|
||||||
probeCount++;
|
probeCount++;
|
||||||
accumulator.add(new ValidationPropertyProbe(validation).run(assertion.getJson(), ctx));
|
accumulator.add(ValidationPropertyProbeFactory.of(validation).run(assertionNode, ctx));
|
||||||
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader;
|
|||||||
|
|
||||||
|
|
||||||
public class ValidationPropertyProbe extends PropertyProbe {
|
public class ValidationPropertyProbe extends PropertyProbe {
|
||||||
private final Validation validation;
|
protected final Validation validation;
|
||||||
private final boolean fullValidate; // TODO: fullValidate
|
protected final boolean fullValidate; // TODO: fullValidate
|
||||||
|
|
||||||
public ValidationPropertyProbe(Validation validation) {
|
public ValidationPropertyProbe(Validation validation) {
|
||||||
this(validation, false);
|
this(validation, false);
|
||||||
@ -46,8 +46,6 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
setValidations(this::validate);
|
setValidations(this::validate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||||
if (validation.isRequired()) {
|
if (validation.isRequired()) {
|
||||||
@ -65,7 +63,7 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
* @param ctx associated run context
|
* @param ctx associated run context
|
||||||
* @return validation result
|
* @return validation result
|
||||||
*/
|
*/
|
||||||
private ReportItems validate(JsonNode node, RunContext ctx) {
|
protected ReportItems validate(JsonNode node, RunContext ctx) {
|
||||||
ReportItems result = new ReportItems();
|
ReportItems result = new ReportItems();
|
||||||
|
|
||||||
// required property
|
// required property
|
||||||
@ -172,7 +170,7 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) {
|
private ReportItems validateExpectedTypes(JsonNode node, RunContext ctx) {
|
||||||
List<ReportItems> results = validation.getExpectedTypes().stream()
|
List<ReportItems> results = validation.getExpectedTypes().stream()
|
||||||
.flatMap(type -> type.getValidations().stream())
|
.flatMap(type -> type.getValidations().stream())
|
||||||
.map(v -> new ValidationPropertyProbe(v, validation.isFullValidate()))
|
.map(v -> ValidationPropertyProbeFactory.of(v, validation.isFullValidate()))
|
||||||
.map(probe -> {
|
.map(probe -> {
|
||||||
try {
|
try {
|
||||||
return probe.run(node, ctx);
|
return probe.run(node, ctx);
|
||||||
@ -183,5 +181,148 @@ public class ValidationPropertyProbe extends PropertyProbe {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return new ReportItems(results);
|
return new ReportItems(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void flattenEmbeddedResource() {
|
||||||
|
/*
|
||||||
|
try:
|
||||||
|
node_id = task_meta['node_id']
|
||||||
|
node = get_node_by_id(state, node_id)
|
||||||
|
prop_name = task_meta['prop_name']
|
||||||
|
node_class = task_meta['node_class']
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
raise TaskPrerequisitesError()
|
||||||
|
|
||||||
|
actions = []
|
||||||
|
value = node.get(prop_name)
|
||||||
|
if value is None:
|
||||||
|
return task_result(True, "Expected property {} was missing in node {}".format(node_id))
|
||||||
|
|
||||||
|
if isinstance(value, six.string_types):
|
||||||
|
return task_result(
|
||||||
|
True, "Property {} referenced from {} is not embedded in need of flattening".format(
|
||||||
|
prop_name, abv_node(node_id=node_id)
|
||||||
|
))
|
||||||
|
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
return task_result(
|
||||||
|
False, "Property {} referenced from {} is not a JSON object or string as expected".format(
|
||||||
|
prop_name, abv_node(node_id=node_id)
|
||||||
|
))
|
||||||
|
embedded_node_id = value.get('id')
|
||||||
|
|
||||||
|
if embedded_node_id is None:
|
||||||
|
new_node = value.copy()
|
||||||
|
embedded_node_id = '_:{}'.format(uuid.uuid4())
|
||||||
|
new_node['id'] = embedded_node_id
|
||||||
|
new_node['@context'] = OPENBADGES_CONTEXT_V2_URI
|
||||||
|
actions.append(add_node(embedded_node_id, data=new_node))
|
||||||
|
actions.append(patch_node(node_id, {prop_name: embedded_node_id}))
|
||||||
|
actions.append(report_message(
|
||||||
|
'Node id missing at {}. A blank node ID has been assigned'.format(
|
||||||
|
abv_node(node_path=[node_id, prop_name], length=64)
|
||||||
|
), message_level=MESSAGE_LEVEL_WARNING)
|
||||||
|
)
|
||||||
|
elif not isinstance(embedded_node_id, six.string_types) or not is_iri(embedded_node_id):
|
||||||
|
return task_result(False, "Embedded JSON object at {} has no proper assigned id.".format(
|
||||||
|
abv_node(node_path=[node_id, prop_name])))
|
||||||
|
|
||||||
|
elif node_class == OBClasses.Assertion and not is_url(embedded_node_id):
|
||||||
|
if not re.match(URN_REGEX, embedded_node_id, re.IGNORECASE):
|
||||||
|
actions.append(report_message(
|
||||||
|
'ID format for {} at {} not in an expected HTTP or URN:UUID scheme'.format(
|
||||||
|
embedded_node_id, abv_node(node_path=[node_id, prop_name])
|
||||||
|
)))
|
||||||
|
new_node = value.copy()
|
||||||
|
new_node['@context'] = OPENBADGES_CONTEXT_V2_URI
|
||||||
|
actions.append(add_node(embedded_node_id, data=value))
|
||||||
|
actions.append(patch_node(node_id, {prop_name: embedded_node_id}))
|
||||||
|
|
||||||
|
else:
|
||||||
|
actions.append(patch_node(node_id, {prop_name: embedded_node_id}))
|
||||||
|
if not node_match_exists(state, embedded_node_id) and not filter_tasks(
|
||||||
|
state, node_id=embedded_node_id, task_type=FETCH_HTTP_NODE):
|
||||||
|
# fetch
|
||||||
|
actions.append(add_task(FETCH_HTTP_NODE, url=embedded_node_id))
|
||||||
|
|
||||||
|
return task_result(True, "Embedded {} node in {} queued for storage and/or refetching as needed", actions)
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.oneedtech.inspect.vc.probe;
|
||||||
|
|
||||||
|
import static org.oneedtech.inspect.util.code.Defensives.checkNotNull;
|
||||||
|
|
||||||
|
import org.oneedtech.inspect.vc.Validation;
|
||||||
|
import org.oneedtech.inspect.vc.Assertion.ValueType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for ValidationPropertyProbes
|
||||||
|
* @author xaracil
|
||||||
|
*/
|
||||||
|
public class ValidationPropertyProbeFactory {
|
||||||
|
public static ValidationPropertyProbe of(Validation validation) {
|
||||||
|
return of(validation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValidationPropertyProbe of(Validation validation, boolean fullValidate) {
|
||||||
|
checkNotNull(validation.getType());
|
||||||
|
if (validation.getType() == ValueType.RDF_TYPE) {
|
||||||
|
return new ValidationRdfTypePropertyProbe(validation, fullValidate);
|
||||||
|
}
|
||||||
|
return new ValidationPropertyProbe(validation, fullValidate);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package org.oneedtech.inspect.vc.probe;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.oneedtech.inspect.core.probe.Outcome;
|
||||||
|
import org.oneedtech.inspect.core.probe.RunContext;
|
||||||
|
import org.oneedtech.inspect.core.report.ReportItems;
|
||||||
|
import org.oneedtech.inspect.vc.Validation;
|
||||||
|
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
|
|
||||||
|
public class ValidationRdfTypePropertyProbe extends ValidationPropertyProbe {
|
||||||
|
public ValidationRdfTypePropertyProbe(Validation validation) {
|
||||||
|
super(validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationRdfTypePropertyProbe(Validation validation, boolean fullValidate) {
|
||||||
|
super(validation, fullValidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReportItems reportForNonExistentProperty(JsonNode node, RunContext ctx) {
|
||||||
|
if (!validation.isRequired()) {
|
||||||
|
// check if we have a default type
|
||||||
|
if (validation.getDefaultType() != null) {
|
||||||
|
JsonNodeFactory factory = JsonNodeFactory.instance;
|
||||||
|
TextNode textNode = factory.textNode(validation.getDefaultType());
|
||||||
|
// validate with default value
|
||||||
|
return validate(textNode, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error("Required property " + validation.getName() + " not present in " + node.toPrettyString(), ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReportItems validate(JsonNode node, RunContext ctx) {
|
||||||
|
ReportItems result = super.validate(node, ctx);
|
||||||
|
if (result.contains(Outcome.ERROR, Outcome.FATAL)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (!validation.getMustContainOne().isEmpty()) {
|
||||||
|
List<String> values = JsonNodeUtil.asStringList(node);
|
||||||
|
boolean valid = validation.getMustContainOne().stream().anyMatch(type -> values.contains(type));
|
||||||
|
if (!valid) {
|
||||||
|
return new ReportItems(List.of(result,
|
||||||
|
fatal("Node " + validation.getName() + " of type " + node.asText()
|
||||||
|
+ " does not have type among allowed values (" + validation.getMustContainOne().stream().collect(Collectors.joining(",")) + ")", ctx)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ReportItems(List.of(result, success(ctx)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user