Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/auth/transport/requests.py: 24%

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

277 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"""Transport adapter for Requests.""" 

16 

17from __future__ import absolute_import 

18 

19import collections 

20import copy 

21import functools 

22import http.client as http_client 

23import logging 

24import numbers 

25import threading 

26import time 

27import urllib.parse as urllib_parse 

28from typing import Optional 

29 

30try: 

31 import requests 

32except ImportError as caught_exc: # pragma: NO COVER 

33 raise ImportError( 

34 "The requests library is not installed from please install the requests package to use the requests transport." 

35 ) from caught_exc 

36import requests.adapters # pylint: disable=ungrouped-imports 

37import requests.exceptions # pylint: disable=ungrouped-imports 

38from requests.packages.urllib3.util.ssl_ import ( # type: ignore 

39 create_urllib3_context, 

40) # pylint: disable=ungrouped-imports 

41 

42import google.auth.transport._mtls_helper 

43from google.auth import _helpers, exceptions, transport 

44from google.auth.transport import _mtls_helper 

45from google.oauth2 import service_account 

46 

47_LOGGER = logging.getLogger(__name__) 

48 

49_DEFAULT_TIMEOUT = 120 # in seconds 

50 

51 

52class _Response(transport.Response): 

53 """Requests transport response adapter. 

54 

55 Args: 

56 response (requests.Response): The raw Requests response. 

57 """ 

58 

59 def __init__(self, response): 

60 self._response = response 

61 

62 @property 

63 def status(self): 

64 return self._response.status_code 

65 

66 @property 

67 def headers(self): 

68 return self._response.headers 

69 

70 @property 

71 def data(self): 

72 return self._response.content 

73 

74 

75class TimeoutGuard(object): 

76 """A context manager raising an error if the suite execution took too long. 

77 

78 Args: 

79 timeout (Union[None, Union[float, Tuple[float, float]]]): 

80 The maximum number of seconds a suite can run without the context 

81 manager raising a timeout exception on exit. If passed as a tuple, 

82 the smaller of the values is taken as a timeout. If ``None``, a 

83 timeout error is never raised. 

84 timeout_error_type (Optional[Exception]): 

85 The type of the error to raise on timeout. Defaults to 

86 :class:`requests.exceptions.Timeout`. 

87 """ 

88 

89 def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): 

90 self._timeout = timeout 

91 self.remaining_timeout = timeout 

92 self._timeout_error_type = timeout_error_type 

93 

94 def __enter__(self): 

95 self._start = time.time() 

96 return self 

97 

98 def __exit__(self, exc_type, exc_value, traceback): 

99 if exc_value: 

100 return # let the error bubble up automatically 

101 

102 if self._timeout is None: 

103 return # nothing to do, the timeout was not specified 

104 

105 elapsed = time.time() - self._start 

106 deadline_hit = False 

107 

108 if isinstance(self._timeout, numbers.Number): 

109 self.remaining_timeout = self._timeout - elapsed 

110 deadline_hit = self.remaining_timeout <= 0 

111 else: 

112 self.remaining_timeout = tuple(x - elapsed for x in self._timeout) 

113 deadline_hit = min(self.remaining_timeout) <= 0 

114 

115 if deadline_hit: 

116 raise self._timeout_error_type() 

117 

118 

119class Request(transport.Request): 

120 """Requests request adapter. 

121 

122 This class is used internally for making requests using various transports 

123 in a consistent way. If you use :class:`AuthorizedSession` you do not need 

124 to construct or use this class directly. 

125 

126 This class can be useful if you want to manually refresh a 

127 :class:`~google.auth.credentials.Credentials` instance:: 

128 

129 import google.auth.transport.requests 

130 import requests 

131 

132 request = google.auth.transport.requests.Request() 

133 

134 credentials.refresh(request) 

135 

136 Args: 

137 session (requests.Session): An instance :class:`requests.Session` used 

138 to make HTTP requests. If not specified, a session will be created. 

139 

140 .. automethod:: __call__ 

141 """ 

142 

143 _mtls_adapter = None 

144 _mtls_unavailable = False 

145 

146 def __init__(self, session: Optional[requests.Session] = None) -> None: 

147 if not session: 

