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

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

154 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 credentials 

34from google.auth import exceptions 

35from google.auth import jwt 

36from google.auth import metrics 

37from google.auth import transport 

38 

39_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" 

40_JSON_CONTENT_TYPE = "application/json" 

41_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" 

42_REFRESH_GRANT_TYPE = "refresh_token" 

43 

44 

45def _handle_error_response(response_data, retryable_error): 

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

47 

48 Args: 

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

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

51 Defaults to False. 

52 

53 Raises: 

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

55 """ 

56 

57 retryable_error = retryable_error if retryable_error else False 

58 

59 if isinstance(response_data, str): 

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

61 try: 

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

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

64 ) 

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

66 except (KeyError, ValueError): 

67 error_details = json.dumps(response_data) 

68 

69 raise exceptions.RefreshError( 

70 error_details, response_data, retryable=retryable_error 

71 ) 

72 

73 

74def _can_retry(status_code, response_data): 

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

76 and response body of the request. 

77 

78 Args: 

79 status_code (int): The response status code. 

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

81 

82 Returns: 

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

84 """ 

85 if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES: 

86 return True 

87 

88 try: 

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

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

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

92 

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

94 return False 

95 

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

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

98 retryable_error_descriptions = { 

99 "internal_failure", 

100 "server_error", 

101 "temporarily_unavailable", 

102 } 

103 

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

105 return True 

106 

107 except AttributeError: 

108 pass 

109 

110 return False 

111 

112 

113def _parse_expiry(response_data): 

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

115 

116 Args: 

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

118 

119 Returns: 

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

121 specified. 

122 """ 

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

124 

125 if expires_in is not None: 

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

127 # JSON String. 

128 if isinstance(expires_in, str): 

129 expires_in = int(expires_in) 

130 

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

132 else: 

133 return None 

134 

135 

136def _token_endpoint_request_no_throw( 

137 request, 

138 token_uri, 

139 body, 

140 access_token=None, 

141 use_json=False, 

142 can_retry=True, 

143 headers=None, 

144 **kwargs 

145): 

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

147 This function doesn't throw on response errors. 

148 

149 Args: 

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

151 HTTP requests. 

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

153 URI. 

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

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

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

157 content type. The default value is False. 

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

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

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

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

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

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

164 to set up client side SSL certificate, and use 

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

166 side SSL certificate verification. 

167 

168 Returns: 

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

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

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

172 is retryable. 

173 """ 

174 if use_json: 

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

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

177 else: 

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

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

180 

181 if access_token: 

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

183 

184 if headers: 

185 headers_to_use.update(headers) 

186 

187 response_data = {} 

188 retryable_error = False 

189 

190 retries = _exponential_backoff.ExponentialBackoff() 

191 for _ in retries: 

192 response = request( 

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

194 ) 

195 response_body = ( 

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

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

198 else response.data 

199 ) 

200 

201 try: 

202 # response_body should be a JSON 

203 response_data = json.loads(response_body) 

204 except ValueError: 

205 response_data = response_body 

206 

207 if response.status == http_client.OK: 

208 return True, response_data, None 

209 

210 retryable_error = _can_retry( 

211 status_code=response.status, response_data=response_data 

212 ) 

213 

214 if not can_retry or not retryable_error: 

215 return False, response_data, retryable_error 

216 

217 return False, response_data, retryable_error 

218 

219 

220def _token_endpoint_request( 

221 request, 

222 token_uri, 

223 body, 

224 access_token=None, 

225 use_json=False, 

226 can_retry=True, 

227 headers=None, 

228 **kwargs 

229): 

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

231 

232 Args: 

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

234 HTTP requests. 

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

236 URI. 

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

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

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

240 content type. The default value is False. 

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

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

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

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

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

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

247 to set up client side SSL certificate, and use 

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

249 side SSL certificate verification. 

250 

251 Returns: 

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

253 

254 Raises: 

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

256 an error. 

