From d8474f9617dd78a6bd9de774538f3a526e65be9b Mon Sep 17 00:00:00 2001 From: Pau Ruiz Safont Date: Thu, 14 May 2020 15:50:39 +0100 Subject: [PATCH] Require tweak (#280) * Use require options as a list, instead of booleans Deprecate the use of the boolean options * Add test for the new require option * Add documentation on how to use the require option Co-authored-by: Pau Ruiz i Safont --- docs/usage.rst | 10 ++++++++++ jwt/api_jwt.py | 34 ++++++++++++++++++++++------------ tests/test_api_jwt.py | 15 +++++++++++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index d2da71a72..d012a95a2 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -242,3 +242,13 @@ Issued At Claim (iat) jwt.encode({'iat': 1371720939}, 'secret') jwt.encode({'iat': datetime.utcnow()}, 'secret') + +Requiring Presence of Claims +---------------------------- + +If you wish to require one or more claims to be present in the claimset, you can set the ``require`` paramenter to include these claims. + +.. code-block:: python + + >>jwt.decode(encoded, options={'require': ['exp', 'iss', 'sub']}) + {u'exp': 1371720939, u'iss': u'urn:foo', u'sub': u'25c37522-f148-4cbf-8ee6-c4a9718dd0af'} diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py index 22bfc6a5d..125b57a58 100644 --- a/jwt/api_jwt.py +++ b/jwt/api_jwt.py @@ -26,10 +26,11 @@ class PyJWT(PyJWS): header_type = "JWT" + deprecated_requires = ["require_exp", "require_iat", "require_nbf"] @staticmethod def _get_default_options(): - # type: () -> Dict[str, bool] + # type: () -> Dict[str, Union[bool, List[str]]] return { "verify_signature": True, "verify_exp": True, @@ -37,9 +38,7 @@ def _get_default_options(): "verify_iat": True, "verify_aud": True, "verify_iss": True, - "require_exp": False, - "require_iat": False, - "require_nbf": False, + "require": [], } def encode( @@ -128,6 +127,22 @@ def _validate_claims( DeprecationWarning, ) + verify_claims = { + required + for required in self.deprecated_requires + if required in options + } + require_options = options.setdefault("require", []) + for opt in verify_claims: + opt_claim = opt.split("require_", 1)[1] + if options[opt]: + require_options.append(opt_claim) + warnings.warn( + "The {0} parameter is deprecated. Please add {1} to" + " the require list in options instead".format(opt, opt_claim), + DeprecationWarning, + ) + if isinstance(leeway, timedelta): leeway = leeway.total_seconds() @@ -154,14 +169,9 @@ def _validate_claims( 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.get("require", []): + if payload.get(claim) is None: + raise MissingRequiredClaimError(claim) def _validate_iat(self, payload, now, leeway): try: diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py index e4065c69e..c1759e358 100644 --- a/tests/test_api_jwt.py +++ b/tests/test_api_jwt.py @@ -437,6 +437,21 @@ def test_decode_should_raise_error_if_nbf_required_but_not_present( assert exc.value.claim == "nbf" + def test_decode_should_raise_error_if_claim_required_but_not_present( + self, jwt + ): + claim = "sub" + payload = { + "some": "payload", + # claim not present + } + token = jwt.encode(payload, "secret") + + with pytest.raises(MissingRequiredClaimError) as exc: + jwt.decode(token, "secret", options={"require": [claim]}) + + assert exc.value.claim == claim + def test_skip_check_signature(self, jwt): token = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"