148 session = requests.Session() 

149 

150 self.session = session 

151 self._mtls_adapter = None 

152 self._mtls_unavailable = False 

153 self._mtls_lock = threading.Lock() 

154 

155 def __getstate__(self): 

156 state = self.__dict__.copy() 

157 state["_mtls_lock"] = None 

158 return state 

159 

160 def __setstate__(self, state): 

161 self.__dict__.update(state) 

162 self._mtls_lock = threading.Lock() 

163 if not isinstance(self._mtls_adapter, _MutualTlsAdapter): 

164 self._mtls_adapter = None 

165 

166 def __del__(self): 

167 try: 

168 if hasattr(self, "session") and self.session is not None: 

169 self.session.close() 

170 except TypeError: 

171 # NOTE: For certain Python binary built, the queue.Empty exception 

172 # might not be considered a normal Python exception causing 

173 # TypeError. 

174 pass 

175 

176 def _configure_mtls_if_needed(self, url): 

177 """Lazily mounts a mutual TLS adapter onto the session for mTLS endpoints. 

178 

179 Args: 

180 url (str): The target request URL. 

181 """ 

182 if not _mtls_helper.is_mtls_endpoint(url): 

183 return 

184 

185 # Mount on the specific origin rather than globally on "https://" so client 

186 # certificates are not sent to other hosts sharing this session. The trailing 

187 # slash prevents requests' startswith() matching from matching lookalike domains. 

188 parsed = urllib_parse.urlparse(url) 

189 prefix = f"{parsed.scheme}://{parsed.netloc.lower()}/" 

190 with self._mtls_lock: 

191 session_adapter = self.session.get_adapter(url if parsed.path else prefix) 

192 # Skip if _MutualTlsAdapter is already mounted or the caller mounted a custom adapter. 

193 if ( 

194 type(session_adapter) is not requests.adapters.HTTPAdapter 

195 or self.session.get_adapter(prefix) is not session_adapter 

196 ): 

197 return 

198 

199 if self._mtls_adapter is None: 

200 if self._mtls_unavailable or not _mtls_helper.check_use_client_cert(): 

201 return 

202 has_cert, cert, key = _mtls_helper.get_client_cert_and_key() 

203 if not has_cert: 

204 self._mtls_unavailable = True 

205 return 

206 

207 self._mtls_adapter = _MutualTlsAdapter( 

208 cert, 

209 key, 

210 max_retries=session_adapter.max_retries, 

211 pool_connections=session_adapter._pool_connections, 

212 pool_maxsize=session_adapter._pool_maxsize, 

213 pool_block=session_adapter._pool_block, 

214 ) 

215 

216 # Replace session.adapters atomically instead of calling session.mount(), 

217 # which mutates the OrderedDict in place and can raise RuntimeError if 

218 # another thread is iterating over session.adapters in get_adapter(). 

219 new_adapters = self.session.adapters.copy() 

220 new_adapters[prefix] = self._mtls_adapter 

221 self.session.adapters = collections.OrderedDict( 

222 sorted( 

223 new_adapters.items(), 

224 key=lambda item: len(item[0]), 

225 reverse=True, 

226 ) 

227 ) 

228 

229 def __call__( 

230 self, 

231 url, 

232 method="GET", 

233 body=None, 

234 headers=None, 

235 timeout=_DEFAULT_TIMEOUT, 

236 **kwargs, 

237 ): 

238 """Make an HTTP request using requests. 

239 

240 Args: 

241 url (str): The URI to be requested. 

242 method (str): The HTTP method to use for the request. Defaults 

243 to 'GET'. 

244 body (bytes): The payload or body in HTTP request. 

245 headers (Mapping[str, str]): Request headers. 

246 timeout (Optional[int]): The number of seconds to wait for a 

247 response from the server. If not specified or if None, the 

248 requests default timeout will be used. 

249 kwargs: Additional arguments passed through to the underlying 

250 requests :meth:`~requests.Session.request` method. 

251 

252 Returns: 

253 google.auth.transport.Response: The HTTP response. 

254 

255 Raises: 

256 google.auth.exceptions.TransportError: If any exception occurred. 

257 """ 

258 if not kwargs.get("cert"): 

