Repository: jpadilla/pyjwt
Pull request #557 (2020-12-18 15:28:57)

Do not mutate the input payload in PyJWT.encode()

Dreamsorcerer: Looks good

Patch:
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index 68597a8..06c89f4 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -47,6 +47,7 @@ class PyJWT(PyJWS):
             )
 
         # Payload
+        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):
diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py
index f4980e6..707724e 100644
--- a/jwt/jwks_client.py
+++ b/jwt/jwks_client.py
@@ -53,5 +53,5 @@ class PyJWKClient:
         unverified = decode_token(
             token, complete=True, options={"verify_signature": False}
         )
-        header = unverified.get("header")
+        header = unverified["header"]
         return self.get_signing_key(header.get("kid"))


Test patch:
diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py
index 83177bf..35ba6ba 100644
--- a/tests/test_api_jwt.py
+++ b/tests/test_api_jwt.py
@@ -210,6 +210,12 @@ class TestJWT:
         assert decoded_payload["nbf"] == timegm(
             current_datetime.utctimetuple()
         )
+        # payload is not mutated.
+        assert payload == {
+            "exp": current_datetime,
+            "iat": current_datetime,
+            "nbf": current_datetime,
+        }
 
     # 'Control' Elliptic Curve JWT created by another library.
     # Used to test for regressions that could affect both
diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py
index 13d1a29..d263fbb 100644
--- a/tests/test_jwks_client.py
+++ b/tests/test_jwks_client.py
@@ -29,26 +29,22 @@ RESPONSE_DATA = {
 }
 
 
-@pytest.fixture
-def mocked_response():
-    @contextlib.contextmanager
-    def _mocked_response(data):
-        with mock.patch("urllib.request.urlopen") as urlopen_mock:
-            response = mock.Mock()
-            response.__enter__ = mock.Mock(return_value=response)
-            response.__exit__ = mock.Mock()
-            response.read.side_effect = [json.dumps(data)]
-            urlopen_mock.return_value = response
-            yield
-
-    return _mocked_response
+@contextlib.contextmanager
+def mocked_response(data):
+    with mock.patch("urllib.request.urlopen") as urlopen_mock:
+        response = mock.Mock()
+        response.__enter__ = mock.Mock(return_value=response)
+        response.__exit__ = mock.Mock()
+        response.read.side_effect = [json.dumps(data)]
+        urlopen_mock.return_value = response
+        yield
 
 
 @pytest.mark.skipif(
     not has_crypto, reason="Not supported without cryptography library"
 )
 class TestPyJWKClient:
-    def test_get_jwk_set(self, mocked_response):
+    def test_get_jwk_set(self):
         url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
 
         with mocked_response(RESPONSE_DATA):
@@ -57,7 +53,7 @@ class TestPyJWKClient:
 
         assert len(jwk_set.keys) == 1
 
-    def test_get_signing_keys(self, mocked_response):
+    def test_get_signing_keys(self):
         url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
 
         with mocked_response(RESPONSE_DATA):
@@ -67,7 +63,7 @@ class TestPyJWKClient:
         assert len(signing_keys) == 1
         assert isinstance(signing_keys[0], PyJWK)
 
-    def test_get_signing_keys_raises_if_none_found(self, mocked_response):
+    def test_get_signing_keys_raises_if_none_found(self):
         url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
 
         mocked_key = RESPONSE_DATA["keys"][0].copy()
@@ -83,7 +79,7 @@ class TestPyJWKClient:
             exc.value
         )
 
-    def test_get_signing_key(self, mocked_response):
+    def test_get_signing_key(self):
         url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
         kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw"
 
@@ -96,7 +92,7 @@ class TestPyJWKClient:
         assert signing_key.key_id == kid
         assert signing_key.public_key_use == "sig"
 
-    def test_get_signing_key_from_jwt(self, mocked_response):
+    def test_get_signing_key_from_jwt(self):
         token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
         url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"