1""" 
    2oauthlib.oauth2.rfc6749.grant_types 
    3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    4""" 
    5import base64 
    6import hashlib 
    7import json 
    8import logging 
    9 
    10from oauthlib import common 
    11 
    12from .. import errors 
    13from .base import GrantTypeBase 
    14 
    15log = logging.getLogger(__name__) 
    16 
    17 
    18def code_challenge_method_s256(verifier, challenge): 
    19    """ 
    20    If the "code_challenge_method" from `Section 4.3`_ was "S256", the 
    21    received "code_verifier" is hashed by SHA-256, base64url-encoded, and 
    22    then compared to the "code_challenge", i.e.: 
    23 
    24    BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge 
    25 
    26    How to implement a base64url-encoding 
    27    function without padding, based upon the standard base64-encoding 
    28    function that uses padding. 
    29 
    30    To be concrete, example C# code implementing these functions is shown 
    31    below.  Similar code could be used in other languages. 
    32 
    33    static string base64urlencode(byte [] arg) 
    34    { 
    35        string s = Convert.ToBase64String(arg); // Regular base64 encoder 
    36        s = s.Split('=')[0]; // Remove any trailing '='s 
    37        s = s.Replace('+', '-'); // 62nd char of encoding 
    38        s = s.Replace('/', '_'); // 63rd char of encoding 
    39        return s; 
    40    } 
    41 
    42    In python urlsafe_b64encode is already replacing '+' and '/', but preserve 
    43    the trailing '='. So we have to remove it. 
    44 
    45    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3 
    46    """ 
    47    return base64.urlsafe_b64encode( 
    48        hashlib.sha256(verifier.encode()).digest() 
    49    ).decode().rstrip('=') == challenge 
    50 
    51 
    52def code_challenge_method_plain(verifier, challenge): 
    53    """ 
    54    If the "code_challenge_method" from `Section 4.3`_ was "plain", they are 
    55    compared directly, i.e.: 
    56 
    57    code_verifier == code_challenge. 
    58 
    59    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3 
    60    """ 
    61    return verifier == challenge 
    62 
    63 
    64class AuthorizationCodeGrant(GrantTypeBase): 
    65 
    66    """`Authorization Code Grant`_ 
    67 
    68    The authorization code grant type is used to obtain both access 
    69    tokens and refresh tokens and is optimized for confidential clients. 
    70    Since this is a redirection-based flow, the client must be capable of 
    71    interacting with the resource owner's user-agent (typically a web 
    72    browser) and capable of receiving incoming requests (via redirection) 
    73    from the authorization server:: 
    74 
    75        +----------+ 
    76        | Resource | 
    77        |   Owner  | 
    78        |          | 
    79        +----------+ 
    80             ^ 
    81             | 
    82            (B) 
    83        +----|-----+          Client Identifier      +---------------+ 
    84        |         -+----(A)-- & Redirection URI ---->|               | 
    85        |  User-   |                                 | Authorization | 
    86        |  Agent  -+----(B)-- User authenticates --->|     Server    | 
    87        |          |                                 |               | 
    88        |         -+----(C)-- Authorization Code ---<|               | 
    89        +-|----|---+                                 +---------------+ 
    90          |    |                                         ^      v 
    91         (A)  (C)                                        |      | 
    92          |    |                                         |      | 
    93          ^    v                                         |      | 
    94        +---------+                                      |      | 
    95        |         |>---(D)-- Authorization Code ---------'      | 
    96        |  Client |          & Redirection URI                  | 
    97        |         |                                             | 
    98        |         |<---(E)----- Access Token -------------------' 
    99        +---------+       (w/ Optional Refresh Token) 
    100 
    101    Note: The lines illustrating steps (A), (B), and (C) are broken into 
    102    two parts as they pass through the user-agent. 
    103 
    104    Figure 3: Authorization Code Flow 
    105 
    106    The flow illustrated in Figure 3 includes the following steps: 
    107 
    108    (A)  The client initiates the flow by directing the resource owner's 
    109         user-agent to the authorization endpoint.  The client includes 
    110         its client identifier, requested scope, local state, and a 
    111         redirection URI to which the authorization server will send the 
    112         user-agent back once access is granted (or denied). 
    113 
    114    (B)  The authorization server authenticates the resource owner (via 
    115         the user-agent) and establishes whether the resource owner 
    116         grants or denies the client's access request. 
    117 
    118    (C)  Assuming the resource owner grants access, the authorization 
    119         server redirects the user-agent back to the client using the 
    120         redirection URI provided earlier (in the request or during 
    121         client registration).  The redirection URI includes an 
    122         authorization code and any local state provided by the client 
    123         earlier. 
    124 
    125    (D)  The client requests an access token from the authorization 
    126         server's token endpoint by including the authorization code 
    127         received in the previous step.  When making the request, the 
    128         client authenticates with the authorization server.  The client 
    129         includes the redirection URI used to obtain the authorization 
    130         code for verification. 
    131 
    132    (E)  The authorization server authenticates the client, validates the 
    133         authorization code, and ensures that the redirection URI 
    134         received matches the URI used to redirect the client in 
    135         step (C).  If valid, the authorization server responds back with 
    136         an access token and, optionally, a refresh token. 
    137 
    138    OAuth 2.0 public clients utilizing the Authorization Code Grant are 
    139    susceptible to the authorization code interception attack. 
    140 
    141    A technique to mitigate against the threat through the use of Proof Key for Code 
    142    Exchange (PKCE, pronounced "pixy") is implemented in the current oauthlib 
    143    implementation. 
    144 
    145    .. _`Authorization Code Grant`: https://tools.ietf.org/html/rfc6749#section-4.1 
    146    .. _`PKCE`: https://tools.ietf.org/html/rfc7636 
    147    """ 
    148 
    149    default_response_mode = 'query' 
    150    response_types = ['code'] 
    151 
    152    # This dict below is private because as RFC mention it: 
    153    # "S256" is Mandatory To Implement (MTI) on the server. 
    154    # 
    155    _code_challenge_methods = { 
    156        'plain': code_challenge_method_plain, 
    157        'S256': code_challenge_method_s256 
    158    } 
    159 
    160    def create_authorization_code(self, request): 
    161        """ 
    162        Generates an authorization grant represented as a dictionary. 
    163 
    164        :param request: OAuthlib request. 
    165        :type request: oauthlib.common.Request 
    166        """ 
    167        grant = {'code': common.generate_token()} 
    168        if hasattr(request, 'state') and request.state: 
    169            grant['state'] = request.state 
    170        log.debug('Created authorization code grant %r for request %r.', 
    171                  grant, request) 
    172        return grant 
    173 
    174    def create_authorization_response(self, request, token_handler): 
    175        """ 
    176        The client constructs the request URI by adding the following 
    177        parameters to the query component of the authorization endpoint URI 
    178        using the "application/x-www-form-urlencoded" format, per `Appendix B`_: 
    179 
    180        response_type 
    181                REQUIRED.  Value MUST be set to "code" for standard OAuth2 
    182                authorization flow.  For OpenID Connect it must be one of 
    183                "code token", "code id_token", or "code token id_token" - we 
    184                essentially test that "code" appears in the response_type. 
    185        client_id 
    186                REQUIRED.  The client identifier as described in `Section 2.2`_. 
    187        redirect_uri 
    188                OPTIONAL.  As described in `Section 3.1.2`_. 
    189        scope 
    190                OPTIONAL.  The scope of the access request as described by 
    191                `Section 3.3`_. 
    192        state 
    193                RECOMMENDED.  An opaque value used by the client to maintain 
    194                state between the request and callback.  The authorization 
    195                server includes this value when redirecting the user-agent back 
    196                to the client.  The parameter SHOULD be used for preventing 
    197                cross-site request forgery as described in `Section 10.12`_. 
    198 
    199        The client directs the resource owner to the constructed URI using an 
    200        HTTP redirection response, or by other means available to it via the 
    201        user-agent. 
    202 
    203        :param request: OAuthlib request. 
    204        :type request: oauthlib.common.Request 
    205        :param token_handler: A token handler instance, for example of type 
    206                              oauthlib.oauth2.BearerToken. 
    207        :returns: headers, body, status 
    208        :raises: FatalClientError on invalid redirect URI or client id. 
    209 
    210        A few examples:: 
    211 
    212            >>> from your_validator import your_validator 
    213            >>> request = Request('https://example.com/authorize?client_id=valid' 
    214            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F') 
    215            >>> from oauthlib.common import Request 
    216            >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken 
    217            >>> token = BearerToken(your_validator) 
    218            >>> grant = AuthorizationCodeGrant(your_validator) 
    219            >>> request.scopes = ['authorized', 'in', 'some', 'form'] 
    220            >>> grant.create_authorization_response(request, token) 
    221            (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400) 
    222            >>> request = Request('https://example.com/authorize?client_id=valid' 
    223            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F' 
    224            ...                   '&response_type=code') 
    225            >>> request.scopes = ['authorized', 'in', 'some', 'form'] 
    226            >>> grant.create_authorization_response(request, token) 
    227            (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200) 
    228            >>> # If the client id or redirect uri fails validation 
    229            >>> grant.create_authorization_response(request, token) 
    230            Traceback (most recent call last): 
    231                File "<stdin>", line 1, in <module> 
    232                File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response 
    233                    >>> grant.create_authorization_response(request, token) 
    234                File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request 
    235            oauthlib.oauth2.rfc6749.errors.InvalidClientIdError 
    236 
    237        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B 
    238        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2 
    239        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2 
    240        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 
    241        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 
    242        """ 
    243        try: 
    244            self.validate_authorization_request(request) 
    245            log.debug('Pre resource owner authorization validation ok for %r.', 
    246                      request) 
    247 
    248        # If the request fails due to a missing, invalid, or mismatching 
    249        # redirection URI, or if the client identifier is missing or invalid, 
    250        # the authorization server SHOULD inform the resource owner of the 
    251        # error and MUST NOT automatically redirect the user-agent to the 
    252        # invalid redirection URI. 
    253        except errors.FatalClientError as e: 
    254            log.debug('Fatal client error during validation of %r. %r.', 
    255                      request, e) 
    256            raise 
    257 
    258        # If the resource owner denies the access request or if the request 
    259        # fails for reasons other than a missing or invalid redirection URI, 
    260        # the authorization server informs the client by adding the following 
    261        # parameters to the query component of the redirection URI using the 
    262        # "application/x-www-form-urlencoded" format, per Appendix B: 
    263        # https://tools.ietf.org/html/rfc6749#appendix-B 
    264        except errors.OAuth2Error as e: 
    265            log.debug('Client error during validation of %r. %r.', request, e) 
    266            request.redirect_uri = request.redirect_uri or self.error_uri 
    267            redirect_uri = common.add_params_to_uri( 
    268                request.redirect_uri, e.twotuples, 
    269                fragment=request.response_mode == "fragment") 
    270            return {'Location': redirect_uri}, None, 302 
    271 
    272        grant = self.create_authorization_code(request) 
    273        for modifier in self._code_modifiers: 
    274            grant = modifier(grant, token_handler, request) 
    275        if 'access_token' in grant: 
    276            self.request_validator.save_token(grant, request) 
    277        log.debug('Saving grant %r for %r.', grant, request) 
    278        self.request_validator.save_authorization_code( 
    279            request.client_id, grant, request) 
    280        return self.prepare_authorization_response( 
    281            request, grant, {}, None, 302) 
    282 
    283    def create_token_response(self, request, token_handler): 
    284        """Validate the authorization code. 
    285 
    286        The client MUST NOT use the authorization code more than once. If an 
    287        authorization code is used more than once, the authorization server 
    288        MUST deny the request and SHOULD revoke (when possible) all tokens 
    289        previously issued based on that authorization code. The authorization 
    290        code is bound to the client identifier and redirection URI. 
    291 
    292        :param request: OAuthlib request. 
    293        :type request: oauthlib.common.Request 
    294        :param token_handler: A token handler instance, for example of type 
    295                              oauthlib.oauth2.BearerToken. 
    296 
    297        """ 
    298        headers = self._get_default_headers() 
    299        try: 
    300            self.validate_token_request(request) 
    301            log.debug('Token request validation ok for %r.', request) 
    302        except errors.OAuth2Error as e: 
    303            log.debug('Client error during validation of %r. %r.', request, e) 
    304            headers.update(e.headers) 
    305            return headers, e.json, e.status_code 
    306 
    307        token = token_handler.create_token(request, refresh_token=self.refresh_token) 
    308 
    309        for modifier in self._token_modifiers: 
    310            token = modifier(token, token_handler, request) 
    311 
    312        self.request_validator.save_token(token, request) 
    313        self.request_validator.invalidate_authorization_code( 
    314            request.client_id, request.code, request) 
    315        headers.update(self._create_cors_headers(request)) 
    316        return headers, json.dumps(token), 200 
    317 
    318    def validate_authorization_request(self, request): 
    319        """Check the authorization request for normal and fatal errors. 
    320 
    321        A normal error could be a missing response_type parameter or the client 
    322        attempting to access scope it is not allowed to ask authorization for. 
    323        Normal errors can safely be included in the redirection URI and 
    324        sent back to the client. 
    325 
    326        Fatal errors occur when the client_id or redirect_uri is invalid or 
    327        missing. These must be caught by the provider and handled, how this 
    328        is done is outside of the scope of OAuthLib but showing an error 
    329        page describing the issue is a good idea. 
    330 
    331        :param request: OAuthlib request. 
    332        :type request: oauthlib.common.Request 
    333        """ 
    334 
    335        # First check for fatal errors 
    336 
    337        # If the request fails due to a missing, invalid, or mismatching 
    338        # redirection URI, or if the client identifier is missing or invalid, 
    339        # the authorization server SHOULD inform the resource owner of the 
    340        # error and MUST NOT automatically redirect the user-agent to the 
    341        # invalid redirection URI. 
    342 
    343        # First check duplicate parameters 
    344        for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'): 
    345            try: 
    346                duplicate_params = request.duplicate_params 
    347            except ValueError: 
    348                raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request) 
    349            if param in duplicate_params: 
    350                raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request) 
    351 
    352        # REQUIRED. The client identifier as described in Section 2.2. 
    353        # https://tools.ietf.org/html/rfc6749#section-2.2 
    354        if not request.client_id: 
    355            raise errors.MissingClientIdError(request=request) 
    356 
    357        if not self.request_validator.validate_client_id(request.client_id, request): 
    358            raise errors.InvalidClientIdError(request=request) 
    359 
    360        # OPTIONAL. As described in Section 3.1.2. 
    361        # https://tools.ietf.org/html/rfc6749#section-3.1.2 
    362        log.debug('Validating redirection uri %s for client %s.', 
    363                  request.redirect_uri, request.client_id) 
    364 
    365        # OPTIONAL. As described in Section 3.1.2. 
    366        # https://tools.ietf.org/html/rfc6749#section-3.1.2 
    367        self._handle_redirects(request) 
    368 
    369        # Then check for normal errors. 
    370 
    371        # If the resource owner denies the access request or if the request 
    372        # fails for reasons other than a missing or invalid redirection URI, 
    373        # the authorization server informs the client by adding the following 
    374        # parameters to the query component of the redirection URI using the 
    375        # "application/x-www-form-urlencoded" format, per Appendix B. 
    376        # https://tools.ietf.org/html/rfc6749#appendix-B 
    377 
    378        # Note that the correct parameters to be added are automatically 
    379        # populated through the use of specific exceptions. 
    380 
    381        request_info = {} 
    382        for validator in self.custom_validators.pre_auth: 
    383            request_info.update(validator(request)) 
    384 
    385        # REQUIRED. 
    386        if request.response_type is None: 
    387            raise errors.MissingResponseTypeError(request=request) 
    388        # Value MUST be set to "code" or one of the OpenID authorization code including 
    389        # response_types "code token", "code id_token", "code token id_token" 
    390        elif 'code' not in request.response_type and request.response_type != 'none': 
    391            raise errors.UnsupportedResponseTypeError(request=request) 
    392 
    393        if not self.request_validator.validate_response_type(request.client_id, 
    394                                                             request.response_type, 
    395                                                             request.client, request): 
    396 
    397            log.debug('Client %s is not authorized to use response_type %s.', 
    398                      request.client_id, request.response_type) 
    399            raise errors.UnauthorizedClientError(request=request) 
    400 
    401        # OPTIONAL. Validate PKCE request or reply with "error"/"invalid_request" 
    402        # https://tools.ietf.org/html/rfc6749#section-4.4.1 
    403        if self.request_validator.is_pkce_required(request.client_id, request) is True and request.code_challenge is None: 
    404            raise errors.MissingCodeChallengeError(request=request) 
    405 
    406        if request.code_challenge is not None: 
    407            request_info["code_challenge"] = request.code_challenge 
    408 
    409            # OPTIONAL, defaults to "plain" if not present in the request. 
    410            if request.code_challenge_method is None: 
    411                request.code_challenge_method = "plain" 
    412 
    413            if request.code_challenge_method not in self._code_challenge_methods: 
    414                raise errors.UnsupportedCodeChallengeMethodError(request=request) 
    415            request_info["code_challenge_method"] = request.code_challenge_method 
    416 
    417        # OPTIONAL. The scope of the access request as described by Section 3.3 
    418        # https://tools.ietf.org/html/rfc6749#section-3.3 
    419        self.validate_scopes(request) 
    420 
    421        request_info.update({ 
    422            'client_id': request.client_id, 
    423            'redirect_uri': request.redirect_uri, 
    424            'response_type': request.response_type, 
    425            'state': request.state, 
    426            'request': request 
    427        }) 
    428 
    429        for validator in self.custom_validators.post_auth: 
    430            request_info.update(validator(request)) 
    431 
    432        return request.scopes, request_info 
    433 
    434    def validate_token_request(self, request): 
    435        """ 
    436        :param request: OAuthlib request. 
    437        :type request: oauthlib.common.Request 
    438        """ 
    439        # REQUIRED. Value MUST be set to "authorization_code". 
    440        if request.grant_type not in ('authorization_code', 'openid'): 
    441            raise errors.UnsupportedGrantTypeError(request=request) 
    442 
    443        for validator in self.custom_validators.pre_token: 
    444            validator(request) 
    445 
    446        if request.code is None: 
    447            raise errors.InvalidRequestError( 
    448                description='Missing code parameter.', request=request) 
    449 
    450        for param in ('client_id', 'grant_type', 'redirect_uri'): 
    451            if param in request.duplicate_params: 
    452                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, 
    453                                                 request=request) 
    454 
    455        if self.request_validator.client_authentication_required(request): 
    456            # If the client type is confidential or the client was issued client 
    457            # credentials (or assigned other authentication requirements), the 
    458            # client MUST authenticate with the authorization server as described 
    459            # in Section 3.2.1. 
    460            # https://tools.ietf.org/html/rfc6749#section-3.2.1 
    461            if not self.request_validator.authenticate_client(request): 
    462                log.debug('Client authentication failed, %r.', request) 
    463                raise errors.InvalidClientError(request=request) 
    464        elif not self.request_validator.authenticate_client_id(request.client_id, request): 
    465            # REQUIRED, if the client is not authenticating with the 
    466            # authorization server as described in Section 3.2.1. 
    467            # https://tools.ietf.org/html/rfc6749#section-3.2.1 
    468            log.debug('Client authentication failed, %r.', request) 
    469            raise errors.InvalidClientError(request=request) 
    470 
    471        if not hasattr(request.client, 'client_id'): 
    472            raise NotImplementedError('Authenticate client must set the ' 
    473                                      'request.client.client_id attribute ' 
    474                                      'in authenticate_client.') 
    475 
    476        request.client_id = request.client_id or request.client.client_id 
    477 
    478        # Ensure client is authorized use of this grant type 
    479        self.validate_grant_type(request) 
    480 
    481        # REQUIRED. The authorization code received from the 
    482        # authorization server. 
    483        if not self.request_validator.validate_code(request.client_id, 
    484                                                    request.code, request.client, request): 
    485            log.debug('Client, %r (%r), is not allowed access to scopes %r.', 
    486                      request.client_id, request.client, request.scopes) 
    487            raise errors.InvalidGrantError(request=request) 
    488 
    489        # OPTIONAL. Validate PKCE code_verifier 
    490        challenge = self.request_validator.get_code_challenge(request.code, request) 
    491 
    492        if challenge is not None: 
    493            if request.code_verifier is None: 
    494                raise errors.MissingCodeVerifierError(request=request) 
    495 
    496            challenge_method = self.request_validator.get_code_challenge_method(request.code, request) 
    497            if challenge_method is None: 
    498                raise errors.InvalidGrantError(request=request, description="Challenge method not found") 
    499 
    500            if challenge_method not in self._code_challenge_methods: 
    501                raise errors.ServerError( 
    502                    description="code_challenge_method {} is not supported.".format(challenge_method), 
    503                    request=request 
    504                ) 
    505 
    506            if not self.validate_code_challenge(challenge, 
    507                                                challenge_method, 
    508                                                request.code_verifier): 
    509                log.debug('request provided a invalid code_verifier.') 
    510                raise errors.InvalidGrantError(request=request) 
    511        elif self.request_validator.is_pkce_required(request.client_id, request) is True: 
    512            if request.code_verifier is None: 
    513                raise errors.MissingCodeVerifierError(request=request) 
    514            raise errors.InvalidGrantError(request=request, description="Challenge not found") 
    515 
    516        for attr in ('user', 'scopes'): 
    517            if getattr(request, attr, None) is None: 
    518                log.debug('request.%s was not set on code validation.', attr) 
    519 
    520        # REQUIRED, if the "redirect_uri" parameter was included in the 
    521        # authorization request as described in Section 4.1.1, and their 
    522        # values MUST be identical. 
    523        if request.redirect_uri is None: 
    524            request.using_default_redirect_uri = True 
    525            request.redirect_uri = self.request_validator.get_default_redirect_uri( 
    526                request.client_id, request) 
    527            log.debug('Using default redirect_uri %s.', request.redirect_uri) 
    528            if not request.redirect_uri: 
    529                raise errors.MissingRedirectURIError(request=request) 
    530        else: 
    531            request.using_default_redirect_uri = False 
    532            log.debug('Using provided redirect_uri %s', request.redirect_uri) 
    533 
    534        if not self.request_validator.confirm_redirect_uri(request.client_id, request.code, 
    535                                                           request.redirect_uri, request.client, 
    536                                                           request): 
    537            log.debug('Redirect_uri (%r) invalid for client %r (%r).', 
    538                      request.redirect_uri, request.client_id, request.client) 
    539            raise errors.MismatchingRedirectURIError(request=request) 
    540 
    541        for validator in self.custom_validators.post_token: 
    542            validator(request) 
    543 
    544    def validate_code_challenge(self, challenge, challenge_method, verifier): 
    545        if challenge_method in self._code_challenge_methods: 
    546            return self._code_challenge_methods[challenge_method](verifier, challenge) 
    547        raise NotImplementedError('Unknown challenge_method %s' % challenge_method)