Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Fixed
~~~~~

- Annotate PyJWKSet.keys for pyright by @tamird in `#1134 <https://github.com/jpadilla/pyjwt/pull/1134>`__
- Close ``HTTPError`` response to prevent ``ResourceWarning`` on Python 3.14 by @veeceey in `#1133 <https://github.com/jpadilla/pyjwt/pull/1133>`__

Added
~~~~~
Expand Down
4 changes: 3 additions & 1 deletion jwt/jwks_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from functools import lru_cache
from ssl import SSLContext
from typing import Any
from urllib.error import URLError
from urllib.error import HTTPError, URLError

from .api_jwk import PyJWK, PyJWKSet
from .api_jwt import decode_complete as decode_token
Expand Down Expand Up @@ -110,6 +110,8 @@ def fetch_data(self) -> Any:
) as response:
jwk_set = json.load(response)
except (URLError, TimeoutError) as e:
if isinstance(e, HTTPError):
e.close()
raise PyJWKClientConnectionError(
f'Fail to fetch data from the url, err: "{e}"'
) from e
Expand Down
27 changes: 26 additions & 1 deletion tests/test_jwks_client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import contextlib
import io
import json
import ssl
import time
from unittest import mock
from urllib.error import URLError
from urllib.error import HTTPError, URLError

import pytest

Expand Down Expand Up @@ -86,6 +87,20 @@ def mocked_timeout():
yield urlopen_mock


@contextlib.contextmanager
def mocked_http_error_response():
with mock.patch("urllib.request.urlopen") as urlopen_mock:
http_error = HTTPError(
url="https://example.com",
code=401,
msg="Unauthorized",
hdrs=None,
fp=io.BytesIO(b""),
)
urlopen_mock.side_effect = http_error
yield urlopen_mock, http_error


@crypto_required
class TestPyJWKClient:
def test_fetch_data_forwards_headers_to_correct_url(self):
Expand Down Expand Up @@ -361,3 +376,13 @@ def test_get_jwt_set_sslcontext_no_ca(self):
)
with pytest.raises(PyJWKClientError):
jwks_client.get_jwk_set()

def test_http_error_is_closed_on_connection_failure(self):
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)

with mocked_http_error_response() as (_, http_error):
with pytest.raises(PyJWKClientConnectionError):
jwks_client.get_jwk_set()

assert http_error.closed