11
11
from oic .exception import MessageException
12
12
from oic .oic import PREFERENCE2PROVIDER
13
13
from oic .oic import scope2claims
14
- from oic .oic .message import AccessTokenRequest
15
14
from oic .oic .message import AccessTokenResponse
16
- from oic .oic .message import AuthorizationRequest
17
15
from oic .oic .message import AuthorizationResponse
18
16
from oic .oic .message import EndSessionRequest
19
17
from oic .oic .message import EndSessionResponse
23
21
from oic .oic .message import RefreshAccessTokenRequest
24
22
from oic .oic .message import RegistrationRequest
25
23
from oic .oic .message import RegistrationResponse
24
+ from oic .extension .provider import Provider as OICProviderExtensions
26
25
26
+ from .message import AuthorizationRequest
27
+ from .message import AccessTokenRequest
27
28
from .access_token import extract_bearer_token_from_http_request
28
29
from .client_authentication import verify_client_authentication
29
30
from .exceptions import AuthorizationError
@@ -81,7 +82,7 @@ def __init__(self, signing_key, configuration_information, authz_state, clients,
81
82
self .userinfo = userinfo
82
83
self .id_token_lifetime = id_token_lifetime
83
84
84
- self .authentication_request_validators = [] # type: List[Callable[[oic.oic.message. AuthorizationRequest], Boolean]]
85
+ self .authentication_request_validators = [] # type: List[Callable[[AuthorizationRequest], Boolean]]
85
86
self .authentication_request_validators .append (authorization_request_verify )
86
87
self .authentication_request_validators .append (
87
88
functools .partial (client_id_is_known , self ))
@@ -114,7 +115,7 @@ def jwks(self):
114
115
return {'keys' : keys }
115
116
116
117
def parse_authentication_request (self , request_body , http_headers = None ):
117
- # type: (str, Optional[Mapping[str, str]]) -> oic.oic.message. AuthorizationRequest
118
+ # type: (str, Optional[Mapping[str, str]]) -> AuthorizationRequest
118
119
"""
119
120
Parses and verifies an authentication request.
120
121
@@ -130,7 +131,7 @@ def parse_authentication_request(self, request_body, http_headers=None):
130
131
logger .debug ('parsed authentication_request: %s' , auth_req )
131
132
return auth_req
132
133
133
- def authorize (self , authentication_request , # type: oic.oic.message. AuthorizationRequest
134
+ def authorize (self , authentication_request , # type: AuthorizationRequest
134
135
user_id , # type: str
135
136
extra_id_token_claims = None
136
137
# type: Optional[Union[Mapping[str, Union[str, List[str]]], Callable[[str, str], Mapping[str, Union[str, List[str]]]]]
@@ -216,7 +217,7 @@ def _create_subject_identifier(self, user_id, client_id, redirect_uri):
216
217
return self .authz_state .get_subject_identifier (subject_type , user_id , sector_identifier )
217
218
218
219
def _get_requested_claims_in (self , authentication_request , response_method ):
219
- # type (oic.oic.message. AuthorizationRequest, str) -> Mapping[str, Optional[Mapping[str, Union[str, List[str]]]]
220
+ # type (AuthorizationRequest, str) -> Mapping[str, Optional[Mapping[str, Union[str, List[str]]]]
220
221
"""
221
222
Parses any claims requested using the 'claims' request parameter, see
222
223
<a href="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter">
@@ -284,7 +285,7 @@ def _create_signed_id_token(self,
284
285
return id_token .to_jwt ([self .signing_key ], alg )
285
286
286
287
def _check_subject_identifier_matches_requested (self , authentication_request , sub ):
287
- # type (oic.message. AuthorizationRequest, str) -> None
288
+ # type (AuthorizationRequest, str) -> None
288
289
"""
289
290
Verifies the subject identifier against any requested subject identifier using the claims request parameter.
290
291
:param authentication_request: authentication request
@@ -328,6 +329,58 @@ def handle_token_request(self, request_body, # type: str
328
329
raise InvalidTokenRequest ('grant_type \' {}\' unknown' .format (token_request ['grant_type' ]), token_request ,
329
330
oauth_error = 'unsupported_grant_type' )
330
331
332
+ def _PKCE_verify (self ,
333
+ token_request , # type: AccessTokenRequest
334
+ authentication_request # type: AuthorizationRequest
335
+ ):
336
+ # type: (...) -> bool
337
+ """
338
+ Verify that the given code_verifier complies with the initially supplied code_challenge.
339
+
340
+ Only supports the SHA256 code challenge method, plaintext is regarded as unsafe.
341
+
342
+ :param token_request: the token request containing the initially supplied code challenge and code_challenge method.
343
+ :param authentication_request: the code_verfier to check against the code challenge.
344
+ :returns: whether the code_verifier is what was expected given the cc_cm
345
+ """
346
+ if not 'code_verifier' in token_request :
347
+ return False
348
+
349
+ if not 'code_challenge_method' in authentication_request :
350
+ raise InvalidTokenRequest ("A code_challenge and code_verifier have been supplied"
351
+ "but missing code_challenge_method in authentication_request" , token_request )
352
+
353
+ # OIC Provider extension returns either a boolean or Response object containing an error. To support
354
+ # stricter typing guidelines, return if True. Error handling support should be in encapsulating function.
355
+ return OICProviderExtensions .verify_code_challenge (token_request ['code_verifier' ],
356
+ authentication_request ['code_challenge' ], authentication_request ['code_challenge_method' ]) == True
357
+
358
+ def _verify_code_exchange_req (self ,
359
+ token_request , # type: AccessTokenRequest
360
+ authentication_request # type: AuthorizationRequest
361
+ ):
362
+ # type: (...) -> None
363
+ """
364
+ Verify that the code exchange request is valid. In order to be valid we validate
365
+ the expected client and redirect_uri. Finally, if requested by the client, perform a
366
+ PKCE check.
367
+
368
+ :param token_request: The request asking for a token given a code, and optionally a code_verifier
369
+ :param authentication_request: The authentication request belonging to the provided code.
370
+ :raises InvalidTokenRequest, InvalidAuthorizationCode: If request is invalid, throw a representing exception.
371
+ """
372
+ if token_request ['client_id' ] != authentication_request ['client_id' ]:
373
+ logger .info ('Authorization code \' %s\' belonging to \' %s\' was used by \' %s\' ' ,
374
+ token_request ['code' ], authentication_request ['client_id' ], token_request ['client_id' ])
375
+ raise InvalidAuthorizationCode ('{} unknown' .format (token_request ['code' ]))
376
+ if token_request ['redirect_uri' ] != authentication_request ['redirect_uri' ]:
377
+ raise InvalidTokenRequest ('Invalid redirect_uri: {} != {}' .format (token_request ['redirect_uri' ],
378
+ authentication_request ['redirect_uri' ]),
379
+ token_request )
380
+ if 'code_challenge' in authentication_request and not self ._PKCE_verify (token_request , authentication_request ):
381
+ raise InvalidTokenRequest ('Unexpected Code Verifier: {}' .format (authentication_request ['code_challenge' ]),
382
+ token_request )
383
+
331
384
def _do_code_exchange (self , request , # type: Dict[str, str]
332
385
extra_id_token_claims = None
333
386
# type: Optional[Union[Mapping[str, Union[str, List[str]]], Callable[[str, str], Mapping[str, Union[str, List[str]]]]]
@@ -351,14 +404,7 @@ def _do_code_exchange(self, request, # type: Dict[str, str]
351
404
352
405
authentication_request = self .authz_state .get_authorization_request_for_code (token_request ['code' ])
353
406
354
- if token_request ['client_id' ] != authentication_request ['client_id' ]:
355
- logger .info ('Authorization code \' %s\' belonging to \' %s\' was used by \' %s\' ' ,
356
- token_request ['code' ], authentication_request ['client_id' ], token_request ['client_id' ])
357
- raise InvalidAuthorizationCode ('{} unknown' .format (token_request ['code' ]))
358
- if token_request ['redirect_uri' ] != authentication_request ['redirect_uri' ]:
359
- raise InvalidTokenRequest ('Invalid redirect_uri: {} != {}' .format (token_request ['redirect_uri' ],
360
- authentication_request ['redirect_uri' ]),
361
- token_request )
407
+ self ._verify_code_exchange_req (token_request , authentication_request )
362
408
363
409
sub = self .authz_state .get_subject_identifier_for_code (token_request ['code' ])
364
410
user_id = self .authz_state .get_user_id_for_subject_identifier (sub )
0 commit comments