summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2023-04-25 16:21:15 -0400
committerJulian Berman <Julian@GrayVines.com>2023-04-25 16:21:15 -0400
commitdc683c3105216f0c3fbfba78815b97f510e434c8 (patch)
tree5552c78b536553563c89d32c709672c20d3fd80b
parent29ad460fb37072200ce019ae4dce4d899350527f (diff)
downloadjsonschema-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.rst4
-rw-r--r--jsonschema/tests/test_deprecations.py6
-rw-r--r--jsonschema/validators.py34
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,
)