1""" 
    2oauthlib.oauth2.rfc6749.parameters 
    3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    4 
    5This module contains methods related to `Section 4`_ of the OAuth 2 RFC. 
    6 
    7.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4 
    8""" 
    9import json 
    10import os 
    11import time 
    12import urllib.parse as urlparse 
    13 
    14from oauthlib.common import add_params_to_qs, add_params_to_uri 
    15from oauthlib.signals import scope_changed 
    16 
    17from .errors import ( 
    18    InsecureTransportError, MismatchingStateError, MissingCodeError, 
    19    MissingTokenError, MissingTokenTypeError, raise_from_error, 
    20) 
    21from .tokens import OAuth2Token 
    22from .utils import is_secure_transport, list_to_scope, scope_to_list 
    23 
    24 
    25def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, 
    26                      scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs): 
    27    """Prepare the authorization grant request URI. 
    28 
    29    The client constructs the request URI by adding the following 
    30    parameters to the query component of the authorization endpoint URI 
    31    using the ``application/x-www-form-urlencoded`` format as defined by 
    32    [`W3C.REC-html401-19991224`_]: 
    33 
    34    :param uri: 
    35    :param client_id: The client identifier as described in `Section 2.2`_. 
    36    :param response_type: To indicate which OAuth 2 grant/flow is required, 
    37                          "code" and "token". 
    38    :param redirect_uri: The client provided URI to redirect back to after 
    39                         authorization as described in `Section 3.1.2`_. 
    40    :param scope: The scope of the access request as described by 
    41                  `Section 3.3`_. 
    42    :param state: An opaque value used by the client to maintain 
    43                  state between the request and callback.  The authorization 
    44                  server includes this value when redirecting the user-agent 
    45                  back to the client.  The parameter SHOULD be used for 
    46                  preventing cross-site request forgery as described in 
    47                  `Section 10.12`_. 
    48    :param code_challenge: PKCE parameter. A challenge derived from the 
    49                           code_verifier that is sent in the authorization 
    50                           request, to be verified against later. 
    51    :param code_challenge_method: PKCE parameter. A method that was used to derive the 
    52                                  code_challenge. Defaults to "plain" if not present in the request. 
    53    :param kwargs: Extra arguments to embed in the grant/authorization URL. 
    54 
    55    An example of an authorization code grant authorization URL: 
    56 
    57    .. code-block:: http 
    58 
    59        GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz 
    60            &code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256 
    61            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 
    62        Host: server.example.com 
    63 
    64    .. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224 
    65    .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2 
    66    .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2 
    67    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 
    68    .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 
    69    """ 
    70    if not is_secure_transport(uri): 
    71        raise InsecureTransportError() 
    72 
    73    params = [(('response_type', response_type)), 
    74              (('client_id', client_id))] 
    75 
    76    if redirect_uri: 
    77        params.append(('redirect_uri', redirect_uri)) 
    78    if scope: 
    79        params.append(('scope', list_to_scope(scope))) 
    80    if state: 
    81        params.append(('state', state)) 
    82    if code_challenge is not None: 
    83        params.append(('code_challenge', code_challenge)) 
    84        params.append(('code_challenge_method', code_challenge_method)) 
    85 
    86    for k in kwargs: 
    87        if kwargs[k]: 
    88            params.append((str(k), kwargs[k])) 
    89 
    90    return add_params_to_uri(uri, params) 
    91 
    92 
    93def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs): 
    94    """Prepare the access token request. 
    95 
    96    The client makes a request to the token endpoint by adding the 
    97    following parameters using the ``application/x-www-form-urlencoded`` 
    98    format in the HTTP request entity-body: 
    99 
    100    :param grant_type: To indicate grant type being used, i.e. "password", 
    101                       "authorization_code" or "client_credentials". 
    102 
    103    :param body: Existing request body (URL encoded string) to embed parameters 
    104                 into. This may contain extra parameters. Default ''. 
    105 
    106    :param include_client_id: `True` (default) to send the `client_id` in the 
    107                              body of the upstream request. This is required 
    108                              if the client is not authenticating with the 
    109                              authorization server as described in 
    110                              `Section 3.2.1`_. 
    111    :type include_client_id: Boolean 
    112 
    113    :param client_id: Unicode client identifier. Will only appear if 
    114                      `include_client_id` is True. * 
    115 
    116    :param client_secret: Unicode client secret. Will only appear if set to a 
    117                          value that is not `None`. Invoking this function with 
    118                          an empty string will send an empty `client_secret` 
    119                          value to the server. * 
    120 
    121    :param code: If using authorization_code grant, pass the previously 
    122                 obtained authorization code as the ``code`` argument. * 
    123 
    124    :param redirect_uri: If the "redirect_uri" parameter was included in the 
    125                         authorization request as described in 
    126                         `Section 4.1.1`_, and their values MUST be identical. * 
    127 
    128    :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the 
    129                          authorization request to the token request. 
    130 
    131    :param kwargs: Extra arguments to embed in the request body. 
    132 
    133    Parameters marked with a `*` above are not explicit arguments in the 
    134    function signature, but are specially documented arguments for items 
    135    appearing in the generic `**kwargs` keyworded input. 
    136 
    137    An example of an authorization code token request body: 
    138 
    139    .. code-block:: http 
    140 
    141        grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA 
    142        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 
    143 
    144    .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1 
    145    """ 
    146    params = [('grant_type', grant_type)] 
    147 
    148    if 'scope' in kwargs: 
    149        kwargs['scope'] = list_to_scope(kwargs['scope']) 
    150 
    151    # pull the `client_id` out of the kwargs. 
    152    client_id = kwargs.pop('client_id', None) 
    153    if include_client_id and client_id is not None: 
    154        params.append(('client_id', client_id)) 
    155 
    156    # use code_verifier if code_challenge was passed in the authorization request 
    157    if code_verifier is not None: 
    158        params.append(('code_verifier', code_verifier)) 
    159 
    160    # the kwargs iteration below only supports including boolean truth (truthy) 
    161    # values, but some servers may require an empty string for `client_secret` 
    162    client_secret = kwargs.pop('client_secret', None) 
    163    if client_secret is not None: 
    164        params.append(('client_secret', client_secret)) 
    165 
    166    # this handles: `code`, `redirect_uri`, and other undocumented params 
    167    for k in kwargs: 
    168        if kwargs[k]: 
    169            params.append((str(k), kwargs[k])) 
    170 
    171    return add_params_to_qs(body, params) 
    172 
    173 
    174def prepare_token_revocation_request(url, token, token_type_hint="access_token", 
    175        callback=None, body='', **kwargs): 
    176    """Prepare a token revocation request. 
    177 
    178    The client constructs the request by including the following parameters 
    179    using the ``application/x-www-form-urlencoded`` format in the HTTP request 
    180    entity-body: 
    181 
    182    :param token: REQUIRED.  The token that the client wants to get revoked. 
    183 
    184    :param token_type_hint: OPTIONAL.  A hint about the type of the token 
    185                            submitted for revocation. Clients MAY pass this 
    186                            parameter in order to help the authorization server 
    187                            to optimize the token lookup.  If the server is 
    188                            unable to locate the token using the given hint, it 
    189                            MUST extend its search across all of its supported 
    190                            token types.  An authorization server MAY ignore 
    191                            this parameter, particularly if it is able to detect 
    192                            the token type automatically. 
    193 
    194    This specification defines two values for `token_type_hint`: 
    195 
    196        * access_token: An access token as defined in [RFC6749], 
    197             `Section 1.4`_ 
    198 
    199        * refresh_token: A refresh token as defined in [RFC6749], 
    200             `Section 1.5`_ 
    201 
    202        Specific implementations, profiles, and extensions of this 
    203        specification MAY define other values for this parameter using the 
    204        registry defined in `Section 4.1.2`_. 
    205 
    206    .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4 
    207    .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5 
    208    .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2 
    209 
    210    """ 
    211    if not is_secure_transport(url): 
    212        raise InsecureTransportError() 
    213 
    214    params = [('token', token)] 
    215 
    216    if token_type_hint: 
    217        params.append(('token_type_hint', token_type_hint)) 
    218 
    219    for k in kwargs: 
    220        if kwargs[k]: 
    221            params.append((str(k), kwargs[k])) 
    222 
    223    headers = {'Content-Type': 'application/x-www-form-urlencoded'} 
    224 
    225    if callback: 
    226        params.append(('callback', callback)) 
    227        return add_params_to_uri(url, params), headers, body 
    228    else: 
    229        return url, headers, add_params_to_qs(body, params) 
    230 
    231 
    232def parse_authorization_code_response(uri, state=None): 
    233    """Parse authorization grant response URI into a dict. 
    234 
    235    If the resource owner grants the access request, the authorization 
    236    server issues an authorization code and delivers it to the client by 
    237    adding the following parameters to the query component of the 
    238    redirection URI using the ``application/x-www-form-urlencoded`` format: 
    239 
    240    **code** 
    241            REQUIRED.  The authorization code generated by the 
    242            authorization server.  The authorization code MUST expire 
    243            shortly after it is issued to mitigate the risk of leaks.  A 
    244            maximum authorization code lifetime of 10 minutes is 
    245            RECOMMENDED.  The client MUST NOT use the authorization code 
    246            more than once.  If an authorization code is used more than 
    247            once, the authorization server MUST deny the request and SHOULD 
    248            revoke (when possible) all tokens previously issued based on 
    249            that authorization code.  The authorization code is bound to 
    250            the client identifier and redirection URI. 
    251 
    252    **state** 
    253            REQUIRED if the "state" parameter was present in the client 
    254            authorization request.  The exact value received from the 
    255            client. 
    256 
    257    :param uri: The full redirect URL back to the client. 
    258    :param state: The state parameter from the authorization request. 
    259 
    260    For example, the authorization server redirects the user-agent by 
    261    sending the following HTTP response: 
    262 
    263    .. code-block:: http 
    264 
    265        HTTP/1.1 302 Found 
    266        Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA 
    267                &state=xyz 
    268 
    269    """ 
    270    if not is_secure_transport(uri): 
    271        raise InsecureTransportError() 
    272 
    273    query = urlparse.urlparse(uri).query 
    274    params = dict(urlparse.parse_qsl(query)) 
    275 
    276    if state and params.get('state') != state: 
    277        raise MismatchingStateError() 
    278 
    279    if 'error' in params: 
    280        raise_from_error(params.get('error'), params) 
    281 
    282    if 'code' not in params: 
    283        raise MissingCodeError("Missing code parameter in response.") 
    284 
    285    return params 
    286 
    287 
    288def parse_implicit_response(uri, state=None, scope=None): 
    289    """Parse the implicit token response URI into a dict. 
    290 
    291    If the resource owner grants the access request, the authorization 
    292    server issues an access token and delivers it to the client by adding 
    293    the following parameters to the fragment component of the redirection 
    294    URI using the ``application/x-www-form-urlencoded`` format: 
    295 
    296    **access_token** 
    297            REQUIRED.  The access token issued by the authorization server. 
    298 
    299    **token_type** 
    300            REQUIRED.  The type of the token issued as described in 
    301            Section 7.1.  Value is case insensitive. 
    302 
    303    **expires_in** 
    304            RECOMMENDED.  The lifetime in seconds of the access token.  For 
    305            example, the value "3600" denotes that the access token will 
    306            expire in one hour from the time the response was generated. 
    307            If omitted, the authorization server SHOULD provide the 
    308            expiration time via other means or document the default value. 
    309 
    310    **scope** 
    311            OPTIONAL, if identical to the scope requested by the client, 
    312            otherwise REQUIRED.  The scope of the access token as described 
    313            by Section 3.3. 
    314 
    315    **state** 
    316            REQUIRED if the "state" parameter was present in the client 
    317            authorization request.  The exact value received from the 
    318            client. 
    319 
    320    :param uri: 
    321    :param state: 
    322    :param scope: 
    323 
    324    Similar to the authorization code response, but with a full token provided 
    325    in the URL fragment: 
    326 
    327    .. code-block:: http 
    328 
    329        HTTP/1.1 302 Found 
    330        Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 
    331                &state=xyz&token_type=example&expires_in=3600 
    332    """ 
    333    if not is_secure_transport(uri): 
    334        raise InsecureTransportError() 
    335 
    336    fragment = urlparse.urlparse(uri).fragment 
    337    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True)) 
    338 
    339    if 'scope' in params: 
    340        params['scope'] = scope_to_list(params['scope']) 
    341 
    342    vin, vat, v_at = parse_expires(params) 
    343    if vin: 
    344        params['expires_in'] = vin 
    345    elif 'expires_in' in params: 
    346        params.pop('expires_in') 
    347    if vat: 
    348        params['expires_at'] = vat 
    349    elif 'expires_at' in params: 
    350        params.pop('expires_at') 
    351 
    352    if state and params.get('state') != state: 
    353        raise ValueError("Mismatching or missing state in params.") 
    354 
    355    params = OAuth2Token(params, old_scope=scope) 
    356    validate_token_parameters(params) 
    357    return params 
    358 
    359 
    360def parse_token_response(body, scope=None): 
    361    """Parse the JSON token response body into a dict. 
    362 
    363    The authorization server issues an access token and optional refresh 
    364    token, and constructs the response by adding the following parameters 
    365    to the entity body of the HTTP response with a 200 (OK) status code: 
    366 
    367    access_token 
    368            REQUIRED.  The access token issued by the authorization server. 
    369    token_type 
    370            REQUIRED.  The type of the token issued as described in 
    371            `Section 7.1`_.  Value is case insensitive. 
    372    expires_in 
    373            RECOMMENDED.  The lifetime in seconds of the access token.  For 
    374            example, the value "3600" denotes that the access token will 
    375            expire in one hour from the time the response was generated. 
    376            If omitted, the authorization server SHOULD provide the 
    377            expiration time via other means or document the default value. 
    378    refresh_token 
    379            OPTIONAL.  The refresh token which can be used to obtain new 
    380            access tokens using the same authorization grant as described 
    381            in `Section 6`_. 
    382    scope 
    383            OPTIONAL, if identical to the scope requested by the client, 
    384            otherwise REQUIRED.  The scope of the access token as described 
    385            by `Section 3.3`_. 
    386 
    387    The parameters are included in the entity body of the HTTP response 
    388    using the "application/json" media type as defined by [`RFC4627`_].  The 
    389    parameters are serialized into a JSON structure by adding each 
    390    parameter at the highest structure level.  Parameter names and string 
    391    values are included as JSON strings.  Numerical values are included 
    392    as JSON numbers.  The order of parameters does not matter and can 
    393    vary. 
    394 
    395    :param body: The full json encoded response body. 
    396    :param scope: The scope requested during authorization. 
    397 
    398    For example: 
    399 
    400    .. code-block:: http 
    401 
    402        HTTP/1.1 200 OK 
    403        Content-Type: application/json 
    404        Cache-Control: no-store 
    405        Pragma: no-cache 
    406 
    407        { 
    408            "access_token":"2YotnFZFEjr1zCsicMWpAA", 
    409            "token_type":"example", 
    410            "expires_in":3600, 
    411            "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", 
    412            "example_parameter":"example_value" 
    413        } 
    414 
    415    .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1 
    416    .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6 
    417    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 
    418    .. _`RFC4627`: https://tools.ietf.org/html/rfc4627 
    419    """ 
    420    try: 
    421        params = json.loads(body) 
    422    except ValueError: 
    423 
    424        # Fall back to URL-encoded string, to support old implementations, 
    425        # including (at time of writing) Facebook. See: 
    426        #   https://github.com/oauthlib/oauthlib/issues/267 
    427 
    428        params = dict(urlparse.parse_qsl(body)) 
    429 
    430    if 'scope' in params: 
    431        params['scope'] = scope_to_list(params['scope']) 
    432 
    433    vin, vat, v_at = parse_expires(params) 
    434    if vin: 
    435        params['expires_in'] = vin 
    436    elif 'expires_in' in params: 
    437        params.pop('expires_in') 
    438    if vat: 
    439        params['expires_at'] = vat 
    440    elif 'expires_at' in params: 
    441        params.pop('expires_at') 
    442 
    443    params = OAuth2Token(params, old_scope=scope) 
    444    validate_token_parameters(params) 
    445    return params 
    446 
    447 
    448def validate_token_parameters(params): 
    449    """Ensures token presence, token type, expiration and scope in params.""" 
    450    if 'error' in params: 
    451        raise_from_error(params.get('error'), params) 
    452 
    453    if 'access_token' not in params: 
    454        raise MissingTokenError(description="Missing access token parameter.") 
    455 
    456    if 'token_type' not in params and os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): 
    457        raise MissingTokenTypeError() 
    458 
    459    # If the issued access token scope is different from the one requested by 
    460    # the client, the authorization server MUST include the "scope" response 
    461    # parameter to inform the client of the actual scope granted. 
    462    # https://tools.ietf.org/html/rfc6749#section-3.3 
    463    if params.scope_changed: 
    464        message = 'Scope has changed from "{old}" to "{new}".'.format( 
    465            old=params.old_scope, new=params.scope, 
    466        ) 
    467        scope_changed.send(message=message, old=params.old_scopes, new=params.scopes) 
    468        if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None): 
    469            w = Warning(message) 
    470            w.token = params 
    471            w.old_scope = params.old_scopes 
    472            w.new_scope = params.scopes 
    473            raise w 
    474 
    475def parse_expires(params): 
    476    """Parse `expires_in`, `expires_at` fields from params 
    477 
    478    Parse following these rules: 
    479    - `expires_in` must be either integer, float or None. If a float, it is converted into an integer. 
    480    - `expires_at` is not in specification so it does its best to: 
    481      - convert into a int, else 
    482      - convert into a float, else 
    483      - reuse the same type as-is (usually string) 
    484    - `_expires_at` is a special internal value returned to be always an `int`, based 
    485    either on the presence of `expires_at`, or reuse the current time plus 
    486    `expires_in`. This is typically used to validate token expiry. 
    487 
    488    :param params: Dict with expires_in and expires_at optionally set 
    489    :return: Tuple of `expires_in`, `expires_at`, and `_expires_at`. None if not set. 
    490    """ 
    491    expires_in = None 
    492    expires_at = None 
    493    _expires_at = None 
    494 
    495    if 'expires_in' in params: 
    496        if isinstance(params.get('expires_in'), int): 
    497            expires_in = params.get('expires_in') 
    498        elif isinstance(params.get('expires_in'), float): 
    499            expires_in = int(params.get('expires_in')) 
    500        elif isinstance(params.get('expires_in'), str): 
    501            try: 
    502                # Attempt to convert to int 
    503                expires_in = int(params.get('expires_in')) 
    504            except ValueError: 
    505                raise ValueError("expires_in must be an int") 
    506        elif params.get('expires_in') is not None: 
    507            raise ValueError("expires_in must be an int") 
    508 
    509    if 'expires_at' in params: 
    510        if isinstance(params.get('expires_at'), (float, int)): 
    511            expires_at = params.get('expires_at') 
    512            _expires_at = expires_at 
    513        elif isinstance(params.get('expires_at'), str): 
    514            try: 
    515                # Attempt to convert to int first, then float if int fails 
    516                expires_at = int(params.get('expires_at')) 
    517                _expires_at = expires_at 
    518            except ValueError: 
    519                try: 
    520                    expires_at = float(params.get('expires_at')) 
    521                    _expires_at = expires_at 
    522                except ValueError: 
    523                    # no change from str 
    524                    expires_at = params.get('expires_at') 
    525    if _expires_at is None and expires_in: 
    526        expires_at = round(time.time()) + expires_in 
    527        _expires_at = expires_at 
    528    return expires_in, expires_at, _expires_at