Skip to content
Open
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
6 changes: 0 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ repos:
- id: check-manifest
args: [--no-build-isolation]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.18.2"
hooks:
- id: mypy
additional_dependencies: [cryptography>=3.4.0]

- repo: https://github.com/abravalheri/validate-pyproject
rev: "v0.24.1"
hooks:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Fixed
- Improve typing/logic for `options` in decode, decode_complete by @pachewise in `#1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__
- Declare float supported type for lifespan and timeout by @nikitagashkov in `#1068 <https://github.com/jpadilla/pyjwt/pull/1068>`__
- Fix ``SyntaxWarning``\s/``DeprecationWarning``\s caused by invalid escape sequences by @kurtmckee in `#1103 <https://github.com/jpadilla/pyjwt/pull/1103>`__
- Development: Test type annotations across all supported Python versions,
increase the strictness of the type checking, and remove the mypy pre-commit hook
by @kurtmckee in `#1112 <https://github.com/jpadilla/pyjwt/pull/1112>`__

Added
~~~~~
Expand Down
67 changes: 43 additions & 24 deletions jwt/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
import json
import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, cast, overload
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Literal,
NoReturn,
Union,
cast,
overload,
)

from .exceptions import InvalidKeyError
from .types import HashlibHash, JWKDict
Expand Down Expand Up @@ -93,31 +102,36 @@
)

if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")):
from typing import TypeAlias
import sys

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
# Python 3.9 and lower
from typing_extensions import TypeAlias

from cryptography.hazmat.primitives.asymmetric.types import (
PrivateKeyTypes,
PublicKeyTypes,
)

# Type aliases for convenience in algorithms method signatures
AllowedRSAKeys: TypeAlias = RSAPrivateKey | RSAPublicKey
AllowedECKeys: TypeAlias = EllipticCurvePrivateKey | EllipticCurvePublicKey
AllowedOKPKeys: TypeAlias = (
Ed25519PrivateKey | Ed25519PublicKey | Ed448PrivateKey | Ed448PublicKey
)
AllowedKeys: TypeAlias = AllowedRSAKeys | AllowedECKeys | AllowedOKPKeys
AllowedRSAKeys: TypeAlias = Union[RSAPrivateKey, RSAPublicKey]
AllowedECKeys: TypeAlias = Union[
EllipticCurvePrivateKey, EllipticCurvePublicKey
]
AllowedOKPKeys: TypeAlias = Union[
Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey
]
AllowedKeys: TypeAlias = Union[AllowedRSAKeys, AllowedECKeys, AllowedOKPKeys]
#: Type alias for allowed ``cryptography`` private keys (requires ``cryptography`` to be installed)
AllowedPrivateKeys: TypeAlias = (
RSAPrivateKey
| EllipticCurvePrivateKey
| Ed25519PrivateKey
| Ed448PrivateKey
)
AllowedPrivateKeys: TypeAlias = Union[
RSAPrivateKey, EllipticCurvePrivateKey, Ed25519PrivateKey, Ed448PrivateKey
]
#: Type alias for allowed ``cryptography`` public keys (requires ``cryptography`` to be installed)
AllowedPublicKeys: TypeAlias = (
RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey | Ed448PublicKey
)
AllowedPublicKeys: TypeAlias = Union[
RSAPublicKey, EllipticCurvePublicKey, Ed25519PublicKey, Ed448PublicKey
]

has_crypto = True
except ModuleNotFoundError:
Expand Down Expand Up @@ -204,7 +218,7 @@ def compute_hash_digest(self, bytestr: bytes) -> bytes:
else:
return bytes(hash_alg(bytestr).digest())

def check_crypto_key_type(self, key: PublicKeyTypes | PrivateKeyTypes):
def check_crypto_key_type(self, key: PublicKeyTypes | PrivateKeyTypes) -> None:
"""Check that the key belongs to the right cryptographic family.

Note that this method only works when ``cryptography`` is installed.
Expand Down Expand Up @@ -251,16 +265,18 @@ def verify(self, msg: bytes, key: Any, sig: bytes) -> bool:
@overload
@staticmethod
@abstractmethod
def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: ... # pragma: no cover
def to_jwk(key_obj: Any, as_dict: Literal[True]) -> JWKDict: ... # pragma: no cover

@overload
@staticmethod
@abstractmethod
def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: ... # pragma: no cover
def to_jwk(
key_obj: Any, as_dict: Literal[False] = False
) -> str: ... # pragma: no cover

@staticmethod
@abstractmethod
def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str:
def to_jwk(key_obj: Any, as_dict: bool = False) -> JWKDict | str:
"""
Serializes a given key into a JWK
"""
Expand Down Expand Up @@ -546,7 +562,8 @@ def from_jwk(jwk: str | JWKDict) -> AllowedRSAKeys:
raise InvalidKeyError("Not a public or private key")

def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes:
return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
signature: bytes = key.sign(msg, padding.PKCS1v15(), self.hash_alg())
return signature

def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool:
try:
Expand Down Expand Up @@ -754,14 +771,15 @@ class RSAPSSAlgorithm(RSAAlgorithm):
"""

