Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/oauth2/_client.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

125 statements  

1# Copyright 2016 Google LLC 

2# 

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 at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# 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 and 

13# limitations under the License. 

14 

15"""OAuth 2.0 client. 

16 

17This is a client for interacting with an OAuth 2.0 authorization server's 

18token endpoint. 

19 

20For more information about the token endpoint, see 

21`Section 3.1 of rfc6749`_ 

22 

23.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2 

24""" 

25 

26import datetime 

27import http.client as http_client 

28import json 

29import urllib 

30 

31from google.auth import _exponential_backoff 

32from google.auth import _helpers 

33from google.auth import exceptions 

34from google.auth import jwt 

35from google.auth import metrics 

36from google.auth import transport 

37 

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 

43 

44def _handle_error_response(response_data, retryable_error): 

45 """Translates an error response into an exception. 

46 

47 Args: 

48 response_data (Mapping | str): The decoded response data. 

49 retryable_error Optional[bool]: A boolean indicating if an error is retryable. 

50 Defaults to False. 

51 

52 Raises: 

53 google.auth.exceptions.RefreshError: The errors contained in response_data. 

54 """ 

55 

56 retryable_error = retryable_error if retryable_error else False 

57 

58 if isinstance(response_data, str): 

59 raise exceptions.RefreshError(response_data, retryable=retryable_error) 

60 try: 

61 error_details = "{}: {}".format( 

62 response_data["error"], response_data.get("error_description") 

63 ) 

64 # If no details could be extracted, use the response data. 

65 except (KeyError, ValueError): 

66 error_details = json.dumps(response_data) 

67 

68 raise exceptions.RefreshError( 

69 error_details, response_data, retryable=retryable_error 

70 ) 

71 

72 

73def _can_retry(status_code, response_data): 

74 """Checks if a request can be retried by inspecting the status code 

75 and response body of the request. 

76 

77 Args: 

78 status_code (int): The response status code. 

79 response_data (Mapping | str): The decoded response data. 

80 

81 Returns: 

82 bool: True if the response is retryable. False otherwise. 

83 """ 

84 if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES: 

85 return True 

86 

87 try: 

88 # For a failed response, response_body could be a string 

89 error_desc = response_data.get("error_description") or "" 

90 error_code = response_data.get("error") or "" 

91 

92 if not isinstance(error_code, str) or not isinstance(error_desc, str): 

93 return False 

94 

95 # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1 

96 # This is needed because a redirect will not return a 500 status code. 

97 retryable_error_descriptions = { 

98 "internal_failure", 

99 "server_error", 

100 "temporarily_unavailable", 

101 } 

102 

103 if any(e in retryable_error_descriptions for e in (error_code, error_desc)): 

104 return True 

105 

106 except AttributeError: 

107 pass 

108 

109 return False 

110 

111 

112def _parse_expiry(response_data): 

113 """Parses the expiry field from a response into a datetime. 

114 

115 Args: 

116 response_data (Mapping): The JSON-parsed response data. 

117 

118 Returns: 

119 Optional[datetime]: The expiration or ``None`` if no expiration was 

120 specified. 

121 """ 

122 expires_in = response_data.get("expires_in", None) 

123 

124 if expires_in is not None: 

125 # Some services do not respect the OAUTH2.0 RFC and send expires_in as a 

126 # JSON String. 

127 if isinstance(expires_in, str): 

128 expires_in = int(expires_in) 

129 

130 return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) 

131 else: 

132 return None 

133 

134 

135def _token_endpoint_request_no_throw( 

136 request, 

137 token_uri, 

138 body, 

139 access_token=None, 

140 use_json=False, 

141 can_retry=True, 

142 headers=None, 

143 **kwargs 

144): 

