Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_client.py508 linesDownload Raw Back to oauth2
1# Copyright 2016 Google LLC2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#      http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15"""OAuth 2.0 client.16 17This is a client for interacting with an OAuth 2.0 authorization server's18token endpoint.19 20For more information about the token endpoint, see21`Section 3.1 of rfc6749`_22 23.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.224"""25 26import datetime27import http.client as http_client28import json29import urllib30 31from google.auth import _exponential_backoff32from google.auth import _helpers33from google.auth import exceptions34from google.auth import jwt35from google.auth import metrics36from google.auth import transport37 38_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"39_JSON_CONTENT_TYPE = "application/json"40_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"41_REFRESH_GRANT_TYPE = "refresh_token"42_IAM_IDTOKEN_ENDPOINT = (43    "https://iamcredentials.googleapis.com/v1/"44    + "projects/-/serviceAccounts/{}:generateIdToken"45)46 47 48def _handle_error_response(response_data, retryable_error):49    """Translates an error response into an exception.50 51    Args:52        response_data (Mapping | str): The decoded response data.53        retryable_error Optional[bool]: A boolean indicating if an error is retryable.54            Defaults to False.55 56    Raises:57        google.auth.exceptions.RefreshError: The errors contained in response_data.58    """59 60    retryable_error = retryable_error if retryable_error else False61 62    if isinstance(response_data, str):63        raise exceptions.RefreshError(response_data, retryable=retryable_error)64    try:65        error_details = "{}: {}".format(66            response_data["error"], response_data.get("error_description")67        )68    # If no details could be extracted, use the response data.69    except (KeyError, ValueError):70        error_details = json.dumps(response_data)71 72    raise exceptions.RefreshError(73        error_details, response_data, retryable=retryable_error74    )75 76 77def _can_retry(status_code, response_data):78    """Checks if a request can be retried by inspecting the status code79    and response body of the request.80 81    Args:82        status_code (int): The response status code.83        response_data (Mapping | str): The decoded response data.84 85    Returns:86      bool: True if the response is retryable. False otherwise.87    """88    if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES:89        return True90 91    try:92        # For a failed response, response_body could be a string93        error_desc = response_data.get("error_description") or ""94        error_code = response_data.get("error") or ""95 96        if not isinstance(error_code, str) or not isinstance(error_desc, str):97            return False98 99        # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1100        # This is needed because a redirect will not return a 500 status code.101        retryable_error_descriptions = {102            "internal_failure",103            "server_error",104            "temporarily_unavailable",105        }106 107        if any(e in retryable_error_descriptions for e in (error_code, error_desc)):108            return True109 110    except AttributeError:111        pass112 113    return False114 115 116def _parse_expiry(response_data):117    """Parses the expiry field from a response into a datetime.118 119    Args:120        response_data (Mapping): The JSON-parsed response data.121 122    Returns:123        Optional[datetime]: The expiration or ``None`` if no expiration was124            specified.125    """126    expires_in = response_data.get("expires_in", None)127 128    if expires_in is not None:129        # Some services do not respect the OAUTH2.0 RFC and send expires_in as a130        # JSON String.131        if isinstance(expires_in, str):132            expires_in = int(expires_in)133 134        return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)135    else:136        return None137 138 139def _token_endpoint_request_no_throw(140    request,141    token_uri,142    body,143    access_token=None,144    use_json=False,145    can_retry=True,146    headers=None,147    **kwargs148):149    """Makes a request to the OAuth 2.0 authorization server's token endpoint.150    This function doesn't throw on response errors.151 152    Args:153        request (google.auth.transport.Request): A callable used to make154            HTTP requests.155        token_uri (str): The OAuth 2.0 authorizations server's token endpoint156            URI.157        body (Mapping[str, str]): The parameters to send in the request body.158        access_token (Optional(str)): The access token needed to make the request.159        use_json (Optional(bool)): Use urlencoded format or json format for the160            content type. The default value is False.161        can_retry (bool): Enable or disable request retry behavior.162        headers (Optional[Mapping[str, str]]): The headers for the request.163        kwargs: Additional arguments passed on to the request method. The164            kwargs will be passed to `requests.request` method, see:165            https://docs.python-requests.org/en/latest/api/#requests.request.166            For example, you can use `cert=("cert_pem_path", "key_pem_path")`167            to set up client side SSL certificate, and use168            `verify="ca_bundle_path"` to set up the CA certificates for sever169            side SSL certificate verification.170 171    Returns:172        Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating173          if the request is successful, a mapping for the JSON-decoded response174          data and in the case of an error a boolean indicating if the error175          is retryable.176    """177    if use_json:178        headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE}179        body = json.dumps(body).encode("utf-8")180    else:181        headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE}182        body = urllib.parse.urlencode(body).encode("utf-8")183 184    if access_token:185        headers_to_use["Authorization"] = "Bearer {}".format(access_token)186 187    if headers:188        headers_to_use.update(headers)189 190    def _perform_request():191        response = request(192            method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs193        )194        response_body = (195            response.data.decode("utf-8")196            if hasattr(response.data, "decode")197            else response.data198        )199        response_data = ""200        try:201            # response_body should be a JSON202            response_data = json.loads(response_body)203        except ValueError:204            response_data = response_body205 206        if response.status == http_client.OK:207            return True, response_data, None208 209        retryable_error = _can_retry(210            status_code=response.status, response_data=response_data211        )212 213        return False, response_data, retryable_error214 215    request_succeeded, response_data, retryable_error = _perform_request()216 217    if request_succeeded or not retryable_error or not can_retry:218        return request_succeeded, response_data, retryable_error219 220    retries = _exponential_backoff.ExponentialBackoff()221    for _ in retries:222        request_succeeded, response_data, retryable_error = _perform_request()223        if request_succeeded or not retryable_error:224            return request_succeeded, response_data, retryable_error225 226    return False, response_data, retryable_error227 228 229def _token_endpoint_request(230    request,231    token_uri,232    body,233    access_token=None,234    use_json=False,235    can_retry=True,236    headers=None,237    **kwargs238):239    """Makes a request to the OAuth 2.0 authorization server's token endpoint.240 241    Args:242        request (google.auth.transport.Request): A callable used to make243            HTTP requests.244        token_uri (str): The OAuth 2.0 authorizations server's token endpoint245            URI.246        body (Mapping[str, str]): The parameters to send in the request body.247        access_token (Optional(str)): The access token needed to make the request.248        use_json (Optional(bool)): Use urlencoded format or json format for the249            content type. The default value is False.250        can_retry (bool): Enable or disable request retry behavior.251        headers (Optional[Mapping[str, str]]): The headers for the request.252        kwargs: Additional arguments passed on to the request method. The253            kwargs will be passed to `requests.request` method, see:254            https://docs.python-requests.org/en/latest/api/#requests.request.255            For example, you can use `cert=("cert_pem_path", "key_pem_path")`256            to set up client side SSL certificate, and use257            `verify="ca_bundle_path"` to set up the CA certificates for sever258            side SSL certificate verification.259 260    Returns:261        Mapping[str, str]: The JSON-decoded response data.262 263    Raises:264        google.auth.exceptions.RefreshError: If the token endpoint returned265            an error.266    """267 268    response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw(269        request,270        token_uri,271        body,272        access_token=access_token,273        use_json=use_json,274        can_retry=can_retry,275        headers=headers,276        **kwargs277    )278    if not response_status_ok:279        _handle_error_response(response_data, retryable_error)280    return response_data281 282 283def jwt_grant(request, token_uri, assertion, can_retry=True):284    """Implements the JWT Profile for OAuth 2.0 Authorization Grants.285 286    For more details, see `rfc7523 section 4`_.287 288    Args:289        request (google.auth.transport.Request): A callable used to make290            HTTP requests.291        token_uri (str): The OAuth 2.0 authorizations server's token endpoint292            URI.293        assertion (str): The OAuth 2.0 assertion.294        can_retry (bool): Enable or disable request retry behavior.295 296    Returns:297        Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,298            expiration, and additional data returned by the token endpoint.299 300    Raises:301        google.auth.exceptions.RefreshError: If the token endpoint returned302            an error.303 304    .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4305    """306    body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}307 308    response_data = _token_endpoint_request(309        request,310        token_uri,311        body,312        can_retry=can_retry,313        headers={314            metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion()315        },316    )317 318    try:319        access_token = response_data["access_token"]320    except KeyError as caught_exc:321        new_exc = exceptions.RefreshError(322            "No access token in response.", response_data, retryable=False323        )324        raise new_exc from caught_exc325 326    expiry = _parse_expiry(response_data)327 328    return access_token, expiry, response_data329 330 331def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_token):332    """Call iam.generateIdToken endpoint to get ID token.333 334    Args:335        request (google.auth.transport.Request): A callable used to make336            HTTP requests.337        signer_email (str): The signer email used to form the IAM338            generateIdToken endpoint.339        audience (str): The audience for the ID token.340        access_token (str): The access token used to call the IAM endpoint.341 342    Returns:343        Tuple[str, datetime]: The ID token and expiration.344    """345    body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"}346 347    response_data = _token_endpoint_request(348        request,349        _IAM_IDTOKEN_ENDPOINT.format(signer_email),350        body,351        access_token=access_token,352        use_json=True,353    )354 355    try:356        id_token = response_data["token"]357    except KeyError as caught_exc:358        new_exc = exceptions.RefreshError(359            "No ID token in response.", response_data, retryable=False360        )361        raise new_exc from caught_exc362 363    payload = jwt.decode(id_token, verify=False)364    expiry = datetime.datetime.utcfromtimestamp(payload["exp"])365 366    return id_token, expiry367 368 369def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):370    """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but371    requests an OpenID Connect ID Token instead of an access token.372 373    This is a variant on the standard JWT Profile that is currently unique374    to Google. This was added for the benefit of authenticating to services375    that require ID Tokens instead of access tokens or JWT bearer tokens.376 377    Args:378        request (google.auth.transport.Request): A callable used to make379            HTTP requests.380        token_uri (str): The OAuth 2.0 authorization server's token endpoint381            URI.382        assertion (str): JWT token signed by a service account. The token's383            payload must include a ``target_audience`` claim.384        can_retry (bool): Enable or disable request retry behavior.385 386    Returns:387        Tuple[str, Optional[datetime], Mapping[str, str]]:388            The (encoded) Open ID Connect ID Token, expiration, and additional389            data returned by the endpoint.390 391    Raises:392        google.auth.exceptions.RefreshError: If the token endpoint returned393            an error.394    """395    body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}396 397    response_data = _token_endpoint_request(398        request,399        token_uri,400        body,401        can_retry=can_retry,402        headers={403            metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion()404        },405    )406 407    try:408        id_token = response_data["id_token"]409    except KeyError as caught_exc:410        new_exc = exceptions.RefreshError(411            "No ID token in response.", response_data, retryable=False412        )413        raise new_exc from caught_exc414 415    payload = jwt.decode(id_token, verify=False)416    expiry = datetime.datetime.utcfromtimestamp(payload["exp"])417 418    return id_token, expiry, response_data419 420 421def _handle_refresh_grant_response(response_data, refresh_token):422    """Extract tokens from refresh grant response.423 424    Args:425        response_data (Mapping[str, str]): Refresh grant response data.426        refresh_token (str): Current refresh token.427 428    Returns:429        Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token,430            refresh token, expiration, and additional data returned by the token431            endpoint. If response_data doesn't have refresh token, then the current432            refresh token will be returned.433 434    Raises:435        google.auth.exceptions.RefreshError: If the token endpoint returned436            an error.437    """438    try:439        access_token = response_data["access_token"]440    except KeyError as caught_exc:441        new_exc = exceptions.RefreshError(442            "No access token in response.", response_data, retryable=False443        )444        raise new_exc from caught_exc445 446    refresh_token = response_data.get("refresh_token", refresh_token)447    expiry = _parse_expiry(response_data)448 449    return access_token, refresh_token, expiry, response_data450 451 452def refresh_grant(453    request,454    token_uri,455    refresh_token,456    client_id,457    client_secret,458    scopes=None,459    rapt_token=None,460    can_retry=True,461):462    """Implements the OAuth 2.0 refresh token grant.463 464    For more details, see `rfc678 section 6`_.465 466    Args:467        request (google.auth.transport.Request): A callable used to make468            HTTP requests.469        token_uri (str): The OAuth 2.0 authorizations server's token endpoint470            URI.471        refresh_token (str): The refresh token to use to get a new access472            token.473        client_id (str): The OAuth 2.0 application's client ID.474        client_secret (str): The Oauth 2.0 appliaction's client secret.475        scopes (Optional(Sequence[str])): Scopes to request. If present, all476            scopes must be authorized for the refresh token. Useful if refresh477            token has a wild card scope (e.g.478            'https://www.googleapis.com/auth/any-api').479        rapt_token (Optional(str)): The reauth Proof Token.480        can_retry (bool): Enable or disable request retry behavior.481 482    Returns:483        Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access484            token, new or current refresh token, expiration, and additional data485            returned by the token endpoint.486 487    Raises:488        google.auth.exceptions.RefreshError: If the token endpoint returned489            an error.490 491    .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6492    """493    body = {494        "grant_type": _REFRESH_GRANT_TYPE,495        "client_id": client_id,496        "client_secret": client_secret,497        "refresh_token": refresh_token,498    }499    if scopes:500        body["scope"] = " ".join(scopes)501    if rapt_token:502        body["rapt"] = rapt_token503 504    response_data = _token_endpoint_request(505        request, token_uri, body, can_retry=can_retry506    )507    return _handle_refresh_grant_response(response_data, refresh_token)508 
codekingpro/portable-devtools · Team Ai