codekingpro/portable-devtools
114k
1# Copyright 2020 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 async 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 exceptions33from google.auth import jwt34from google.oauth2 import _client as client35 36 37async def _token_endpoint_request_no_throw(38 request, token_uri, body, access_token=None, use_json=False, can_retry=True39):40 """Makes a request to the OAuth 2.0 authorization server's token endpoint.41 This function doesn't throw on response errors.42 43 Args:44 request (google.auth.transport.Request): A callable used to make45 HTTP requests.46 token_uri (str): The OAuth 2.0 authorizations server's token endpoint47 URI.48 body (Mapping[str, str]): The parameters to send in the request body.49 access_token (Optional(str)): The access token needed to make the request.50 use_json (Optional(bool)): Use urlencoded format or json format for the51 content type. The default value is False.52 can_retry (bool): Enable or disable request retry behavior.53 54 Returns:55 Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating56 if the request is successful, a mapping for the JSON-decoded response57 data and in the case of an error a boolean indicating if the error58 is retryable.59 """60 if use_json:61 headers = {"Content-Type": client._JSON_CONTENT_TYPE}62 body = json.dumps(body).encode("utf-8")63 else:64 headers = {"Content-Type": client._URLENCODED_CONTENT_TYPE}65 body = urllib.parse.urlencode(body).encode("utf-8")66 67 if access_token:68 headers["Authorization"] = "Bearer {}".format(access_token)69 70 async def _perform_request():71 response = await request(72 method="POST", url=token_uri, headers=headers, body=body73 )74 75 # Using data.read() resulted in zlib decompression errors. This may require future investigation.76 response_body1 = await response.content()77 78 response_body = (79 response_body1.decode("utf-8")80 if hasattr(response_body1, "decode")81 else response_body182 )83 84 try:85 response_data = json.loads(response_body)86 except ValueError:87 response_data = response_body88 89 if response.status == http_client.OK:90 return True, response_data, None91 92 retryable_error = client._can_retry(93 status_code=response.status, response_data=response_data94 )95 96 return False, response_data, retryable_error97 98 request_succeeded, response_data, retryable_error = await _perform_request()99 100 if request_succeeded or not retryable_error or not can_retry:101 return request_succeeded, response_data, retryable_error102 103 retries = _exponential_backoff.ExponentialBackoff()104 for _ in retries:105 request_succeeded, response_data, retryable_error = await _perform_request()106 if request_succeeded or not retryable_error:107 return request_succeeded, response_data, retryable_error108 109 return False, response_data, retryable_error110 111 112async def _token_endpoint_request(113 request, token_uri, body, access_token=None, use_json=False, can_retry=True114):115 """Makes a request to the OAuth 2.0 authorization server's token endpoint.116 117 Args:118 request (google.auth.transport.Request): A callable used to make119 HTTP requests.120 token_uri (str): The OAuth 2.0 authorizations server's token endpoint121 URI.122 body (Mapping[str, str]): The parameters to send in the request body.123 access_token (Optional(str)): The access token needed to make the request.124 use_json (Optional(bool)): Use urlencoded format or json format for the125 content type. The default value is False.126 can_retry (bool): Enable or disable request retry behavior.127 128 Returns:129 Mapping[str, str]: The JSON-decoded response data.130 131 Raises:132 google.auth.exceptions.RefreshError: If the token endpoint returned133 an error.134 """135 136 response_status_ok, response_data, retryable_error = await _token_endpoint_request_no_throw(137 request,138 token_uri,139 body,140 access_token=access_token,141 use_json=use_json,142 can_retry=can_retry,143 )144 if not response_status_ok:145 client._handle_error_response(response_data, retryable_error)146 return response_data147 148 149async def jwt_grant(request, token_uri, assertion, can_retry=True):150 """Implements the JWT Profile for OAuth 2.0 Authorization Grants.151 152 For more details, see `rfc7523 section 4`_.153 154 Args:155 request (google.auth.transport.Request): A callable used to make156 HTTP requests.157 token_uri (str): The OAuth 2.0 authorizations server's token endpoint158 URI.159 assertion (str): The OAuth 2.0 assertion.160 can_retry (bool): Enable or disable request retry behavior.161 162 Returns:163 Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,164 expiration, and additional data returned by the token endpoint.165 166 Raises:167 google.auth.exceptions.RefreshError: If the token endpoint returned168 an error.169 170 .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4171 """172 body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}173 174 response_data = await _token_endpoint_request(175 request, token_uri, body, can_retry=can_retry176 )177 178 try:179 access_token = response_data["access_token"]180 except KeyError as caught_exc:181 new_exc = exceptions.RefreshError(182 "No access token in response.", response_data, retryable=False183 )184 raise new_exc from caught_exc185 186 expiry = client._parse_expiry(response_data)187 188 return access_token, expiry, response_data189 190 191async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):192 """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but193 requests an OpenID Connect ID Token instead of an access token.194 195 This is a variant on the standard JWT Profile that is currently unique196 to Google. This was added for the benefit of authenticating to services197 that require ID Tokens instead of access tokens or JWT bearer tokens.198 199 Args:200 request (google.auth.transport.Request): A callable used to make201 HTTP requests.202 token_uri (str): The OAuth 2.0 authorization server's token endpoint203 URI.204 assertion (str): JWT token signed by a service account. The token's205 payload must include a ``target_audience`` claim.206 can_retry (bool): Enable or disable request retry behavior.207 208 Returns:209 Tuple[str, Optional[datetime], Mapping[str, str]]:210 The (encoded) Open ID Connect ID Token, expiration, and additional211 data returned by the endpoint.212 213 Raises:214 google.auth.exceptions.RefreshError: If the token endpoint returned215 an error.216 """217 body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}218 219 response_data = await _token_endpoint_request(220 request, token_uri, body, can_retry=can_retry221 )222 223 try:224 id_token = response_data["id_token"]225 except KeyError as caught_exc:226 new_exc = exceptions.RefreshError(227 "No ID token in response.", response_data, retryable=False228 )229 raise new_exc from caught_exc230 231 payload = jwt.decode(id_token, verify=False)232 expiry = datetime.datetime.utcfromtimestamp(payload["exp"])233 234 return id_token, expiry, response_data235 236 237async def refresh_grant(238 request,239 token_uri,240 refresh_token,241 client_id,242 client_secret,243 scopes=None,244 rapt_token=None,245 can_retry=True,246):247 """Implements the OAuth 2.0 refresh token grant.248 249 For more details, see `rfc678 section 6`_.250 251 Args:252 request (google.auth.transport.Request): A callable used to make253 HTTP requests.254 token_uri (str): The OAuth 2.0 authorizations server's token endpoint255 URI.256 refresh_token (str): The refresh token to use to get a new access257 token.258 client_id (str): The OAuth 2.0 application's client ID.259 client_secret (str): The Oauth 2.0 appliaction's client secret.260 scopes (Optional(Sequence[str])): Scopes to request. If present, all261 scopes must be authorized for the refresh token. Useful if refresh262 token has a wild card scope (e.g.263 'https://www.googleapis.com/auth/any-api').264 rapt_token (Optional(str)): The reauth Proof Token.265 can_retry (bool): Enable or disable request retry behavior.266 267 Returns:268 Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The269 access token, new or current refresh token, expiration, and additional data270 returned by the token endpoint.271 272 Raises:273 google.auth.exceptions.RefreshError: If the token endpoint returned274 an error.275 276 .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6277 """278 body = {279 "grant_type": client._REFRESH_GRANT_TYPE,280 "client_id": client_id,281 "client_secret": client_secret,282 "refresh_token": refresh_token,283 }284 if scopes:285 body["scope"] = " ".join(scopes)286 if rapt_token:287 body["rapt"] = rapt_token288 289 response_data = await _token_endpoint_request(290 request, token_uri, body, can_retry=can_retry291 )292 return client._handle_refresh_grant_response(response_data, refresh_token)293 