1111from oic .exception import MessageException
1212from oic .oic import PREFERENCE2PROVIDER
1313from oic .oic import scope2claims
14- from oic .oic .message import AccessTokenRequest
1514from oic .oic .message import AccessTokenResponse
16- from oic .oic .message import AuthorizationRequest
1715from oic .oic .message import AuthorizationResponse
1816from oic .oic .message import EndSessionRequest
1917from oic .oic .message import EndSessionResponse
2321from oic .oic .message import RefreshAccessTokenRequest
2422from oic .oic .message import RegistrationRequest
2523from oic .oic .message import RegistrationResponse
24+ from oic .extension .provider import Provider as OICProviderExtensions
2625
26+ from .message import AuthorizationRequest
27+ from .message import AccessTokenRequest
2728from .access_token import extract_bearer_token_from_http_request
2829from .client_authentication import verify_client_authentication
2930from .exceptions import AuthorizationError
@@ -81,7 +82,7 @@ def __init__(self, signing_key, configuration_information, authz_state, clients,
8182 self .userinfo = userinfo
8283 self .id_token_lifetime = id_token_lifetime
8384
84- self .authentication_request_validators = [] # type: List[Callable[[oic.oic.message. AuthorizationRequest], Boolean]]
85+ self .authentication_request_validators = [] # type: List[Callable[[AuthorizationRequest], Boolean]]
8586 self .authentication_request_validators .append (authorization_request_verify )
8687 self .authentication_request_validators .append (
8788 functools .partial (client_id_is_known , self ))
@@ -114,7 +115,7 @@ def jwks(self):
114115 return {'keys' : keys }
115116
116117 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
118119 """
119120 Parses and verifies an authentication request.
120121
@@ -130,7 +131,7 @@ def parse_authentication_request(self, request_body, http_headers=None):
130131 logger .debug ('parsed authentication_request: %s' , auth_req )
131132 return auth_req
132133
133- def authorize (self , authentication_request , # type: oic.oic.message. AuthorizationRequest
134+ def authorize (self , authentication_request , # type: AuthorizationRequest
134135 user_id , # type: str
135136 extra_id_token_claims = None
136137 # 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):
216217 return self .authz_state .get_subject_identifier (subject_type , user_id , sector_identifier )
217218
218219 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]]]]
220221 """
221222 Parses any claims requested using the 'claims' request parameter, see
222223 <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter">
@@ -284,7 +285,7 @@ def _create_signed_id_token(self,
284285 return id_token .to_jwt ([self .signing_key ], alg )
285286
286287 def _check_subject_identifier_matches_requested (self , authentication_request , sub ):
287- # type (oic.message. AuthorizationRequest, str) -> None
288+ # type (AuthorizationRequest, str) -> None
288289 """
289290 Verifies the subject identifier against any requested subject identifier using the claims request parameter.
290291 :param authentication_request: authentication request
@@ -328,6 +329,58 @@ def handle_token_request(self, request_body, # type: str
328329 raise InvalidTokenRequest ('grant_type \' {}\' unknown' .format (token_request ['grant_type' ]), token_request ,
329330 oauth_error = 'unsupported_grant_type' )
330331
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+
331384 def _do_code_exchange (self , request , # type: Dict[str, str]
332385 extra_id_token_claims = None
333386 # 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]
351404
352405 authentication_request = self .authz_state .get_authorization_request_for_code (token_request ['code' ])
353406
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 )
362408
363409 sub = self .authz_state .get_subject_identifier_for_code (token_request ['code' ])
364410 user_id = self .authz_state .get_user_id_for_subject_identifier (sub )
0 commit comments