259 try: 

260 self._configure_mtls_if_needed(url) 

261 except ( 

262 exceptions.MutualTLSChannelError, 

263 exceptions.ClientCertError, 

264 OSError, 

265 ) as caught_exc: 

266 new_exc = exceptions.TransportError(caught_exc) 

267 raise new_exc from caught_exc 

268 

269 # Missing CA bundles or cert paths raised by requests must bubble up 

270 # unwrapped to fail immediately rather than triggering client retries. 

271 try: 

272 _helpers.request_log(_LOGGER, method, url, body, headers) 

273 response = self.session.request( 

274 method, url, data=body, headers=headers, timeout=timeout, **kwargs 

275 ) 

276 _helpers.response_log(_LOGGER, response) 

277 return _Response(response) 

278 except requests.exceptions.RequestException as caught_exc: 

279 new_exc = exceptions.TransportError(caught_exc) 

280 raise new_exc from caught_exc 

281 

282 

283class _MutualTlsAdapter(requests.adapters.HTTPAdapter): 

284 """ 

285 A TransportAdapter that enables mutual TLS. 

286 

287 Args: 

288 cert (bytes): client certificate in PEM format 

289 key (bytes): client private key in PEM format 

290 

291 Raises: 

292 ImportError: if certifi is not installed 

293 google.auth.exceptions.MutualTLSChannelError: If the cert or key is invalid. 

294 """ 

295 

296 def __init__(self, cert, key, **kwargs): 

297 import ssl 

298 

299 import certifi 

300 

301 ctx_poolmanager = create_urllib3_context() 

302 ctx_poolmanager.check_hostname = False 

303 ctx_poolmanager.load_verify_locations(cafile=certifi.where()) 

304 

305 ctx_proxymanager = create_urllib3_context() 

306 ctx_proxymanager.check_hostname = False 

307 ctx_proxymanager.load_verify_locations(cafile=certifi.where()) 

308 

309 try: 

310 with _mtls_helper.secure_cert_key_paths(cert, key) as ( 

311 cert_path, 

312 key_path, 

313 passphrase, 

314 ): 

315 password = passphrase 

316 ctx_poolmanager.load_cert_chain( 

317 certfile=cert_path, 

318 keyfile=key_path, 

319 password=password, 

320 ) 

321 ctx_proxymanager.load_cert_chain( 

322 certfile=cert_path, 

323 keyfile=key_path, 

324 password=password, 

325 ) 

326 except ( 

327 ssl.SSLError, 

328 OSError, 

329 IOError, 

330 ValueError, 

331 RuntimeError, 

332 TypeError, 

333 ) as exc: 

334 raise exceptions.MutualTLSChannelError( 

335 "Failed to configure client certificate and key for mTLS." 

336 ) from exc 

337 

338 self._ctx_poolmanager = ctx_poolmanager 

339 self._ctx_proxymanager = ctx_proxymanager 

340 

341 super(_MutualTlsAdapter, self).__init__(**kwargs) 

342 

343 def __deepcopy__(self, memo): 

344 new_adapter = object.__new__(type(self)) 

345 memo[id(self)] = new_adapter 

346 new_adapter._ctx_poolmanager = self._ctx_poolmanager 

347 new_adapter._ctx_proxymanager = self._ctx_proxymanager 

348 new_adapter.__setstate__(copy.deepcopy(self.__getstate__(), memo)) 

349 return new_adapter 

350 

351 def __reduce_ex__(self, protocol): 

352 return (requests.adapters.HTTPAdapter, (), self.__getstate__()) 

353 

354 def init_poolmanager(self, *args, **kwargs): 

355 kwargs["ssl_context"] = self._ctx_poolmanager 

356 super(_MutualTlsAdapter, self).init_poolmanager(*args, **kwargs) 

357 

358 def proxy_manager_for(self, *args, **kwargs): 

359 kwargs["ssl_context"] = self._ctx_proxymanager 

360 return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs) 

361 

362 

363class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): 

