12
12
13
13
try :
14
14
from jose import jwt
15
+ from jose .exceptions import JWTError , JWSError
15
16
except ImportError :
16
17
logging .getLogger (__name__ ).debug ("jose library not installed" )
17
18
@@ -51,14 +52,15 @@ class AuthOauthProvider(models.Model):
51
52
)
52
53
53
54
@tools .ormcache ("self.jwks_uri" , "kid" )
54
- def _get_key (self , kid ):
55
+ def _get_keys (self , kid ):
55
56
r = requests .get (self .jwks_uri )
56
57
r .raise_for_status ()
57
58
response = r .json ()
58
- for key in response ["keys" ]:
59
- if key ["kid" ] == kid :
60
- return key
61
- return {}
59
+ # the keys returned here should follow
60
+ # JWS Notes on Key Selection
61
+ # https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-signature#appendix-D
62
+ return [key for key in response ["keys" ]
63
+ if kid is None or key .get ("kid" , None ) == kid ]
62
64
63
65
def _map_token_values (self , res ):
64
66
if self .token_map :
@@ -72,19 +74,31 @@ def _parse_id_token(self, id_token, access_token):
72
74
self .ensure_one ()
73
75
res = {}
74
76
header = jwt .get_unverified_header (id_token )
75
- res .update (
76
- jwt .decode (
77
- id_token ,
78
- self ._get_key (header .get ("kid" )),
79
- algorithms = ["RS256" ],
80
- audience = self .client_id ,
81
- access_token = access_token ,
82
- )
83
- )
84
-
77
+ res .update (self ._decode_id_token (access_token , id_token , header .get ("kid" )))
85
78
res .update (self ._map_token_values (res ))
86
79
return res
87
80
81
+ def _decode_id_token (self , access_token , id_token , kid ):
82
+ keys = self ._get_keys (kid )
83
+ if len (keys ) > 1 and kid is None :
84
+ # https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1
85
+ # If there are multiple keys in the referenced JWK Set document, a kid
86
+ # value MUST be provided in the JOSE Header.
87
+ raise JWTError ("OpenID Connect requires kid to be set if there is more"
88
+ " than one key in the JWKS" )
89
+ error = None
90
+ # we accept multiple keys with the same kid in case a key gets rotated.
91
+ for key in keys :
92
+ try :
93
+ values = jwt .decode (id_token , key , algorithms = ["RS256" ],
94
+ audience = self .client_id , access_token = access_token )
95
+ return values
96
+ except (JWTError , JWSError ) as e :
97
+ error = e
98
+ if error :
99
+ raise error
100
+ return {}
101
+
88
102
89
103
class AuthOauthProviderGroupLine (models .Model ):
90
104
_name = 'auth.oauth.provider.group_line'
0 commit comments