145 """Makes a request to the OAuth 2.0 authorization server's token endpoint. 

146 This function doesn't throw on response errors. 

147 

148 Args: 

149 request (google.auth.transport.Request): A callable used to make 

150 HTTP requests. 

151 token_uri (str): The OAuth 2.0 authorizations server's token endpoint 

152 URI. 

153 body (Mapping[str, str]): The parameters to send in the request body. 

154 access_token (Optional(str)): The access token needed to make the request. 

155 use_json (Optional(bool)): Use urlencoded format or json format for the 

156 content type. The default value is False. 

157 can_retry (bool): Enable or disable request retry behavior. 

158 headers (Optional[Mapping[str, str]]): The headers for the request. 

159 kwargs: Additional arguments passed on to the request method. The 

160 kwargs will be passed to `requests.request` method, see: 

161 https://docs.python-requests.org/en/latest/api/#requests.request. 

162 For example, you can use `cert=("cert_pem_path", "key_pem_path")` 

163 to set up client side SSL certificate, and use 

164 `verify="ca_bundle_path"` to set up the CA certificates for sever 

165 side SSL certificate verification. 

166 

167 Returns: 

168 Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating 

169 if the request is successful, a mapping for the JSON-decoded response 

170 data and in the case of an error a boolean indicating if the error 

171 is retryable. 

172 """ 

173 if use_json: 

174 headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE} 

175 body = json.dumps(body).encode("utf-8") 

176 else: 

177 headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE} 

178 body = urllib.parse.urlencode(body).encode("utf-8") 

179 

180 if access_token: 

181 headers_to_use["Authorization"] = "Bearer {}".format(access_token) 

182 

183 if headers: 

184 headers_to_use.update(headers) 

185 

186 response_data = {} 

187 retryable_error = False 

188 

189 retries = _exponential_backoff.ExponentialBackoff() 

190 for _ in retries: 

191 response = request( 

192 method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs 

193 ) 

194 response_body = ( 

195 response.data.decode("utf-8") 

196 if hasattr(response.data, "decode") 

197 else response.data 

198 ) 

199 

200 try: 

201 # response_body should be a JSON 

202 response_data = json.loads(response_body) 

203 except ValueError: 

204 response_data = response_body 

205 

206 if response.status == http_client.OK: 

207 return True, response_data, None 

208 

209 retryable_error = _can_retry( 

210 status_code=response.status, response_data=response_data 

211 ) 

212 

213 if not can_retry or not retryable_error: 

214 return False, response_data, retryable_error 

215 

216 return False, response_data, retryable_error 

217 

218 

219def _token_endpoint_request( 

220 request, 

221 token_uri, 

222 body, 

223 access_token=None, 

224 use_json=False, 

225 can_retry=True, 

226 headers=None, 

227 **kwargs 

228): 

229 """Makes a request to the OAuth 2.0 authorization server's token endpoint. 

230 

231 Args: 

232 request (google.auth.transport.Request): A callable used to make 

233 HTTP requests. 

234 token_uri (str): The OAuth 2.0 authorizations server's token endpoint 

235 URI. 

236 body (Mapping[str, str]): The parameters to send in the request body. 

237 access_token (Optional(str)): The access token needed to make the request. 

238 use_json (Optional(bool)): Use urlencoded format or json format for the 

239 content type. The default value is False. 

240 can_retry (bool): Enable or disable request retry behavior. 

241 headers (Optional[Mapping[str, str]]): The headers for the request. 

242 kwargs: Additional arguments passed on to the request method. The 

243 kwargs will be passed to `requests.request` method, see: 

244 https://docs.python-requests.org/en/latest/api/#requests.request. 

245 For example, you can use `cert=("cert_pem_path", "key_pem_path")` 

246 to set up client side SSL certificate, and use 

247 `verify="ca_bundle_path"` to set up the CA certificates for sever 

248 side SSL certificate verification. 

249 

250 Returns: 

251 Mapping[str, str]: The JSON-decoded response data. 

252 

253 Raises: 

254 google.auth.exceptions.RefreshError: If the token endpoint returned 

255 an error. 

256 """ 