364 """ 

365 A TransportAdapter that enables mutual TLS and offloads the client side 

366 signing operation to the signing library. 

367 

368 Args: 

369 enterprise_cert_file_path (str): the path to a enterprise cert JSON 

370 file. The file should contain the following field: 

371 

372 { 

373 "libs": { 

374 "signer_library": "...", 

375 "offload_library": "..." 

376 } 

377 } 

378 

379 Raises: 

380 ImportError: if certifi is not installed 

381 google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel 

382 creation failed for any reason. 

383 """ 

384 

385 def __init__(self, enterprise_cert_file_path): 

386 import certifi 

387 

388 from google.auth.transport import _custom_tls_signer 

389 

390 self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) 

391 self.signer.load_libraries() 

392 

393 poolmanager = create_urllib3_context() 

394 poolmanager.load_verify_locations(cafile=certifi.where()) 

395 self.signer.attach_to_ssl_context(poolmanager) 

396 self._ctx_poolmanager = poolmanager 

397 

398 proxymanager = create_urllib3_context() 

399 proxymanager.load_verify_locations(cafile=certifi.where()) 

400 self.signer.attach_to_ssl_context(proxymanager) 

401 self._ctx_proxymanager = proxymanager 

402 

403 super(_MutualTlsOffloadAdapter, self).__init__() 

404 

405 def init_poolmanager(self, *args, **kwargs): 

406 kwargs["ssl_context"] = self._ctx_poolmanager 

407 super(_MutualTlsOffloadAdapter, self).init_poolmanager(*args, **kwargs) 

408 

409 def proxy_manager_for(self, *args, **kwargs): 

410 kwargs["ssl_context"] = self._ctx_proxymanager 

411 return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) 

412 

413 

414class AuthorizedSession(requests.Session): 

415 """A Requests Session class with credentials. 

416 

417 This class is used to perform requests to API endpoints that require 

418 authorization:: 

419 

420 from google.auth.transport.requests import AuthorizedSession 

421 

422 authed_session = AuthorizedSession(credentials) 

423 

424 response = authed_session.request( 

425 'GET', 'https://www.googleapis.com/storage/v1/b') 

426 

427 

428 The underlying :meth:`request` implementation handles adding the 

429 credentials' headers to the request and refreshing credentials as needed. 

430 

431 This class also supports mutual TLS via :meth:`configure_mtls_channel` 

432 method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` 

433 environment variable must be explicitly set to ``true``, otherwise it does 

434 nothing. Assume the environment is set to ``true``, the method behaves in the 

435 following manner: 

436 

437 If client_cert_callback is provided, client certificate and private 

438 key are loaded using the callback; if client_cert_callback is None, 

439 application default SSL credentials will be used. Exceptions are raised if 

440 there are problems with the certificate, private key, or the loading process, 

441 so it should be called within a try/except block. 

442 

443 First we set the environment variable to ``true``, then create an :class:`AuthorizedSession` 

444 instance and specify the endpoints:: 

445 

446 regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' 

447 mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' 

448 

449 authed_session = AuthorizedSession(credentials) 

450 

451 Now we can pass a callback to :meth:`configure_mtls_channel`:: 

452 

453 def my_cert_callback(): 

454 # some code to load client cert bytes and private key bytes, both in 

455 # PEM format. 

456 some_code_to_load_client_cert_and_key() 

457 if loaded: 

458 return cert, key 

459 raise MyClientCertFailureException() 

460 

461 # Always call configure_mtls_channel within a try/except block. 

462 try: 

463 authed_session.configure_mtls_channel(my_cert_callback) 

464 except: 

465 # handle exceptions. 

466 

467 if authed_session.is_mtls: 

468 response = authed_session.request('GET', mtls_endpoint) 

469 else: 

470 response = authed_session.request('GET', regular_endpoint) 

471 

472 

473 You can alternatively use application default SSL credentials like this:: 

474 

475 try: 

476 authed_session.configure_mtls_channel() 

477 except: 

478 # handle exceptions. 

479 

480 Args: 

481 credentials (google.auth.credentials.Credentials): The credentials to 

482 add to the request. 

483 refresh_status_codes (Sequence[int]): Which HTTP status codes indicate 

484 that credentials should be refreshed and the request should be 

485 retried. 

486 max_refresh_attempts (int): The maximum number of times to attempt to 

487 refresh the credentials and retry the request. 

488 refresh_timeout (Optional[int]): The timeout value in seconds for 

489 credential refresh HTTP requests. 

490 auth_request (google.auth.transport.requests.Request): 

491 (Optional) An instance of 

492 :class:`~google.auth.transport.requests.Request` used when 

493 refreshing credentials. If not passed, 

494 an instance of :class:`~google.auth.transport.requests.Request` 

495 is created. 

496 default_host (Optional[str]): A host like "pubsub.googleapis.com". 

497 This is used when a self-signed JWT is created from service 

498 account credentials. 

499 """ 