257 """ 

258 

259 response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw( 

260 request, 

261 token_uri, 

262 body, 

263 access_token=access_token, 

264 use_json=use_json, 

265 can_retry=can_retry, 

266 headers=headers, 

267 **kwargs 

268 ) 

269 if not response_status_ok: 

270 _handle_error_response(response_data, retryable_error) 

271 return response_data 

272 

273 

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

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

276 

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

278 

279 Args: 

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

281 HTTP requests. 

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

283 URI. 

284 assertion (str): The OAuth 2.0 assertion. 

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

286 

287 Returns: 

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

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

290 

291 Raises: 

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

293 an error. 

294 

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

296 """ 

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

298 

299 response_data = _token_endpoint_request( 

300 request, 

301 token_uri, 

302 body, 

303 can_retry=can_retry, 

304 headers={ 

305 metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion() 

306 }, 

307 ) 

308 

309 try: 

310 access_token = response_data["access_token"] 

311 except KeyError as caught_exc: 

312 new_exc = exceptions.RefreshError( 

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

314 ) 

315 raise new_exc from caught_exc 

316 

317 expiry = _parse_expiry(response_data) 

318 

319 return access_token, expiry, response_data 

320 

321 

322def call_iam_generate_id_token_endpoint( 

323 request, 

324 iam_id_token_endpoint, 

325 signer_email, 

326 audience, 

327 access_token, 

328 universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, 

329): 

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

331 

332 Args: 

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

334 HTTP requests. 

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

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

337 generateIdToken endpoint. 

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

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

340 universe_domain (str): The universe domain for the request. The 

341 default is ``googleapis.com``. 

342 

343 Returns: 

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

345 """ 

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

347 

348 response_data = _token_endpoint_request( 

349 request, 

350 iam_id_token_endpoint.replace( 

351 credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain 

352 ).format(signer_email), 

353 body, 

354 access_token=access_token, 

355 use_json=True, 

356 ) 

357 

358 try: 

359 id_token = response_data["token"] 

360 except KeyError as caught_exc: 

361 new_exc = exceptions.RefreshError( 

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

363 ) 

364 raise new_exc from caught_exc 

365 

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

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

368 

369 return id_token, expiry 

370 

371 

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

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

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

375 

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

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

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

379 

380 Args: 

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

382 HTTP requests. 

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

384 URI. 

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

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

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

388 

389 Returns: 

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

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

392 data returned by the endpoint. 

393 

394 Raises: 

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

396 an error. 

397 """ 

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

399 

400 response_data = _token_endpoint_request( 

401 request, 

402 token_uri, 

403 body, 

404 can_retry=can_retry, 

405 headers={ 

406 metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion() 

407 }, 

408 ) 

409 

410 try: 

411 id_token = response_data["id_token"] 

412 except KeyError as caught_exc: 

413 new_exc = exceptions.RefreshError( 

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

415 ) 

416 raise new_exc from caught_exc 

417 

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

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

420 

421 return id_token, expiry, response_data 

422 

423 

424def _handle_refresh_grant_response(response_data, refresh_token): 

425 """Extract tokens from refresh grant response. 

426 

427 Args: 

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

429 refresh_token (str): Current refresh token. 

430 

431 Returns: 

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

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

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

435 refresh token will be returned. 

436 

437 Raises: 

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

439 an error. 

440 """ 

441 try: 

442 access_token = response_data["access_token"] 

443 except KeyError as caught_exc: 

444 new_exc = exceptions.RefreshError( 

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

446 ) 

447 raise new_exc from caught_exc 

448 

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

450 expiry = _parse_expiry(response_data) 

451 

452 return access_token, refresh_token, expiry, response_data 

453 

454 

455def refresh_grant( 

456 request, 

457 token_uri, 

458 refresh_token, 

459 client_id, 

460 client_secret, 

461 scopes=None, 

462 rapt_token=None, 

463 can_retry=True, 

464): 

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

466 

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

468 

469 Args: 

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

471 HTTP requests. 

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

473 URI. 

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

475 token. 

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

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

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

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

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

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

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

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

484 

485 Returns: 

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

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

488 returned by the token endpoint. 

489 

490 Raises: 

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