257 

258 response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw( 

259 request, 

260 token_uri, 

261 body, 

262 access_token=access_token, 

263 use_json=use_json, 

264 can_retry=can_retry, 

265 headers=headers, 

266 **kwargs 

267 ) 

268 if not response_status_ok: 

269 _handle_error_response(response_data, retryable_error) 

270 return response_data 

271 

272 

273def jwt_grant(request, token_uri, assertion, can_retry=True): 

274 """Implements the JWT Profile for OAuth 2.0 Authorization Grants. 

275 

276 For more details, see `rfc7523 section 4`_. 

277 

278 Args: 

279 request (google.auth.transport.Request): A callable used to make 

280 HTTP requests. 

281 token_uri (str): The OAuth 2.0 authorizations server's token endpoint 

282 URI. 

283 assertion (str): The OAuth 2.0 assertion. 

284 can_retry (bool): Enable or disable request retry behavior. 

285 

286 Returns: 

287 Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, 

288 expiration, and additional data returned by the token endpoint. 

289 

290 Raises: 

291 google.auth.exceptions.RefreshError: If the token endpoint returned 

292 an error. 

293 

294 .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 

295 """ 

296 body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} 

297 

298 response_data = _token_endpoint_request( 

299 request, 

300 token_uri, 

301 body, 

302 can_retry=can_retry, 

303 headers={ 

304 metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion() 

305 }, 

306 ) 

307 

308 try: 

309 access_token = response_data["access_token"] 

310 except KeyError as caught_exc: 

311 new_exc = exceptions.RefreshError( 

312 "No access token in response.", response_data, retryable=False 

313 ) 

314 raise new_exc from caught_exc 

315 

316 expiry = _parse_expiry(response_data) 

317 

318 return access_token, expiry, response_data 

319 

320 

321def call_iam_generate_id_token_endpoint( 

322 request, iam_id_token_endpoint, signer_email, audience, access_token 

323): 

324 """Call iam.generateIdToken endpoint to get ID token. 

325 

326 Args: 

327 request (google.auth.transport.Request): A callable used to make 

328 HTTP requests. 

329 iam_id_token_endpoint (str): The IAM ID token endpoint to use. 

330 signer_email (str): The signer email used to form the IAM 

331 generateIdToken endpoint. 

332 audience (str): The audience for the ID token. 

333 access_token (str): The access token used to call the IAM endpoint. 

334 

335 Returns: 

336 Tuple[str, datetime]: The ID token and expiration. 

337 """ 

338 body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"} 

339 

340 response_data = _token_endpoint_request( 

341 request, 

342 iam_id_token_endpoint.format(signer_email), 

343 body, 

344 access_token=access_token, 

345 use_json=True, 

346 ) 

347 

348 try: 

349 id_token = response_data["token"] 

350 except KeyError as caught_exc: 

351 new_exc = exceptions.RefreshError( 

352 "No ID token in response.", response_data, retryable=False 

353 ) 

354 raise new_exc from caught_exc 

355 

356 payload = jwt.decode(id_token, verify=False) 

357 expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) 

358 

359 return id_token, expiry 

360 

361 

362def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): 

363 """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but 

364 requests an OpenID Connect ID Token instead of an access token. 

365 

366 This is a variant on the standard JWT Profile that is currently unique 

367 to Google. This was added for the benefit of authenticating to services 

368 that require ID Tokens instead of access tokens or JWT bearer tokens. 

369 

370 Args: 

371 request (google.auth.transport.Request): A callable used to make 

372 HTTP requests. 

373 token_uri (str): The OAuth 2.0 authorization server's token endpoint 

374 URI. 

375 assertion (str): JWT token signed by a service account. The token's 

376 payload must include a ``target_audience`` claim. 

377 can_retry (bool): Enable or disable request retry behavior. 

378 

379 Returns: 

380 Tuple[str, Optional[datetime], Mapping[str, str]]: 

381 The (encoded) Open ID Connect ID Token, expiration, and additional 

382 data returned by the endpoint. 

383 

384 Raises: 

385 google.auth.exceptions.RefreshError: If the token endpoint returned 

386 an error. 

387 """ 