def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes:
return key.sign(
signature: bytes = key.sign(
msg,
padding.PSS(
mgf=padding.MGF1(self.hash_alg()),
salt_length=self.hash_alg().digest_size,
),
self.hash_alg(),
)
return signature

def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool:
try:
Expand Down Expand Up @@ -823,7 +841,8 @@ def sign(
:return bytes signature: The signature, as bytes
"""
msg_bytes = msg.encode("utf-8") if isinstance(msg, str) else msg
return key.sign(msg_bytes)
signature: bytes = key.sign(msg_bytes)
return signature

def verify(
self, msg: str | bytes, key: AllowedOKPKeys, sig: str | bytes
Expand Down
20 changes: 13 additions & 7 deletions jwt/api_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from calendar import timegm
from collections.abc import Iterable, Sequence
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Any, Container
from typing import TYPE_CHECKING, Any, Container, Union, cast

from . import api_jws
from .exceptions import (
Expand All @@ -23,7 +23,13 @@
from .warnings import RemovedInPyjwt3Warning

if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")):
from typing import TypeAlias
import sys

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
# Python 3.9 and lower
from typing_extensions import TypeAlias

from .algorithms import has_crypto
from .api_jwk import PyJWK
Expand All @@ -32,11 +38,11 @@
if has_crypto:
from .algorithms import AllowedPrivateKeys, AllowedPublicKeys

AllowedPrivateKeyTypes: TypeAlias = AllowedPrivateKeys | PyJWK | str | bytes # type: ignore
AllowedPublicKeyTypes: TypeAlias = AllowedPublicKeys | PyJWK | str | bytes # type: ignore
AllowedPrivateKeyTypes: TypeAlias = Union[AllowedPrivateKeys, PyJWK, str, bytes]
AllowedPublicKeyTypes: TypeAlias = Union[AllowedPublicKeys, PyJWK, str, bytes]
else:
AllowedPrivateKeyTypes: TypeAlias = PyJWK | str | bytes # type: ignore
AllowedPublicKeyTypes: TypeAlias = PyJWK | str | bytes # type: ignore
AllowedPrivateKeyTypes: TypeAlias = Union[PyJWK, str, bytes] # type: ignore
AllowedPublicKeyTypes: TypeAlias = Union[PyJWK, str, bytes] # type: ignore


class PyJWT:
Expand Down Expand Up @@ -360,7 +366,7 @@ def decode(
issuer=issuer,
leeway=leeway,
)
return decoded["payload"]
return cast(dict[str, Any], decoded["payload"])

def _validate_claims(
self,
Expand Down
5 changes: 2 additions & 3 deletions jwt/jwks_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ def __init__(

if cache_keys:
# Cache signing keys
get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key)
# Ignore mypy (https://github.com/python/mypy/issues/2427)
self.get_signing_key = lru_cache(maxsize=max_cached_keys)(
self.get_signing_key
) # type: ignore
self.get_signing_key = get_signing_key # type: ignore[method-assign]

def fetch_data(self) -> Any:
jwk_set: Any = None
Expand Down
14 changes: 2 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,10 @@ combine_as_imports = true
profile = "black"

[tool.mypy]
allow_incomplete_defs = true
allow_untyped_defs = true
disable_error_code = [
"method-assign",
"unused-ignore",
]
packages = "jwt"
ignore_missing_imports = true
no_implicit_optional = true
overrides = [
{ disallow_untyped_calls = false, module = "tests.*" },
]
python_version = 3.11
strict = true
warn_return_any = false
warn_return_any = true
warn_unused_ignores = true

[tool.setuptools]
Expand Down
13 changes: 13 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ envlist =
lint
typing
py{39,310,311,312,313,314,py39,py310,py311}-{crypto,nocrypto}
py{39,310,311,312,313,314}{,-crypto}-mypy
docs
pypi-description
coverage-report
isolated_build = True
labels =
mypy = py{39,310,311,312,313,314}{,-crypto}-mypy


[testenv]
Expand All @@ -57,6 +60,16 @@ commands =
python -m doctest README.rst docs/usage.rst


[testenv:py{39,310,311,312,313,314}{,-crypto}-mypy]
extras =
crypto: crypto
deps =
mypy
set_env =
MYPY_FORCE_COLOR=1
commands =
mypy

[testenv:lint]
basepython = python3.9
extras = dev
Expand Down
Loading