500 

501 def __init__( 

502 self, 

503 credentials, 

504 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, 

505 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, 

506 refresh_timeout=None, 

507 auth_request=None, 

508 default_host=None, 

509 ): 

510 super(AuthorizedSession, self).__init__() 

511 self.credentials = credentials 

512 self._refresh_status_codes = refresh_status_codes 

513 self._max_refresh_attempts = max_refresh_attempts 

514 self._refresh_timeout = refresh_timeout 

515 self._is_mtls = False 

516 self._default_host = default_host 

517 

518 if auth_request is None: 

519 self._auth_request_session = requests.Session() 

520 

521 # Using an adapter to make HTTP requests robust to network errors. 

522 # This adapter retrys HTTP requests when network errors occur 

523 # and the requests seems safely retryable. 

524 retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) 

525 self._auth_request_session.mount("https://", retry_adapter) 

526 

527 # Do not pass `self` as the session here, as it can lead to 

528 # infinite recursion. 

529 auth_request = Request(self._auth_request_session) 

530 else: 

531 self._auth_request_session = None 

532 

533 # Request instance used by internal methods (for example, 

534 # credentials.refresh). 

535 self._auth_request = auth_request 

536 

537 # https://google.aip.dev/auth/4111 

538 # Attempt to use self-signed JWTs when a service account is used. 

539 if isinstance(self.credentials, service_account.Credentials): 

540 self.credentials._create_self_signed_jwt( 

541 "https://{}/".format(self._default_host) if self._default_host else None 

542 ) 

543 

544 def configure_mtls_channel(self, client_cert_callback=None): 

545 """Configure the client certificate and key for SSL connection. 

546 

547 This method configures mTLS if client certificates are explicitly enabled 

548 (via GOOGLE_API_USE_CLIENT_CERTIFICATE=true) or auto-enabled (when the env 

549 variable is unset and workload certificates are discovered). In these cases, 

550 if the client certificate and key are successfully obtained, a 

551 :class:`_MutualTlsAdapter` instance will be mounted to the "https://" prefix. 

552 

553 Args: 

554 client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): 

555 The optional callback returns the client certificate and private 

556 key bytes both in PEM format. 

557 If the callback is None, application default SSL credentials 

558 will be used. 

559 

560 .. warning:: 

561 Calling this method mutates the underlying `requests.Session` adapter 

562 dictionary. It is not thread-safe to call this explicitly while other 

563 threads are making requests. 

564 

565 Raises: 

566 google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel 

567 creation failed for any reason. The existing session state (such 

568 as adapter mounts) remains unmodified if this error is raised. 

569 """ 

570 use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert() 

571 if not use_client_cert: 

572 return 

573 

574 try: 

575 ( 

576 is_mtls, 

577 cert, 

578 key, 

579 ) = google.auth.transport._mtls_helper.get_client_cert_and_key( 

580 client_cert_callback 

581 ) 

582 

583 old_adapter = self.adapters.get("https://") 

584 

585 kwargs = {} 

586 if old_adapter is not None: 

587 kwargs["max_retries"] = getattr(old_adapter, "max_retries", 0) 

588 kwargs["pool_connections"] = getattr( 

589 old_adapter, "_pool_connections", requests.adapters.DEFAULT_POOLSIZE 

590 ) 

591 kwargs["pool_maxsize"] = getattr( 

592 old_adapter, "_pool_maxsize", requests.adapters.DEFAULT_POOLSIZE 

593 ) 

594 kwargs["pool_block"] = getattr( 

595 old_adapter, "_pool_block", requests.adapters.DEFAULT_POOLBLOCK 

596 ) 

597 

