diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 30a57b67..71dffc6a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +v4.18.6 +======= + +* Set a ``jsonschema`` specific user agent when automatically retrieving remote references (which is deprecated). + v4.18.5 ======= diff --git a/jsonschema/tests/test_deprecations.py b/jsonschema/tests/test_deprecations.py index de2279c2..aa108c05 100644 --- a/jsonschema/tests/test_deprecations.py +++ b/jsonschema/tests/test_deprecations.py @@ -1,7 +1,11 @@ -from unittest import TestCase -import importlib +from contextlib import contextmanager +from io import BytesIO +from unittest import TestCase, mock +import importlib.metadata +import json import subprocess import sys +import urllib.request import referencing.exceptions @@ -16,8 +20,9 @@ def test_version(self): message = "Accessing jsonschema.__version__ is deprecated" with self.assertWarnsRegex(DeprecationWarning, message) as w: - from jsonschema import __version__ # noqa: F401 + from jsonschema import __version__ + self.assertEqual(__version__, importlib.metadata.version("jsonschema")) self.assertEqual(w.filename, __file__) def test_validators_ErrorTree(self): @@ -357,3 +362,44 @@ def test_cli(self): capture_output=True, ) self.assertIn(b"The jsonschema CLI is deprecated ", process.stderr) + + def test_automatic_remote_retrieval(self): + """ + Automatic retrieval of remote references is deprecated as of v4.18.0. + """ + ref = "http://bar#/$defs/baz" + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": {"baz": {"type": "integer"}}, + } + + if "requests" in sys.modules: # pragma: no cover + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = None + + @contextmanager + def fake_urlopen(request): + self.assertIsInstance(request, urllib.request.Request) + self.assertEqual(request.full_url, "http://bar") + + # Ha ha urllib.request.Request "normalizes" header names and + # Request.get_header does not also normalize them... + (header, value), = request.header_items() + self.assertEqual(header.lower(), "user-agent") + self.assertEqual( + value, "python-jsonschema (deprecated $ref resolution)", + ) + yield BytesIO(json.dumps(schema).encode("utf8")) + + validator = validators.Draft202012Validator({"$ref": ref}) + + message = "Automatically retrieving remote references " + patch = mock.patch.object(urllib.request, "urlopen", new=fake_urlopen) + + with patch, self.assertWarnsRegex(DeprecationWarning, message): + self.assertEqual( + (validator.is_valid({}), validator.is_valid(37)), + (False, True), + ) diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 850a1de6..0ba8adb0 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -103,8 +103,10 @@ def _validates(cls): def _warn_for_remote_retrieve(uri: str): - from urllib.request import urlopen - with urlopen(uri) as response: + from urllib.request import Request, urlopen + headers = {"User-Agent": "python-jsonschema (deprecated $ref resolution)"} + request = Request(uri, headers=headers) + with urlopen(request) as response: warnings.warn( "Automatically retrieving remote references can be a security " "vulnerability and is discouraged by the JSON Schema "