Skip to content

Commit dc683c3

Browse files
committed
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
1 parent 29ad460 commit dc683c3

File tree

3 files changed

+37
-7
lines changed

3 files changed

+37
-7
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ It does so in a way that *should* be backwards compatible, preserving old behavi
99
This change is a culmination of a meaningful chunk of work to make ``$ref`` resolution more flexible and more correct.
1010
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.
1111
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``.
12+
In particular, this referencing change includes a change concerning *automatic* retrieval of remote references (retrieving ``http://foo/bar`` automatically within a schema).
13+
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.
14+
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).
1215
* Support for Python 3.7 has been dropped, as it is nearing end-of-life.
1316
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.
1417
* 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
2023

2124
* ``jsonschema.RefResolver`` -- see above for details on the replacement
2225
* ``jsonschema.RefResolutionError`` -- see above for details on the replacement
26+
* relying on automatic resolution of remote references -- see above for details on the replacement
2327
* importing ``jsonschema.ErrorTree`` -- instead import it via ``jsonschema.exceptions.ErrorTree``
2428
* importing ``jsonschema.FormatError`` -- instead import it via ``jsonschema.exceptions.FormatError``
2529

jsonschema/tests/test_deprecations.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ def test_catching_Unresolvable_directly(self):
178178
multiple inheritance subclass, we need to be extra sure it works and
179179
stays working.
180180
"""
181-
validator = validators.Draft202012Validator({"$ref": "http://foo.com"})
181+
validator = validators.Draft202012Validator({"$ref": "urn:nothing"})
182182

183183
with self.assertRaises(referencing.exceptions.Unresolvable) as e:
184184
validator.validate(12)
185185

186-
expected = referencing.exceptions.Unresolvable(ref="http://foo.com")
186+
expected = referencing.exceptions.Unresolvable(ref="urn:nothing")
187187
self.assertEqual(e.exception, expected)
188188

189189
def test_catching_Unresolvable_via_RefResolutionError(self):
@@ -195,7 +195,7 @@ def test_catching_Unresolvable_via_RefResolutionError(self):
195195
with self.assertWarns(DeprecationWarning):
196196
from jsonschema import RefResolutionError
197197

198-
validator = validators.Draft202012Validator({"$ref": "http://foo.com"})
198+
validator = validators.Draft202012Validator({"$ref": "urn:nothing"})
199199

200200
with self.assertRaises(referencing.exceptions.Unresolvable):
201201
validator.validate(12)

jsonschema/validators.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
from attrs import define, field, fields
1919
from jsonschema_specifications import REGISTRY as SPECIFICATIONS
20-
from referencing import Specification
2120
from rpds import HashTrieMap
2221
import referencing.exceptions
2322
import referencing.jsonschema
@@ -103,6 +102,29 @@ def _validates(cls):
103102
return _validates
104103

105104

105+
def _warn_for_remote_retrieve(uri: str):
106+
from urllib.request import urlopen
107+
with urlopen(uri) as response:
108+
warnings.warn(
109+
"Automatically retrieving remote references can be a security "
110+
"vulnerability and is discouraged by the JSON Schema "
111+
"specifications. Relying on this behavior is deprecated "
112+
"and will shortly become an error. If you are sure you want to "
113+
"remotely retrieve your reference and that it is safe to do so, "
114+
"you can find instructions for doing so via referencing.Registry "
115+
"in the referencing documentation "
116+
"(https://referencing.readthedocs.org).",
117+
DeprecationWarning,
118+
stacklevel=9, # Ha ha ha ha magic numbers :/
119+
)
120+
return referencing.Resource.from_contents(json.load(response))
121+
122+
123+
_DEFAULT_REGISTRY = SPECIFICATIONS.combine(
124+
referencing.Registry(retrieve=_warn_for_remote_retrieve),
125+
)
126+
127+
106128
def create(
107129
meta_schema: referencing.jsonschema.ObjectSchema,
108130
validators: (
@@ -185,7 +207,7 @@ def create(
185207

186208
specification = referencing.jsonschema.specification_with(
187209
dialect_id=id_of(meta_schema) or "urn:unknown-dialect",
188-
default=Specification.OPAQUE,
210+
default=referencing.Specification.OPAQUE,
189211
)
190212

191213
@define
@@ -202,8 +224,12 @@ class Validator:
202224
format_checker: _format.FormatChecker | None = field(default=None)
203225
# TODO: include new meta-schemas added at runtime
204226
_registry: referencing.jsonschema.SchemaRegistry = field(
205-
default=SPECIFICATIONS,
206-
converter=SPECIFICATIONS.combine, # type: ignore[misc]
227+
default=_DEFAULT_REGISTRY,
228+
converter=lambda value: (
229+
_DEFAULT_REGISTRY
230+
if value is _DEFAULT_REGISTRY
231+
else SPECIFICATIONS.combine(value)
232+
),
207233
kw_only=True,
208234
repr=False,
209235
)

0 commit comments

Comments
 (0)