598 old_auth_adapter = None 

599 auth_kwargs = {} 

600 if self._auth_request_session is not None: 

601 old_auth_adapter = self._auth_request_session.adapters.get("https://") 

602 

603 if old_auth_adapter is not None: 

604 auth_kwargs["max_retries"] = getattr( 

605 old_auth_adapter, "max_retries", 0 

606 ) 

607 auth_kwargs["pool_connections"] = getattr( 

608 old_auth_adapter, 

609 "_pool_connections", 

610 requests.adapters.DEFAULT_POOLSIZE, 

611 ) 

612 auth_kwargs["pool_maxsize"] = getattr( 

613 old_auth_adapter, 

614 "_pool_maxsize", 

615 requests.adapters.DEFAULT_POOLSIZE, 

616 ) 

617 auth_kwargs["pool_block"] = getattr( 

618 old_auth_adapter, 

619 "_pool_block", 

620 requests.adapters.DEFAULT_POOLBLOCK, 

621 ) 

622 

623 if is_mtls: 

624 new_adapter = _MutualTlsAdapter(cert, key, **kwargs) 

625 if self._auth_request_session is not None: 

626 new_auth_adapter = _MutualTlsAdapter(cert, key, **auth_kwargs) 

627 else: 

628 new_auth_adapter = None 

629 else: 

630 new_adapter = requests.adapters.HTTPAdapter(**kwargs) 

631 if self._auth_request_session is not None: 

632 new_auth_adapter = requests.adapters.HTTPAdapter(**auth_kwargs) 

633 else: 

634 new_auth_adapter = None 

635 except ( 

636 exceptions.ClientCertError, 

637 ImportError, 

638 OSError, 

639 ValueError, 

640 ) as caught_exc: 

641 new_exc = exceptions.MutualTLSChannelError(caught_exc) 

642 raise new_exc from caught_exc 

643 

644 self.mount("https://", new_adapter) 

645 

646 if old_adapter is not None and old_adapter is not new_adapter: 

647 old_adapter.close() 

648 

649 if self._auth_request_session is not None and new_auth_adapter is not None: 

650 self._auth_request_session.mount("https://", new_auth_adapter) 

651 

652 if ( 

653 old_auth_adapter is not None 

654 and old_auth_adapter is not new_auth_adapter 

655 ): 

656 old_auth_adapter.close() 

657 

658 self._is_mtls = is_mtls 

659 if is_mtls: 

660 self._cached_cert = cert 

661 else: 

662 if hasattr(self, "_cached_cert"): 

663 del self._cached_cert 

664 

665 def request( 

666 self, 

667 method, 

668 url, 

669 data=None, 

670 headers=None, 

671 max_allowed_time=None, 

672 timeout=_DEFAULT_TIMEOUT, 

673 **kwargs, 

674 ): 

675 """Implementation of Requests' request. 

676 

677 Args: 

678 timeout (Optional[Union[float, Tuple[float, float]]]): 

679 The amount of time in seconds to wait for the server response 

680 with each individual request. Can also be passed as a tuple 

681 ``(connect_timeout, read_timeout)``. See :meth:`requests.Session.request` 

682 documentation for details. 

683 max_allowed_time (Optional[float]): 

684 If the method runs longer than this, a ``Timeout`` exception is 

685 automatically raised. Unlike the ``timeout`` parameter, this 

686 value applies to the total method execution time, even if 

687 multiple requests are made under the hood. 

688 

689 Mind that it is not guaranteed that the timeout error is raised 

690 at ``max_allowed_time``. It might take longer, for example, if 

691 an underlying request takes a lot of time, but the request 

692 itself does not timeout, e.g. if a large file is being 

693 transmitted. The timeout error will be raised after such 

694 request completes. 

695 Raises: 

696 google.auth.exceptions.MutualTLSChannelError: If mutual TLS 

697 channel creation fails for any reason. 

698 ValueError: If the client certificate is invalid. 

699 """ 

700 # pylint: disable=arguments-differ 

701 # Requests has a ton of arguments to request, but only two 

702 # (method, url) are required. We pass through all of the other 

703 # arguments to super, so no need to exhaustively list them here. 

704 

705 # Use a kwarg for this instead of an attribute to maintain 

