Repository: mpdavis/python-jose

Issue:
Various issues in jwt.decode / jws._get_keys
I've had a couple issues (figuring out how to use jwt.decode) which stem from the jws._get_keys implementation.

1. key argument must be iterable- raises exception otherwise
2. string key argument must not contain 'keys' (ie if a PEM base64 segment or HS secret segment contains keys, it'll break)
3. key can't be the result of calling jwk.construct (usability issue)
4. attempting json.loads on anything not a string seems weird




Comments:

Patch:
diff --git a/jose/jws.py b/jose/jws.py
index 293b32a..8d7bce6 100644
--- a/jose/jws.py
+++ b/jose/jws.py
@@ -225,28 +225,27 @@ def _sig_matches_keys(keys, signing_input, signature, alg):
 def _get_keys(key):
 
     try:
-        key = json.loads(key)
+        key = json.loads(key, parse_int=str, parse_float=str)
     except Exception:
         pass
 
-    # JWK Set per RFC 7517
-    if 'keys' in key:
-        return key['keys']
-
-    # Individual JWK per RFC 7517
-    elif 'kty' in key:
-        return (key,)
-
-    # Some other mapping. Firebase uses just dict of kid, cert pairs
-    elif isinstance(key, Mapping):
-        values = key.values()
-        if values:
-            return values
-        return (key,)
+    if isinstance(key, Mapping):
+        if 'keys' in key:
+            # JWK Set per RFC 7517
+            return key['keys']
+        elif 'kty' in key:
+            # Individual JWK per RFC 7517
+            return (key,)
+        else:
+            # Some other mapping. Firebase uses just dict of kid, cert pairs
+            values = key.values()
+            if values:
+                return values
+            return (key,)
 
     # Iterable but not text or mapping => list- or tuple-like
     elif (isinstance(key, Iterable) and
-          not (isinstance(key, six.string_types) or isinstance(key, Mapping))):
+          not (isinstance(key, six.string_types) or isinstance(key, six.binary_type))):
         return key
 
     # Scalar value, wrap in tuple.


Test patch:
diff --git a/tests/test_jws.py b/tests/test_jws.py
index 2282c5f..4ca57a4 100644
--- a/tests/test_jws.py
+++ b/tests/test_jws.py
@@ -77,6 +77,17 @@ class TestJWS(object):
         with pytest.raises(JWSError):
             jws.sign(payload, 'secret', algorithm='RS256')
 
+    @pytest.mark.parametrize("key", [
+        b'key',
+        'key',
+    ])
+    def test_round_trip_with_different_key_types(self, key):
+        signed_data = jws.sign({'testkey': 'testvalue'}, key, algorithm=ALGORITHMS.HS256)
+        verified_bytes = jws.verify(signed_data, key, algorithms=[ALGORITHMS.HS256])
+        verified_data = json.loads(verified_bytes.decode('utf-8'))
+        assert 'testkey' in verified_data.keys()
+        assert verified_data['testkey'] == 'testvalue'
+
 
 class TestHMAC(object):
 
diff --git a/tests/test_jwt.py b/tests/test_jwt.py
index 03f5c28..03783ef 100644
--- a/tests/test_jwt.py
+++ b/tests/test_jwt.py
@@ -52,6 +52,13 @@ class TestJWT:
                 key=key,
                 algorithms=[])
 
+    @pytest.mark.parametrize("key, token",
+                             [('1234567890', u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.aNBlulVhiYSCzvsh1rTzXZC2eWJmNrMBjINT-0wQz4k'),
+                              ('123456789.0',u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.D8WLFPMi3yKgua2jm3BKThFsParXpgxhIbsUc39zJDw')])
+    def test_numeric_key(self, key, token):
+        token_info = jwt.decode(token, key)
+        assert token_info == {"name": "test"}
+
     def test_invalid_claims_json(self):
         old_jws_verify = jws.verify
         try:
@@ -165,6 +172,16 @@ class TestJWT:
 
         assert decoded == claims
 
+    @pytest.mark.parametrize('key', [
+        b'key',
+        'key',
+    ])
+    def test_round_trip_with_different_key_types(self, key):
+        token = jwt.encode({'testkey': 'testvalue'}, key, algorithm='HS256')
+        verified_data = jwt.decode(token, key, algorithms=['HS256'])
+        assert 'testkey' in verified_data.keys()
+        assert verified_data['testkey'] == 'testvalue'
+
     def test_leeway_is_int(self):
         pass