diff options
author | Julian Berman <Julian@GrayVines.com> | 2023-04-25 16:21:15 -0400 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2023-04-25 16:21:15 -0400 |
commit | dc683c3105216f0c3fbfba78815b97f510e434c8 (patch) | |
tree | 5552c78b536553563c89d32c709672c20d3fd80b | |
parent | 29ad460fb37072200ce019ae4dce4d899350527f (diff) | |
download | jsonschema-dc683c3105216f0c3fbfba78815b97f510e434c8.tar.gz |
Re-enable (but deprecate) automatic reference retrieval.
Changing this without deprecation is backwards incompatible, so we
re-introduce a warning.
This only applies if you have not configured neither a Registry nor a legacy
RefResolver. Both of the former 2 cases already have 'correct' behavior (the
former will not automatically retrieve references and is not backwards
incompatible as it is a new API, and the latter will do so but is already
fully deprecated by this release).
Cloess: #1089
-rw-r--r-- | CHANGELOG.rst | 4 | ||||
-rw-r--r-- | jsonschema/tests/test_deprecations.py | 6 | ||||
-rw-r--r-- | jsonschema/validators.py | 34 |
3 files changed, 37 insertions, 7 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 10372e8..da49bc3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ It does so in a way that *should* be backwards compatible, preserving old behavi This change is a culmination of a meaningful chunk of work to make ``$ref`` resolution more flexible and more correct. Backwards compatibility *should* be preserved for existing code which uses ``RefResolver``, though doing so is again now deprecated, and all such use cases should be doable using the new APIs. Please file issues on the ``referencing`` tracker if there is functionality missing from it, or here on the ``jsonschema`` issue tracker if you have issues with existing code not functioning the same, or with figuring out how to change it to use ``referencing``. + In particular, this referencing change includes a change concerning *automatic* retrieval of remote references (retrieving ``http://foo/bar`` automatically within a schema). + This behavior has always been a potential security risk and counter to the recommendations of the JSON Schema specifications; it has survived this long essentially only for backwards compatibility reasons, and now explicitly produces warnings. + The ``referencing`` library itself will *not* automatically retrieve references if you interact directly with it, so the deprecated behavior is only triggered if you fully rely on the default ``$ref`` resolution behavior and also include remote references in your schema, which will still be retrieved during the deprecation period (after which they will become an error). * Support for Python 3.7 has been dropped, as it is nearing end-of-life. This should not be a "visible" change in the sense that ``requires-python`` has been updated, so users using 3.7 should still receive ``v4.17.3`` when installing the library. * On draft 2019-09, ``unevaluatedItems`` now properly does *not* consider items to be evaluated by an ``additionalItems`` schema if ``items`` is missing from the schema, as the specification says in this case that ``additionalItems`` must be completely ignored. @@ -20,6 +23,7 @@ Deprecations * ``jsonschema.RefResolver`` -- see above for details on the replacement * ``jsonschema.RefResolutionError`` -- see above for details on the replacement +* relying on automatic resolution of remote references -- see above for details on the replacement * importing ``jsonschema.ErrorTree`` -- instead import it via ``jsonschema.exceptions.ErrorTree`` * importing ``jsonschema.FormatError`` -- instead import it via ``jsonschema.exceptions.FormatError`` diff --git a/jsonschema/tests/test_deprecations.py b/jsonschema/tests/test_deprecations.py index cf5538f..1b19736 100644 --- a/jsonschema/tests/test_deprecations.py +++ b/jsonschema/tests/test_deprecations.py @@ -178,12 +178,12 @@ class TestDeprecations(TestCase): multiple inheritance subclass, we need to be extra sure it works and stays working. """ - validator = validators.Draft202012Validator({"$ref": "http://foo.com"}) + validator = validators.Draft202012Validator({"$ref": "urn:nothing"}) with self.assertRaises(referencing.exceptions.Unresolvable) as e: validator.validate(12) - expected = referencing.exceptions.Unresolvable(ref="http://foo.com") + expected = referencing.exceptions.Unresolvable(ref="urn:nothing") self.assertEqual(e.exception, expected) def test_catching_Unresolvable_via_RefResolutionError(self): @@ -195,7 +195,7 @@ class TestDeprecations(TestCase): with self.assertWarns(DeprecationWarning): from jsonschema import RefResolutionError - validator = validators.Draft202012Validator({"$ref": "http://foo.com"}) + validator = validators.Draft202012Validator({"$ref": "urn:nothing"}) with self.assertRaises(referencing.exceptions.Unresolvable): validator.validate(12) diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 78e5829..5b16fdc 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -17,7 +17,6 @@ import warnings from attrs import define, field, fields from jsonschema_specifications import REGISTRY as SPECIFICATIONS -from referencing import Specification from rpds import HashTrieMap import referencing.exceptions import referencing.jsonschema @@ -103,6 +102,29 @@ def validates(version): return _validates +def _warn_for_remote_retrieve(uri: str): + from urllib.request import urlopen + with urlopen(uri) as response: + warnings.warn( + "Automatically retrieving remote references can be a security " + "vulnerability and is discouraged by the JSON Schema " + "specifications. Relying on this behavior is deprecated " + "and will shortly become an error. If you are sure you want to " + "remotely retrieve your reference and that it is safe to do so, " + "you can find instructions for doing so via referencing.Registry " + "in the referencing documentation " + "(https://referencing.readthedocs.org).", + DeprecationWarning, + stacklevel=9, # Ha ha ha ha magic numbers :/ + ) + return referencing.Resource.from_contents(json.load(response)) + + +_DEFAULT_REGISTRY = SPECIFICATIONS.combine( + referencing.Registry(retrieve=_warn_for_remote_retrieve), +) + + def create( meta_schema: referencing.jsonschema.ObjectSchema, validators: ( @@ -185,7 +207,7 @@ def create( specification = referencing.jsonschema.specification_with( dialect_id=id_of(meta_schema) or "urn:unknown-dialect", - default=Specification.OPAQUE, + default=referencing.Specification.OPAQUE, ) @define @@ -202,8 +224,12 @@ def create( format_checker: _format.FormatChecker | None = field(default=None) # TODO: include new meta-schemas added at runtime _registry: referencing.jsonschema.SchemaRegistry = field( - default=SPECIFICATIONS, - converter=SPECIFICATIONS.combine, # type: ignore[misc] + default=_DEFAULT_REGISTRY, + converter=lambda value: ( + _DEFAULT_REGISTRY + if value is _DEFAULT_REGISTRY + else SPECIFICATIONS.combine(value) + ), kw_only=True, repr=False, ) |