388 body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} 

389 

390 response_data = _token_endpoint_request( 

391 request, 

392 token_uri, 

393 body, 

394 can_retry=can_retry, 

395 headers={ 

396 metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion() 

397 }, 

398 ) 

399 

400 try: 

401 id_token = response_data["id_token"] 

402 except KeyError as caught_exc: 

403 new_exc = exceptions.RefreshError( 

404 "No ID token in response.", response_data, retryable=False 

405 ) 

406 raise new_exc from caught_exc 

407 

408 payload = jwt.decode(id_token, verify=False) 

409 expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) 

410 

411 return id_token, expiry, response_data 

412 

413 

414def _handle_refresh_grant_response(response_data, refresh_token): 

415 """Extract tokens from refresh grant response. 

416 

417 Args: 

418 response_data (Mapping[str, str]): Refresh grant response data. 

419 refresh_token (str): Current refresh token. 

420 

421 Returns: 

422 Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token, 

423 refresh token, expiration, and additional data returned by the token 

424 endpoint. If response_data doesn't have refresh token, then the current 

425 refresh token will be returned. 

426 

427 Raises: 

428 google.auth.exceptions.RefreshError: If the token endpoint returned 

429 an error. 

430 """ 

431 try: 

432 access_token = response_data["access_token"] 

433 except KeyError as caught_exc: 

434 new_exc = exceptions.RefreshError( 

435 "No access token in response.", response_data, retryable=False 

436 ) 

437 raise new_exc from caught_exc 

438 

439 refresh_token = response_data.get("refresh_token", refresh_token) 

440 expiry = _parse_expiry(response_data) 

441 

442 return access_token, refresh_token, expiry, response_data 

443 

444 

445def refresh_grant( 

446 request, 

447 token_uri, 

448 refresh_token, 

449 client_id, 

450 client_secret, 

451 scopes=None, 

452 rapt_token=None, 

453 can_retry=True, 

454): 

455 """Implements the OAuth 2.0 refresh token grant. 

456 

457 For more details, see `rfc678 section 6`_. 

458 

459 Args: 

460 request (google.auth.transport.Request): A callable used to make 

461 HTTP requests. 

462 token_uri (str): The OAuth 2.0 authorizations server's token endpoint 

463 URI. 

464 refresh_token (str): The refresh token to use to get a new access 

465 token. 

466 client_id (str): The OAuth 2.0 application's client ID. 

467 client_secret (str): The Oauth 2.0 appliaction's client secret. 

468 scopes (Optional(Sequence[str])): Scopes to request. If present, all 

469 scopes must be authorized for the refresh token. Useful if refresh 

470 token has a wild card scope (e.g. 

471 'https://www.googleapis.com/auth/any-api'). 

472 rapt_token (Optional(str)): The reauth Proof Token. 

473 can_retry (bool): Enable or disable request retry behavior. 

474 

475 Returns: 

476 Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access 

477 token, new or current refresh token, expiration, and additional data 

478 returned by the token endpoint. 

479 

480 Raises: 

481 google.auth.exceptions.RefreshError: If the token endpoint returned 

482 an error. 

483 

484 .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 

485 """ 

486 body = { 

487 "grant_type": _REFRESH_GRANT_TYPE, 

488 "client_id": client_id, 

489 "client_secret": client_secret, 

490 "refresh_token": refresh_token, 

491 } 

492 if scopes: 

493 body["scope"] = " ".join(scopes) 

494 if rapt_token: 

495 body["rapt"] = rapt_token 

496 

497 response_data = _token_endpoint_request( 

498 request, token_uri, body, can_retry=can_retry 

499 ) 

500 return _handle_refresh_grant_response(response_data, refresh_token)