def __call__( self, url, method="GET", body=None, headers=None, timeout=None, **kwargs ): """Make an HTTP request using http.client. Args: url (str): The URI to be requested. method (str): The HTTP method to use for the request. Defaults to 'GET'. body (bytes): The payload / body in HTTP request. headers (Mapping): Request headers. timeout (Optional(int)): The number of seconds to wait for a response from the server. If not specified or if None, the socket global default timeout will be used. kwargs: Additional arguments passed throught to the underlying :meth:`~http.client.HTTPConnection.request` method. Returns: Response: The HTTP response. Raises: google.auth.exceptions.TransportError: If any exception occurred. """ # socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client. if timeout is None: timeout = socket._GLOBAL_DEFAULT_TIMEOUT # http.client doesn't allow None as the headers argument. if headers is None: headers = {} # http.client needs the host and path parts specified separately. parts = urllib.parse.urlsplit(url) path = urllib.parse.urlunsplit( ("", "", parts.path, parts.query, parts.fragment) ) if parts.scheme != "http": raise exceptions.TransportError( "http.client transport only supports the http scheme, {}" "was specified".format(parts.scheme) ) connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: _LOGGER.debug("Making request: %s %s", method, url) connection.request(method, path, body=body, headers=headers, **kwargs) response = connection.getresponse() return Response(response) except (http_client.HTTPException, socket.error) as caught_exc: new_exc = exceptions.TransportError(caught_exc) six.raise_from(new_exc, caught_exc) finally: connection.close()
async def __call__( self, url, method="GET", body=None, headers=None, timeout=_DEFAULT_TIMEOUT, **kwargs, ): """ Make an HTTP request using aiohttp. Args: url (str): The URL to be requested. method (Optional[str]): The HTTP method to use for the request. Defaults to 'GET'. body (Optional[bytes]): The payload or body in HTTP request. headers (Optional[Mapping[str, str]]): Request headers. timeout (Optional[int]): The number of seconds to wait for a response from the server. If not specified or if None, the requests default timeout will be used. kwargs: Additional arguments passed through to the underlying requests :meth:`requests.Session.request` method. Returns: google.auth.transport.Response: The HTTP response. Raises: google.auth.exceptions.TransportError: If any exception occurred. """ try: if self.session is None: # pragma: NO COVER self.session = aiohttp.ClientSession( auto_decompress=False) # pragma: NO COVER requests._LOGGER.debug("Making request: %s %s", method, url) response = await self.session.request(method, url, data=body, headers=headers, timeout=timeout, **kwargs) return _CombinedResponse(response) except aiohttp.ClientError as caught_exc: new_exc = exceptions.TransportError(caught_exc) raise new_exc from caught_exc except asyncio.TimeoutError as caught_exc: new_exc = exceptions.TransportError(caught_exc) raise new_exc from caught_exc
def get(request, path, root=_METADATA_ROOT, recursive=False): """Fetch a resource from the metadata server. Args: request (google.auth.transport.Request): A callable used to make HTTP requests. path (str): The resource to retrieve. For example, ``'instance/service-accounts/default'``. root (str): The full path to the metadata server root. recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more details. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of the decoded JSON is return. Otherwise, the response content is returned as a string. Raises: google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ base_url = urlparse.urljoin(root, path) query_params = {} if recursive: query_params["recursive"] = "true" url = _helpers.update_query(base_url, query_params) response = request(url=url, method="GET", headers=_METADATA_HEADERS) if response.status == http_client.OK: content = _helpers.from_bytes(response.data) if response.headers["content-type"] == "application/json": try: return json.loads(content) except ValueError as caught_exc: new_exc = exceptions.TransportError( "Received invalid JSON from the Google Compute Engine" "metadata service: {:.20}".format(content)) six.raise_from(new_exc, caught_exc) else: return content else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine" "metadata service. Status: {} Response:\n{}".format( url, response.status, response.data), response, )
def test_if_transient_error(): assert retry.if_transient_error(exceptions.InternalServerError("")) assert retry.if_transient_error(exceptions.TooManyRequests("")) assert retry.if_transient_error(exceptions.ServiceUnavailable("")) assert retry.if_transient_error(requests.exceptions.ConnectionError("")) assert retry.if_transient_error(auth_exceptions.TransportError("")) assert not retry.if_transient_error(exceptions.InvalidArgument(""))
def test_refresh_error(self, get): get.side_effect = exceptions.TransportError("http error") with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(None) assert excinfo.match(r"http error")
def test_transport_error_from_metadata(self, get, get_service_account_info): get.side_effect = exceptions.TransportError("transport error") get_service_account_info.return_value = {"email": "*****@*****.**"} cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True) with pytest.raises(exceptions.RefreshError) as excinfo: cred.refresh(request=mock.Mock()) assert excinfo.match(r"transport error")
def __call__(self, url, method='GET', body=None, headers=None, timeout=None, **kwargs): """Make an HTTP request using httplib2. Args: url (str): The URI to be requested. method (str): The HTTP method to use for the request. Defaults to 'GET'. body (bytes): The payload / body in HTTP request. headers (Mapping[str, str]): Request headers. timeout (Optional[int]): The number of seconds to wait for a response from the server. This is ignored by httplib2 and will issue a warning. kwargs: Additional arguments passed throught to the underlying :meth:`httplib2.Http.request` method. Returns: google.auth.transport.Response: The HTTP response. Raises: google.auth.exceptions.TransportError: If any exception occurred. """ if timeout is not None: _LOGGER.warning( 'httplib2 transport does not support per-request timeout. ' 'Set the timeout when constructing the httplib2.Http instance.' ) if self.user_agent: if headers.get('user-agent'): headers['user-agent'] = '%s %s' % (self.user_agent, headers['user-agent']) else: headers['user-agent'] = self.user_agent try: _LOGGER.debug('Making request: %s %s', method, url) response, data = self.http.request(url, method=method, body=body, headers=headers, **kwargs) return _Response(response, data) # httplib2 should catch the lower http error, this is a bug and # needs to be fixed there. Catch the error for the meanwhile. except (httplib2.HttpLib2Error, http_client.HTTPException) as exc: raise exceptions.TransportError(exc)
def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = _helpers.to_bytes(data) response.headers = headers or {} request = mock.create_autospec(transport.Request) if retry: request.side_effect = [exceptions.TransportError(), response] else: request.return_value = response return request
def test_get_failure_connection_failed(): request = make_request("") request.side_effect = exceptions.TransportError() with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) assert excinfo.match(r"Compute Engine Metadata server unavailable") request.assert_called_with( method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, ) assert request.call_count == 5
def _make_signing_request(self, message): """Makes a request to the API signBlob API.""" message = _helpers.to_bytes(message) method = "POST" url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) self._credentials.before_request(self._request, method, url, headers) response = self._request(url=url, method=method, body=body, headers=headers) if response.status != http_client.OK: raise exceptions.TransportError( "Error calling the IAM signBytes API: {}".format(response.data) ) return json.loads(response.data.decode("utf-8"))
def test_fetch_id_token_credentials_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) with mock.patch( "google.auth.compute_engine._metadata.ping", side_effect=exceptions.TransportError(), ): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"Neither metadata server or valid service account credentials are found." ) with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"Neither metadata server or valid service account credentials are found." )
def _sign_message(message, access_token, service_account_email): """Signs a message. :type message: str :param message: The message to be signed. :type access_token: str :param access_token: Access token for a service account. :type service_account_email: str :param service_account_email: E-mail address of the service account. :raises: :exc:`TransportError` if an `access_token` is unauthorized. :rtype: str :returns: The signature of the message. """ message = _helpers._to_bytes(message) method = "POST" url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/{}:signBlob?alt=json".format( service_account_email ) headers = { "Authorization": "Bearer " + access_token, "Content-type": "application/json", } body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) request = requests.Request() response = request(url=url, method=method, body=body, headers=headers) if response.status != six.moves.http_client.OK: raise exceptions.TransportError( "Error calling the IAM signBytes API: {}".format(response.data) ) data = json.loads(response.data.decode("utf-8")) return data["signature"]
def sign_bytes(self, message): iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), "delegates": self._delegates, } headers = {"Content-Type": "application/json"} authed_session = AuthorizedSession(self._source_credentials) response = authed_session.post(url=iam_sign_endpoint, headers=headers, json=body) if response.status_code != http_client.OK: raise exceptions.TransportError( "Error calling sign_bytes: {}".format(response.json())) return base64.b64decode(response.json()["signedBlob"])
def _fetch_certs(request, certs_url): """Fetches certificates. Google-style cerificate endpoints return JSON in the format of ``{'key id': 'x509 certificate'}``. Args: request (google.auth.transport.Request): The object used to make HTTP requests. certs_url (str): The certificate endpoint URL. Returns: Mapping[str, str]: A mapping of public key ID to x.509 certificate data. """ response = request(certs_url, method="GET") if response.status != http.client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url)) return json.loads(response.data.decode("utf-8"))
async def _fetch_certs(session: ClientSession, certs_url): """Fetches certificates. If non-expired certificates exists in CERT_CACHE these are returned. Otherwise new certs are fetched from certs_url and placed into cache. Google-style cerificate endpoints return JSON in the format of ``{'key id': 'x509 certificate'}``. Args: session (aiohhtp.Session): The object used to make HTTP requests. certs_url (str): The certificate endpoint URL. Returns: Mapping[str, str]: A mapping of public key ID to x.509 certificate data. """ try: certs, expiry = CERTS_CACHE[certs_url] except KeyError: pass else: if datetime.now() > expiry: del CERTS_CACHE[certs_url] else: return certs async with session.get(certs_url) as response: # data = await resp.json() if response.status != HTTPStatus.OK: raise exceptions.TransportError( 'Could not fetch certificates at {}'.format(certs_url)) certs = await response.json() CERTS_CACHE[certs_url] = (certs, datetime.now() + CERTS_CACHE_TTL) return certs
async def test_fetch_id_token_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) with mock.patch( "google.auth.compute_engine._metadata.ping", side_effect=exceptions.TransportError(), ): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.AsyncMock() await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") assert excinfo.match( r"Neither metadata server or valid service account credentials are found." ) with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.AsyncMock() await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") assert excinfo.match( r"Neither metadata server or valid service account credentials are found." )
def test_ping_failure_connection_failed(): request = make_request("") request.side_effect = exceptions.TransportError() assert not _metadata.ping(request)
@mock.patch('google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) def test__get_gce_credentials_no_ping(ping_mock): credentials, project_id = _default._get_gce_credentials() assert credentials is None assert project_id is None @mock.patch('google.auth.compute_engine._metadata.ping', return_value=True, autospec=True) @mock.patch('google.auth.compute_engine._metadata.get_project_id', side_effect=exceptions.TransportError(), autospec=True) def test__get_gce_credentials_no_project_id(get_mock, ping_mock): credentials, project_id = _default._get_gce_credentials() assert isinstance(credentials, compute_engine.Credentials) assert project_id is None @mock.patch('google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) def test__get_gce_credentials_explicit_request(ping_mock): _default._get_gce_credentials(mock.sentinel.request) ping_mock.assert_called_with(request=mock.sentinel.request)
def get( request, path, root=_METADATA_ROOT, params=None, recursive=False, retry_count=5 ): """Fetch a resource from the metadata server. Args: request (google.auth.transport.Request): A callable used to make HTTP requests. path (str): The resource to retrieve. For example, ``'instance/service-accounts/default'``. root (str): The full path to the metadata server root. params (Optional[Mapping[str, str]]): A mapping of query parameter keys to values. recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more details. retry_count (int): How many times to attempt connecting to metadata server using above timeout. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of the decoded JSON is return. Otherwise, the response content is returned as a string. Raises: google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ base_url = urlparse.urljoin(root, path) query_params = {} if params is None else params if recursive: query_params["recursive"] = "true" url = _helpers.update_query(base_url, query_params) retries = 0 while retries < retry_count: try: response = request(url=url, method="GET", headers=_METADATA_HEADERS) break except exceptions.TransportError as e: _LOGGER.warning( "Compute Engine Metadata server unavailable on " "attempt %s of %s. Reason: %s", retries + 1, retry_count, e, ) retries += 1 else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine" "metadata service. Compute Engine Metadata server unavailable".format(url) ) if response.status == http.client.OK: content = _helpers.from_bytes(response.data) if response.headers["content-type"] == "application/json": try: return json.loads(content) except ValueError as caught_exc: new_exc = exceptions.TransportError( "Received invalid JSON from the Google Compute Engine" "metadata service: {:.20}".format(content) ) raise new_exc from caught_exc else: return content else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine" "metadata service. Status: {} Response:\n{}".format( url, response.status, response.data ), response, )
def test_ping_failure_connection_failed(mock_request): request_mock = mock_request('') request_mock.side_effect = exceptions.TransportError() assert not _metadata.ping(request_mock)
@mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) def test__get_gce_credentials_no_ping(unused_ping): credentials, project_id = _default._get_gce_credentials() assert credentials is None assert project_id is None @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=True, autospec=True) @mock.patch( 'google.auth.compute_engine._metadata.get_project_id', side_effect=exceptions.TransportError(), autospec=True) def test__get_gce_credentials_no_project_id(unused_get, unused_ping): credentials, project_id = _default._get_gce_credentials() assert isinstance(credentials, compute_engine.Credentials) assert project_id is None def test__get_gce_credentials_no_compute_engine(): import sys with mock.patch.dict('sys.modules'): sys.modules['google.auth.compute_engine'] = None credentials, project_id = _default._get_gce_credentials() assert credentials is None assert project_id is None