706 # thread-safety. 

707 _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) 

708 

709 # Make a copy of the headers. They will be modified by the credentials 

710 # and we want to pass the original headers if we recurse. 

711 request_headers = headers.copy() if headers is not None else {} 

712 

713 # Do not apply the timeout unconditionally in order to not override the 

714 # _auth_request's default timeout. 

715 auth_request = ( 

716 self._auth_request 

717 if timeout is None 

718 else functools.partial(self._auth_request, timeout=timeout) 

719 ) 

720 

721 remaining_time = max_allowed_time 

722 

723 with TimeoutGuard(remaining_time) as guard: 

724 self.credentials.before_request(auth_request, method, url, request_headers) 

725 remaining_time = guard.remaining_timeout 

726 

727 with TimeoutGuard(remaining_time) as guard: 

728 _helpers.request_log(_LOGGER, method, url, data, headers) 

729 response = super(AuthorizedSession, self).request( 

730 method, 

731 url, 

732 data=data, 

733 headers=request_headers, 

734 timeout=timeout, 

735 **kwargs, 

736 ) 

737 remaining_time = guard.remaining_timeout 

738 

739 # If the response indicated that the credentials needed to be 

740 # refreshed, then refresh the credentials and re-attempt the 

741 # request. 

742 # A stored token may expire between the time it is retrieved and 

743 # the time the request is made, so we may need to try twice. 

744 if ( 

745 response.status_code in self._refresh_status_codes 

746 and _credential_refresh_attempt < self._max_refresh_attempts 

747 ): 

748 # Handle unauthorized permission error(401 status code) 

749 if response.status_code == http_client.UNAUTHORIZED: 

750 use_mtls = self.is_mtls and _mtls_helper.is_mtls_endpoint(url) 

751 if use_mtls: 

752 ( 

753 call_cert_bytes, 

754 call_key_bytes, 

755 cached_fingerprint, 

756 current_cert_fingerprint, 

757 ) = _mtls_helper.check_parameters_for_unauthorized_response( 

758 self._cached_cert 

759 ) 

760 if cached_fingerprint != current_cert_fingerprint: 

761 try: 

762 _LOGGER.info( 

763 "Client certificate has changed, reconfiguring mTLS " 

764 "channel." 

765 ) 

766 self.configure_mtls_channel( 

767 lambda: (call_cert_bytes, call_key_bytes) 

768 ) 

769 except Exception as e: 

770 _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) 

771 raise exceptions.MutualTLSChannelError( 

772 "Failed to reconfigure mTLS channel" 

773 ) from e 

774 else: 

775 _LOGGER.info( 

776 "Skipping reconfiguration of mTLS channel because the client" 

777 " certificate has not changed." 

778 ) 

779 _LOGGER.info( 

780 "Refreshing credentials due to a %s response. Attempt %s/%s.", 

781 response.status_code, 

782 _credential_refresh_attempt + 1, 

783 self._max_refresh_attempts, 

784 ) 

785 

786 # Do not apply the timeout unconditionally in order to not override the 

787 # _auth_request's default timeout. 

788 auth_request = ( 

789 self._auth_request 

790 if timeout is None 

791 else functools.partial(self._auth_request, timeout=timeout) 

792 ) 

793 

794 with TimeoutGuard(remaining_time) as guard: 

795 self.credentials.refresh(auth_request) 

796 remaining_time = guard.remaining_timeout 

797 

798 # Recurse. Pass in the original headers, not our modified set, but 

799 # do pass the adjusted max allowed time (i.e. the remaining total time). 

800 return self.request( 

801 method, 

802 url, 

803 data=data, 

804 headers=headers, 

805 max_allowed_time=remaining_time, 

806 timeout=timeout, 

807 _credential_refresh_attempt=_credential_refresh_attempt + 1, 

808 **kwargs, 

809 ) 

810 

811 return response 

812 

813 @property 

814 def is_mtls(self): 

815 """Indicates if the created SSL channel is mutual TLS.""" 

816 return self._is_mtls 

817 

818 def close(self): 

819 if self._auth_request_session is not None: 

820 self._auth_request_session.close() 

821 super(AuthorizedSession, self).close()