Update PyJWT-2.2.0

This commit is contained in:
JonnyWong16 2021-10-14 21:40:57 -07:00
parent b55b053b1e
commit 4eb0fea423
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
15 changed files with 1143 additions and 641 deletions

View file

@ -1,187 +1,224 @@
import json
import warnings
from calendar import timegm
from collections import Mapping
from datetime import datetime, timedelta
from collections.abc import Iterable, Mapping
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional, Type, Union
from .api_jws import PyJWS
from .algorithms import Algorithm, get_default_algorithms # NOQA
from .compat import string_types, timedelta_total_seconds
from . import api_jws
from .exceptions import (
DecodeError, ExpiredSignatureError, ImmatureSignatureError,
InvalidAudienceError, InvalidIssuedAtError,
InvalidIssuerError, MissingRequiredClaimError
DecodeError,
ExpiredSignatureError,
ImmatureSignatureError,
InvalidAudienceError,
InvalidIssuedAtError,
InvalidIssuerError,
MissingRequiredClaimError,
)
from .utils import merge_dict
class PyJWT(PyJWS):
header_type = 'JWT'
class PyJWT:
def __init__(self, options=None):
if options is None:
options = {}
self.options = {**self._get_default_options(), **options}
@staticmethod
def _get_default_options():
def _get_default_options() -> Dict[str, Union[bool, List[str]]]:
return {
'verify_signature': True,
'verify_exp': True,
'verify_nbf': True,
'verify_iat': True,
'verify_aud': True,
'verify_iss': True,
'require_exp': False,
'require_iat': False,
'require_nbf': False
"verify_signature": True,
"verify_exp": True,
"verify_nbf": True,
"verify_iat": True,
"verify_aud": True,
"verify_iss": True,
"require": [],
}
def encode(self, payload, key, algorithm='HS256', headers=None,
json_encoder=None):
def encode(
self,
payload: Dict[str, Any],
key: str,
algorithm: Optional[str] = "HS256",
headers: Optional[Dict] = None,
json_encoder: Optional[Type[json.JSONEncoder]] = None,
) -> str:
# Check that we get a mapping
if not isinstance(payload, Mapping):
raise TypeError('Expecting a mapping object, as JWT only supports '
'JSON objects as payloads.')
raise TypeError(
"Expecting a mapping object, as JWT only supports "
"JSON objects as payloads."
)
# Payload
for time_claim in ['exp', 'iat', 'nbf']:
payload = payload.copy()
for time_claim in ["exp", "iat", "nbf"]:
# Convert datetime to a intDate value in known time-format claims
if isinstance(payload.get(time_claim), datetime):
payload[time_claim] = timegm(payload[time_claim].utctimetuple())
json_payload = json.dumps(
payload,
separators=(',', ':'),
cls=json_encoder
).encode('utf-8')
payload, separators=(",", ":"), cls=json_encoder
).encode("utf-8")
return super(PyJWT, self).encode(
json_payload, key, algorithm, headers, json_encoder
return api_jws.encode(json_payload, key, algorithm, headers, json_encoder)
def decode_complete(
self,
jwt: str,
key: str = "",
algorithms: List[str] = None,
options: Dict = None,
audience: Optional[Union[str, List[str]]] = None,
issuer: Optional[str] = None,
leeway: Union[float, timedelta] = 0,
) -> Dict[str, Any]:
if options is None:
options = {"verify_signature": True}
else:
options.setdefault("verify_signature", True)
if not options["verify_signature"]:
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
if options["verify_signature"] and not algorithms:
raise DecodeError(
'It is required that you pass in a value for the "algorithms" argument when calling decode().'
)
decoded = api_jws.decode_complete(
jwt,
key=key,
algorithms=algorithms,
options=options,
)
def decode(self, jwt, key='', verify=True, algorithms=None, options=None,
**kwargs):
payload, signing_input, header, signature = self._load(jwt)
decoded = super(PyJWT, self).decode(jwt, key, verify, algorithms,
options, **kwargs)
try:
payload = json.loads(decoded.decode('utf-8'))
payload = json.loads(decoded["payload"])
except ValueError as e:
raise DecodeError('Invalid payload string: %s' % e)
if not isinstance(payload, Mapping):
raise DecodeError('Invalid payload string: must be a json object')
raise DecodeError("Invalid payload string: %s" % e)
if not isinstance(payload, dict):
raise DecodeError("Invalid payload string: must be a json object")
if verify:
merged_options = merge_dict(self.options, options)
self._validate_claims(payload, merged_options, **kwargs)
merged_options = {**self.options, **options}
self._validate_claims(payload, merged_options, audience, issuer, leeway)
return payload
decoded["payload"] = payload
return decoded
def _validate_claims(self, payload, options, audience=None, issuer=None,
leeway=0, **kwargs):
if 'verify_expiration' in kwargs:
options['verify_exp'] = kwargs.get('verify_expiration', True)
warnings.warn('The verify_expiration parameter is deprecated. '
'Please use options instead.', DeprecationWarning)
def decode(
self,
jwt: str,
key: str = "",
algorithms: List[str] = None,
options: Dict = None,
audience: Optional[Union[str, List[str]]] = None,
issuer: Optional[str] = None,
leeway: Union[float, timedelta] = 0,
) -> Dict[str, Any]:
decoded = self.decode_complete(
jwt, key, algorithms, options, audience, issuer, leeway
)
return decoded["payload"]
def _validate_claims(self, payload, options, audience, issuer, leeway):
if isinstance(leeway, timedelta):
leeway = timedelta_total_seconds(leeway)
leeway = leeway.total_seconds()
if not isinstance(audience, (string_types, type(None))):
raise TypeError('audience must be a string or None')
if not isinstance(audience, (str, type(None), Iterable)):
raise TypeError("audience must be a string, iterable, or None")
self._validate_required_claims(payload, options)
now = timegm(datetime.utcnow().utctimetuple())
now = timegm(datetime.now(tz=timezone.utc).utctimetuple())
if 'iat' in payload and options.get('verify_iat'):
if "iat" in payload and options["verify_iat"]:
self._validate_iat(payload, now, leeway)
if 'nbf' in payload and options.get('verify_nbf'):
if "nbf" in payload and options["verify_nbf"]:
self._validate_nbf(payload, now, leeway)
if 'exp' in payload and options.get('verify_exp'):
if "exp" in payload and options["verify_exp"]:
self._validate_exp(payload, now, leeway)
if options.get('verify_iss'):
if options["verify_iss"]:
self._validate_iss(payload, issuer)
if options.get('verify_aud'):
if options["verify_aud"]:
self._validate_aud(payload, audience)
def _validate_required_claims(self, payload, options):
if options.get('require_exp') and payload.get('exp') is None:
raise MissingRequiredClaimError('exp')
if options.get('require_iat') and payload.get('iat') is None:
raise MissingRequiredClaimError('iat')
if options.get('require_nbf') and payload.get('nbf') is None:
raise MissingRequiredClaimError('nbf')
for claim in options["require"]:
if payload.get(claim) is None:
raise MissingRequiredClaimError(claim)
def _validate_iat(self, payload, now, leeway):
try:
iat = int(payload['iat'])
int(payload["iat"])
except ValueError:
raise DecodeError('Issued At claim (iat) must be an integer.')
if iat > (now + leeway):
raise InvalidIssuedAtError('Issued At claim (iat) cannot be in'
' the future.')
raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.")
def _validate_nbf(self, payload, now, leeway):
try:
nbf = int(payload['nbf'])
nbf = int(payload["nbf"])
except ValueError:
raise DecodeError('Not Before claim (nbf) must be an integer.')
raise DecodeError("Not Before claim (nbf) must be an integer.")
if nbf > (now + leeway):
raise ImmatureSignatureError('The token is not yet valid (nbf)')
raise ImmatureSignatureError("The token is not yet valid (nbf)")
def _validate_exp(self, payload, now, leeway):
try:
exp = int(payload['exp'])
exp = int(payload["exp"])
except ValueError:
raise DecodeError('Expiration Time claim (exp) must be an'
' integer.')
raise DecodeError("Expiration Time claim (exp) must be an" " integer.")
if exp < (now - leeway):
raise ExpiredSignatureError('Signature has expired')
raise ExpiredSignatureError("Signature has expired")
def _validate_aud(self, payload, audience):
if audience is None and 'aud' not in payload:
return
if audience is None:
if "aud" not in payload or not payload["aud"]:
return
# Application did not specify an audience, but
# the token has the 'aud' claim
raise InvalidAudienceError("Invalid audience")
if audience is not None and 'aud' not in payload:
if "aud" not in payload or not payload["aud"]:
# Application specified an audience, but it could not be
# verified since the token does not contain a claim.
raise MissingRequiredClaimError('aud')
raise MissingRequiredClaimError("aud")
audience_claims = payload['aud']
audience_claims = payload["aud"]
if isinstance(audience_claims, string_types):
if isinstance(audience_claims, str):
audience_claims = [audience_claims]
if not isinstance(audience_claims, list):
raise InvalidAudienceError('Invalid claim format in token')
if any(not isinstance(c, string_types) for c in audience_claims):
raise InvalidAudienceError('Invalid claim format in token')
if audience not in audience_claims:
raise InvalidAudienceError('Invalid audience')
raise InvalidAudienceError("Invalid claim format in token")
if any(not isinstance(c, str) for c in audience_claims):
raise InvalidAudienceError("Invalid claim format in token")
if isinstance(audience, str):
audience = [audience]
if all(aud not in audience_claims for aud in audience):
raise InvalidAudienceError("Invalid audience")
def _validate_iss(self, payload, issuer):
if issuer is None:
return
if 'iss' not in payload:
raise MissingRequiredClaimError('iss')
if "iss" not in payload:
raise MissingRequiredClaimError("iss")
if payload['iss'] != issuer:
raise InvalidIssuerError('Invalid issuer')
if payload["iss"] != issuer:
raise InvalidIssuerError("Invalid issuer")
_jwt_global_obj = PyJWT()
encode = _jwt_global_obj.encode
decode_complete = _jwt_global_obj.decode_complete
decode = _jwt_global_obj.decode
register_algorithm = _jwt_global_obj.register_algorithm
unregister_algorithm = _jwt_global_obj.unregister_algorithm
get_unverified_header = _jwt_global_obj.get_unverified_header