492 an error. 

493 

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

495 """ 

496 body = { 

497 "grant_type": _REFRESH_GRANT_TYPE, 

498 "client_id": client_id, 

499 "client_secret": client_secret, 

500 "refresh_token": refresh_token, 

501 } 

502 if scopes: 

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

504 if rapt_token: 

505 body["rapt"] = rapt_token 

506 

507 response_data = _token_endpoint_request( 

508 request, token_uri, body, can_retry=can_retry 

509 ) 

510 return _handle_refresh_grant_response(response_data, refresh_token) 

511 

512 

513def _lookup_trust_boundary(request, url, headers=None): 

514 """Implements the global lookup of a credential trust boundary. 

515 For the lookup, we send a request to the global lookup endpoint and then 

516 parse the response. Service account credentials, workload identity 

517 pools and workforce pools implementation may have trust boundaries configured. 

518 Args: 

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

520 HTTP requests. 

521 url (str): The trust boundary lookup url. 

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

523 Returns: 

524 Mapping[str,list|str]: A dictionary containing 

525 "locations" as a list of allowed locations as strings and 

526 "encodedLocations" as a hex string. 

527 e.g: 

528 { 

529 "locations": [ 

530 "us-central1", "us-east1", "europe-west1", "asia-east1" 

531 ], 

532 "encodedLocations": "0xA30" 

533 } 

534 If the credential is not set up with explicit trust boundaries, a trust boundary 

535 of "all" will be returned as a default response. 

536 { 

537 "locations": [], 

538 "encodedLocations": "0x0" 

539 } 

540 Raises: 

541 exceptions.RefreshError: If the response status code is not 200. 

542 exceptions.MalformedError: If the response is not in a valid format. 

543 """ 

544 

545 response_data = _lookup_trust_boundary_request(request, url, headers=headers) 

546 # In case of no-op response, the "locations" list may or may not be present as an empty list. 

547 if "encodedLocations" not in response_data: 

548 raise exceptions.MalformedError( 

549 "Invalid trust boundary info: {}".format(response_data) 

550 ) 

551 return response_data 

552 

553 

554def _lookup_trust_boundary_request(request, url, can_retry=True, headers=None): 

555 """Makes a request to the trust boundary lookup endpoint. 

556 

557 Args: 

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

559 HTTP requests. 

560 url (str): The trust boundary lookup url. 

561 can_retry (bool): Enable or disable request retry behavior. Defaults to true. 

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

563 

564 Returns: 

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

566 

567 Raises: 

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

569 an error. 

570 """ 

571 response_status_ok, response_data, retryable_error = _lookup_trust_boundary_request_no_throw( 

572 request, url, can_retry, headers 

573 ) 

574 if not response_status_ok: 

575 _handle_error_response(response_data, retryable_error) 

576 return response_data 

577 

578 

579def _lookup_trust_boundary_request_no_throw(request, url, can_retry=True, headers=None): 

580 """Makes a request to the trust boundary lookup endpoint. This 

581 function doesn't throw on response errors. 

582 

583 Args: 

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

585 HTTP requests. 

586 url (str): The trust boundary lookup url. 

587 can_retry (bool): Enable or disable request retry behavior. Defaults to true. 

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

589 

590 Returns: 

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

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

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

594 is retryable. 

595 """ 

596 

597 response_data = {} 

598 retryable_error = False 

599 

600 retries = _exponential_backoff.ExponentialBackoff() 

601 for _ in retries: 

602 response = request(method="GET", url=url, headers=headers) 

603 response_body = ( 

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

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

606 else response.data 

607 ) 

608 

609 try: 

610 # response_body should be a JSON 

611 response_data = json.loads(response_body) 

612 except ValueError: 

613 response_data = response_body 

614 

615 if response.status == http_client.OK: 

616 return True, response_data, None 

617 

618 retryable_error = _can_retry( 

619 status_code=response.status, response_data=response_data 

620 ) 

621 

622 if not can_retry or not retryable_error: 

623 return False, response_data, retryable_error 

624 

625 return False, response_data, retryable_error