From 7cebb1ef5d43b001c61f42eb6ed54d6b439e29b6 Mon Sep 17 00:00:00 2001 From: Stefano Ortolani Date: Thu, 4 Apr 2024 19:47:38 +0100 Subject: [PATCH] Add support to relative custom schemas --- stix2validator/test/v21/misc_tests.py | 13 ++++++++++ .../test/v21/test_examples/tool.json | 12 +++++++++ stix2validator/test/v21/test_schemas/bar.json | 7 ++++++ .../test/v21/test_schemas/tool.json | 15 +++++++++++ stix2validator/validator.py | 25 +++++++++++++------ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 stix2validator/test/v21/test_examples/tool.json create mode 100644 stix2validator/test/v21/test_schemas/bar.json create mode 100644 stix2validator/test/v21/test_schemas/tool.json diff --git a/stix2validator/test/v21/misc_tests.py b/stix2validator/test/v21/misc_tests.py index 1bf1aee..35a659f 100644 --- a/stix2validator/test/v21/misc_tests.py +++ b/stix2validator/test/v21/misc_tests.py @@ -20,6 +20,10 @@ 'test_examples', 'tlp-amber.json') CUSTOM_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_schemas') +RELATIVE = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'test_examples', 'tool.json') +RELATIVE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'test_schemas') IDENTITY = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_examples', 'identity.json') IDENTITY_CUSTOM = os.path.join(os.path.dirname(os.path.realpath(__file__)), @@ -76,6 +80,15 @@ def test_validate_file_custom(caplog): assert 'STIX JSON: Valid' in caplog.text +def test_validate_file_custom_relative(caplog): + caplog.set_level('INFO') + results = validate_file(RELATIVE, options=ValidationOptions(schema_dir=RELATIVE_DIR)) + assert results.is_valid + + print_results(results) + assert 'STIX JSON: Valid' in caplog.text + + def test_validate_file_warning(caplog): results = validate_file(IDENTITY_CUSTOM) assert results.is_valid diff --git a/stix2validator/test/v21/test_examples/tool.json b/stix2validator/test/v21/test_examples/tool.json new file mode 100644 index 0000000..8ab167a --- /dev/null +++ b/stix2validator/test/v21/test_examples/tool.json @@ -0,0 +1,12 @@ +{ + "type": "tool", + "spec_version": "2.1", + "id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2016-04-06T20:03:48.000Z", + "modified": "2016-04-06T20:03:48.000Z", + "tool_types": [ "remote-access"], + "name": "VNC", + "foo_value": "bizz", + "bar_value": "buzz" +} diff --git a/stix2validator/test/v21/test_schemas/bar.json b/stix2validator/test/v21/test_schemas/bar.json new file mode 100644 index 0000000..1c7d57f --- /dev/null +++ b/stix2validator/test/v21/test_schemas/bar.json @@ -0,0 +1,7 @@ +{ + "properties": { + "bar_value": { + "type": "string" + } + } +} diff --git a/stix2validator/test/v21/test_schemas/tool.json b/stix2validator/test/v21/test_schemas/tool.json new file mode 100644 index 0000000..01672d6 --- /dev/null +++ b/stix2validator/test/v21/test_schemas/tool.json @@ -0,0 +1,15 @@ +{ + "allOf": [ + { + "properties": { + "foo_value": { + "type": "string" + } + }, + "required": ["foo_value"] + }, + { + "$ref": "bar.json" + } + ] +} diff --git a/stix2validator/validator.py b/stix2validator/validator.py index c3ef1dc..b8c0e2b 100644 --- a/stix2validator/validator.py +++ b/stix2validator/validator.py @@ -3,10 +3,10 @@ from collections.abc import Iterable import copy +import functools import io from itertools import chain import os -import pathlib import re import sys from urllib import parse, request @@ -553,11 +553,12 @@ def patch_schema(schema_data: dict, schema_path: str) -> dict: return schema_data -def retrieve_from_filesystem(schema_path_uri: str) -> Resource: +def retrieve_from_filesystem(schema_path_uri: str, schema_dir: str) -> Resource: """Callback to retrieve a schema given its path. Args: schema_path_uri: the schema URI. + schema_dir: the optional directory of local schemas. Returns: A resource loaded with the content. @@ -568,14 +569,22 @@ def retrieve_from_filesystem(schema_path_uri: str) -> Resource: schema = patch_schema(schema, schema_path_uri) return Resource.from_contents(schema) else: - schema_path = from_uri_to_path(schema_path_uri) + if os.path.isabs(schema_path_uri) or schema_path_uri.startswith("file://"): + is_relative = False + schema_path = from_uri_to_path(schema_path_uri) + else: + is_relative = True + schema_path = from_uri_to_path(os.path.join(schema_dir, schema_path_uri)) with open(schema_path, "r") as f: schema = json.load(f) schema = patch_schema(schema, schema_path) - return Resource.from_contents(schema) + if is_relative: + return Resource.opaque(schema) + else: + return Resource.from_contents(schema) -def load_validator(schema_path, schema): +def load_validator(schema_path, schema, schema_dir): """Create a JSON schema validator for the given schema. Args: @@ -585,10 +594,10 @@ def load_validator(schema_path, schema): Returns: An instance of Draft202012Validator. """ - schema_path = pathlib.Path(schema_path).as_uri() schema = patch_schema(schema, schema_path) + retrieve_callback = functools.partial(retrieve_from_filesystem, schema_dir=schema_dir) registry = Registry( - retrieve=retrieve_from_filesystem + retrieve=retrieve_callback ).with_resource(schema_path, DRAFT202012.create_resource(schema)) validator = STIXValidator(schema, registry=registry, format_checker=Draft202012Validator.FORMAT_CHECKER) return validator @@ -694,7 +703,7 @@ def _get_error_generator(name, obj, schema_dir=None, version=DEFAULT_VER, defaul } # Don't use custom validator; only check schemas, no additional checks - validator = load_validator(schema_path, schema) + validator = load_validator(schema_path, schema, schema_dir) try: error_gen = validator.iter_errors(obj) except schema_exceptions.RefResolutionError: