From 5bd5ccf7cf229f033c7152ce0b650a40feb25f81 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:25:46 -0700 Subject: [PATCH] fix: add back python 2.7 for gcloud usage only (#892) * fix: add back python 2.7 for gcloud * fix: fix setup and tests * fix: add enum34 for python 2.7 * fix: add app engine app and fix noxfile * fix: move test_app_engine.py * fix: fix downscoped * fix: fix downscoped * fix: remove py2 from classifiers --- google/auth/_cloud_sdk.py | 4 +- google/auth/_credentials_async.py | 11 +- google/auth/_default.py | 8 +- google/auth/_default_async.py | 8 +- google/auth/_helpers.py | 17 ++- google/auth/_oauth2client.py | 6 +- google/auth/_service_account_info.py | 4 +- google/auth/aws.py | 21 ++- google/auth/compute_engine/_metadata.py | 12 +- google/auth/compute_engine/credentials.py | 6 +- google/auth/credentials.py | 11 +- google/auth/crypt/__init__.py | 4 +- google/auth/crypt/_python_rsa.py | 9 +- google/auth/crypt/base.py | 11 +- google/auth/crypt/es256.py | 7 +- google/auth/downscoped.py | 12 +- google/auth/external_account.py | 7 +- google/auth/iam.py | 5 +- google/auth/identity_pool.py | 6 +- google/auth/impersonated_credentials.py | 8 +- google/auth/jwt.py | 28 ++-- google/auth/transport/__init__.py | 12 +- google/auth/transport/_aiohttp_requests.py | 5 +- google/auth/transport/_http_client.py | 12 +- google/auth/transport/_mtls_helper.py | 6 +- google/auth/transport/grpc.py | 16 ++- google/auth/transport/mtls.py | 6 +- google/auth/transport/requests.py | 19 ++- google/auth/transport/urllib3.py | 19 ++- google/oauth2/_client.py | 18 +-- google/oauth2/_client_async.py | 16 ++- google/oauth2/_id_token_async.py | 8 +- google/oauth2/_reauth_async.py | 2 + google/oauth2/challenges.py | 5 +- google/oauth2/credentials.py | 8 +- google/oauth2/id_token.py | 8 +- google/oauth2/reauth.py | 2 + google/oauth2/sts.py | 7 +- google/oauth2/utils.py | 5 +- noxfile.py | 17 +++ setup.py | 25 ++-- system_tests/noxfile.py | 51 ++++++- .../app_engine_test_app/.gitignore | 1 + .../app_engine_test_app/app.yaml | 12 ++ .../app_engine_test_app/appengine_config.py | 30 ++++ .../app_engine_test_app/main.py | 129 ++++++++++++++++++ .../app_engine_test_app/requirements.txt | 3 + .../system_tests_sync/test_app_engine.py | 22 +++ .../system_tests_sync/test_downscoping.py | 6 +- .../test_external_accounts.py | 14 +- testing/constraints-2.7.txt | 1 + tests/compute_engine/test__metadata.py | 20 +-- tests/crypt/test__cryptography_rsa.py | 6 +- tests/crypt/test__python_rsa.py | 10 +- tests/crypt/test_es256.py | 6 +- tests/oauth2/test__client.py | 15 +- tests/oauth2/test_sts.py | 24 ++-- tests/test__helpers.py | 12 +- tests/test__oauth2client.py | 8 +- tests/test__service_account_info.py | 3 +- tests/test_aws.py | 74 +++++----- tests/test_downscoped.py | 20 +-- tests/test_external_account.py | 84 ++++++------ tests/test_iam.py | 6 +- tests/test_identity_pool.py | 12 +- tests/test_impersonated_credentials.py | 34 ++--- tests/test_jwt.py | 8 +- tests/transport/compliance.py | 14 +- tests/transport/test_requests.py | 22 +-- tests/transport/test_urllib3.py | 12 +- tests_async/oauth2/test__client_async.py | 15 +- tests_async/transport/async_compliance.py | 16 +-- 72 files changed, 744 insertions(+), 367 deletions(-) create mode 100644 system_tests/system_tests_sync/app_engine_test_app/.gitignore create mode 100644 system_tests/system_tests_sync/app_engine_test_app/app.yaml create mode 100644 system_tests/system_tests_sync/app_engine_test_app/appengine_config.py create mode 100644 system_tests/system_tests_sync/app_engine_test_app/main.py create mode 100644 system_tests/system_tests_sync/app_engine_test_app/requirements.txt create mode 100644 system_tests/system_tests_sync/test_app_engine.py create mode 100644 testing/constraints-2.7.txt diff --git a/google/auth/_cloud_sdk.py b/google/auth/_cloud_sdk.py index 1f13ad420..40e6aec13 100644 --- a/google/auth/_cloud_sdk.py +++ b/google/auth/_cloud_sdk.py @@ -18,6 +18,8 @@ import os import subprocess +import six + from google.auth import environment_vars from google.auth import exceptions @@ -154,4 +156,4 @@ def get_auth_access_token(account=None): new_exc = exceptions.UserAccessTokenError( "Failed to obtain access token", caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) diff --git a/google/auth/_credentials_async.py b/google/auth/_credentials_async.py index 760758d85..d4d4e2c0e 100644 --- a/google/auth/_credentials_async.py +++ b/google/auth/_credentials_async.py @@ -18,10 +18,13 @@ import abc import inspect +import six + from google.auth import credentials -class Credentials(credentials.Credentials, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Credentials): """Async inherited credentials class from google.auth.credentials. The added functionality is the before_request call which requires async/await syntax. @@ -81,7 +84,8 @@ class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): """ -class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReadOnlyScoped(credentials.ReadOnlyScoped): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -167,5 +171,6 @@ def with_scopes_if_required(credentials, scopes): return credentials -class Signing(credentials.Signing, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signing(credentials.Signing): """Interface for credentials that can cryptographically sign messages.""" diff --git a/google/auth/_default.py b/google/auth/_default.py index 8b0573bc8..4ae7c8c06 100644 --- a/google/auth/_default.py +++ b/google/auth/_default.py @@ -23,6 +23,8 @@ import os import warnings +import six + from google.auth import environment_vars from google.auth import exceptions import google.auth.transport._http_client @@ -116,7 +118,7 @@ def load_credentials_from_file( new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -132,7 +134,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -149,7 +151,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") diff --git a/google/auth/_default_async.py b/google/auth/_default_async.py index 3fa125b46..fb277c54e 100644 --- a/google/auth/_default_async.py +++ b/google/auth/_default_async.py @@ -21,6 +21,8 @@ import json import os +import six + from google.auth import _default from google.auth import environment_vars from google.auth import exceptions @@ -61,7 +63,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -77,7 +79,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -94,7 +96,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return credentials, info.get("project_id") else: diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py index 55adf5bc3..b239fcd4f 100644 --- a/google/auth/_helpers.py +++ b/google/auth/_helpers.py @@ -17,7 +17,9 @@ import base64 import calendar import datetime -import urllib + +import six +from six.moves import urllib # Token server doesn't provide a new a token when doing refresh unless the @@ -85,6 +87,9 @@ def datetime_to_secs(value): def to_bytes(value, encoding="utf-8"): """Converts a string value to bytes, if necessary. + Unfortunately, ``six.b`` is insufficient for this task since in + Python 2 because it does not modify ``unicode`` objects. + Args: value (Union[str, bytes]): The value to be converted. encoding (str): The encoding to use to convert unicode to bytes. @@ -97,8 +102,8 @@ def to_bytes(value, encoding="utf-8"): Raises: ValueError: If the value could not be converted to bytes. """ - result = value.encode(encoding) if isinstance(value, str) else value - if isinstance(result, bytes): + result = value.encode(encoding) if isinstance(value, six.text_type) else value + if isinstance(result, six.binary_type): return result else: raise ValueError("{0!r} could not be converted to bytes".format(value)) @@ -117,8 +122,8 @@ def from_bytes(value): Raises: ValueError: If the value could not be converted to unicode. """ - result = value.decode("utf-8") if isinstance(value, bytes) else value - if isinstance(result, str): + result = value.decode("utf-8") if isinstance(value, six.binary_type) else value + if isinstance(result, six.text_type): return result else: raise ValueError("{0!r} could not be converted to unicode".format(value)) @@ -160,7 +165,7 @@ def update_query(url, params, remove=None): query_params.update(params) # Remove any values specified in remove. query_params = { - key: value for key, value in query_params.items() if key not in remove + key: value for key, value in six.iteritems(query_params) if key not in remove } # Re-encoded the query string. new_query = urllib.parse.urlencode(query_params, doseq=True) diff --git a/google/auth/_oauth2client.py b/google/auth/_oauth2client.py index 3512e1d11..95a9876f3 100644 --- a/google/auth/_oauth2client.py +++ b/google/auth/_oauth2client.py @@ -21,6 +21,8 @@ from __future__ import absolute_import +import six + from google.auth import _helpers import google.auth.app_engine import google.auth.compute_engine @@ -32,7 +34,7 @@ import oauth2client.contrib.gce import oauth2client.service_account except ImportError as caught_exc: - raise ImportError("oauth2client is not installed.") from caught_exc + six.raise_from(ImportError("oauth2client is not installed."), caught_exc) try: import oauth2client.contrib.appengine # pytype: disable=import-error @@ -164,4 +166,4 @@ def convert(credentials): return _CLASS_CONVERSION_MAP[credentials_class](credentials) except KeyError as caught_exc: new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) diff --git a/google/auth/_service_account_info.py b/google/auth/_service_account_info.py index 54a40e9da..3d340c78d 100644 --- a/google/auth/_service_account_info.py +++ b/google/auth/_service_account_info.py @@ -17,6 +17,8 @@ import io import json +import six + from google.auth import crypt @@ -41,7 +43,7 @@ def from_dict(data, require=None): """ keys_needed = set(require if require is not None else []) - missing = keys_needed.difference(data) + missing = keys_needed.difference(six.iterkeys(data)) if missing: raise ValueError( diff --git a/google/auth/aws.py b/google/auth/aws.py index c8deee707..925b1ddfb 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -39,13 +39,20 @@ import hashlib import hmac -import http.client import io import json import os +import posixpath import re -import urllib -from urllib.parse import urljoin + +try: + from urllib.parse import urljoin +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from urlparse import urljoin + +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import environment_vars @@ -116,7 +123,9 @@ def get_request_options( # Normalize the URL path. This is needed for the canonical_uri. # os.path.normpath can't be used since it normalizes "/" paths # to "\\" in Windows OS. - normalized_uri = urllib.parse.urlparse(urljoin(url, uri.path)) + normalized_uri = urllib.parse.urlparse( + urljoin(url, posixpath.normpath(uri.path)) + ) # Validate provided URL. if not uri.hostname or uri.scheme != "https": raise ValueError("Invalid AWS service URL") @@ -631,7 +640,7 @@ def _get_metadata_security_credentials(self, request, role_name): else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS security credentials", response_body ) @@ -670,7 +679,7 @@ def _get_metadata_role_name(self, request): else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS role name", response_body ) diff --git a/google/auth/compute_engine/_metadata.py b/google/auth/compute_engine/_metadata.py index af0d84943..9db7bea92 100644 --- a/google/auth/compute_engine/_metadata.py +++ b/google/auth/compute_engine/_metadata.py @@ -18,11 +18,13 @@ """ import datetime -import http.client import json import logging import os -from urllib import parse as urlparse + +import six +from six.moves import http_client +from six.moves.urllib import parse as urlparse from google.auth import _helpers from google.auth import environment_vars @@ -89,7 +91,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) return ( - response.status == http.client.OK + response.status == http_client.OK and metadata_flavor == _METADATA_FLAVOR_VALUE ) @@ -163,7 +165,7 @@ def get( "metadata service. Compute Engine Metadata server unavailable".format(url) ) - if response.status == http.client.OK: + if response.status == http_client.OK: content = _helpers.from_bytes(response.data) if response.headers["content-type"] == "application/json": try: @@ -173,7 +175,7 @@ def get( "Received invalid JSON from the Google Compute Engine" "metadata service: {:.20}".format(content) ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) else: return content else: diff --git a/google/auth/compute_engine/credentials.py b/google/auth/compute_engine/credentials.py index cb4e0f0a0..b39ac50ae 100644 --- a/google/auth/compute_engine/credentials.py +++ b/google/auth/compute_engine/credentials.py @@ -21,6 +21,8 @@ import datetime +import six + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -112,7 +114,7 @@ def refresh(self, request): ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) @property def service_account_email(self): @@ -350,7 +352,7 @@ def _call_metadata_identity_endpoint(self, request): id_token = _metadata.get(request, path, params=params) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) _, payload, _, _ = jwt._unverified_decode(id_token) return id_token, datetime.datetime.fromtimestamp(payload["exp"]) diff --git a/google/auth/credentials.py b/google/auth/credentials.py index 8d9974ce1..ec21a2756 100644 --- a/google/auth/credentials.py +++ b/google/auth/credentials.py @@ -17,10 +17,13 @@ import abc +import six + from google.auth import _helpers -class Credentials(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Credentials(object): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -184,7 +187,8 @@ def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" -class ReadOnlyScoped(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReadOnlyScoped(object): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -325,7 +329,8 @@ def with_scopes_if_required(credentials, scopes, default_scopes=None): return credentials -class Signing(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signing(object): """Interface for credentials that can cryptographically sign messages.""" @abc.abstractmethod diff --git a/google/auth/crypt/__init__.py b/google/auth/crypt/__init__.py index 97e9d81d5..15ac95068 100644 --- a/google/auth/crypt/__init__.py +++ b/google/auth/crypt/__init__.py @@ -37,6 +37,8 @@ version is at least 1.4.0. """ +import six + from google.auth.crypt import base from google.auth.crypt import rsa @@ -88,7 +90,7 @@ class to use for verification. This can be used to select different Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, (str, bytes)): + if isinstance(certs, (six.text_type, six.binary_type)): certs = [certs] for cert in certs: diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index 1c4a9dab8..ec30dd09a 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -21,13 +21,12 @@ from __future__ import absolute_import -import io - from pyasn1.codec.der import decoder from pyasn1_modules import pem from pyasn1_modules.rfc2459 import Certificate from pyasn1_modules.rfc5208 import PrivateKeyInfo import rsa +import six from google.auth import _helpers from google.auth.crypt import base @@ -53,9 +52,9 @@ def _bit_list_to_bytes(bit_list): """ num_bits = len(bit_list) byte_vals = bytearray() - for start in range(0, num_bits, 8): + for start in six.moves.xrange(0, num_bits, 8): curr_bits = bit_list[start : start + 8] - char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits)) + char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) @@ -153,7 +152,7 @@ def from_string(cls, key, key_id=None): """ key = _helpers.from_bytes(key) # PEM expects str in Python 3 marker_id, key_bytes = pem.readPemBlocksFromFile( - io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER ) # Key is in pkcs1 format. diff --git a/google/auth/crypt/base.py b/google/auth/crypt/base.py index 0bda9c345..c98d5bf64 100644 --- a/google/auth/crypt/base.py +++ b/google/auth/crypt/base.py @@ -18,12 +18,15 @@ import io import json +import six + _JSON_FILE_PRIVATE_KEY = "private_key" _JSON_FILE_PRIVATE_KEY_ID = "private_key_id" -class Verifier(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Verifier(object): """Abstract base class for crytographic signature verifiers.""" @abc.abstractmethod @@ -43,7 +46,8 @@ def verify(self, message, signature): raise NotImplementedError("Verify must be implemented") -class Signer(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signer(object): """Abstract base class for cryptographic signers.""" @abc.abstractproperty @@ -66,7 +70,8 @@ def sign(self, message): raise NotImplementedError("Sign must be implemented") -class FromServiceAccountMixin(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class FromServiceAccountMixin(object): """Mix-in to enable factory constructors for a Signer.""" @abc.abstractmethod diff --git a/google/auth/crypt/es256.py b/google/auth/crypt/es256.py index 71dcbfcac..c6d617606 100644 --- a/google/auth/crypt/es256.py +++ b/google/auth/crypt/es256.py @@ -15,6 +15,7 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ +from cryptography import utils import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes @@ -52,8 +53,8 @@ def verify(self, message, signature): sig_bytes = _helpers.to_bytes(signature) if len(sig_bytes) != 64: return False - r = int.from_bytes(sig_bytes[:32], byteorder="big") - s = int.from_bytes(sig_bytes[32:], byteorder="big") + r = utils.int_from_bytes(sig_bytes[:32], byteorder="big") + s = utils.int_from_bytes(sig_bytes[32:], byteorder="big") asn1_sig = encode_dss_signature(r, s) message = _helpers.to_bytes(message) @@ -120,7 +121,7 @@ def sign(self, message): # Convert ASN1 encoded signature to (r||s) raw signature. (r, s) = decode_dss_signature(asn1_signature) - return r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big") + return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32) @classmethod def from_string(cls, key, key_id=None): diff --git a/google/auth/downscoped.py b/google/auth/downscoped.py index 96a4e6547..a1d7b6e46 100644 --- a/google/auth/downscoped.py +++ b/google/auth/downscoped.py @@ -50,6 +50,8 @@ import datetime +import six + from google.auth import _helpers from google.auth import credentials from google.oauth2 import sts @@ -221,7 +223,7 @@ def available_resource(self, value): Raises: TypeError: If the value is not a string. """ - if not isinstance(value, str): + if not isinstance(value, six.string_types): raise TypeError("The provided available_resource is not a string.") self._available_resource = value @@ -247,7 +249,7 @@ def available_permissions(self, value): ValueError: If the value is not valid. """ for available_permission in value: - if not isinstance(available_permission, str): + if not isinstance(available_permission, six.string_types): raise TypeError( "Provided available_permissions are not a list of strings." ) @@ -350,7 +352,7 @@ def expression(self, value): Raises: TypeError: If the value is not of type string. """ - if not isinstance(value, str): + if not isinstance(value, six.string_types): raise TypeError("The provided expression is not a string.") self._expression = value @@ -373,7 +375,7 @@ def title(self, value): Raises: TypeError: If the value is not of type string or None. """ - if not isinstance(value, str) and value is not None: + if not isinstance(value, six.string_types) and value is not None: raise TypeError("The provided title is not a string or None.") self._title = value @@ -396,7 +398,7 @@ def description(self, value): Raises: TypeError: If the value is not of type string or None. """ - if not isinstance(value, str) and value is not None: + if not isinstance(value, six.string_types) and value is not None: raise TypeError("The provided description is not a string or None.") self._description = value diff --git a/google/auth/external_account.py b/google/auth/external_account.py index f588981a0..cbd0baf4e 100644 --- a/google/auth/external_account.py +++ b/google/auth/external_account.py @@ -33,6 +33,8 @@ import json import re +import six + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -50,9 +52,8 @@ _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" -class Credentials( - credentials.Scoped, credentials.CredentialsWithQuotaProject, metaclass=abc.ABCMeta -): +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): """Base class for all external account credentials. This is used to instantiate Credentials for exchanging external account diff --git a/google/auth/iam.py b/google/auth/iam.py index 277f4b7f3..5d63dc5d8 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -20,9 +20,10 @@ """ import base64 -import http.client import json +from six.moves import http_client + from google.auth import _helpers from google.auth import crypt from google.auth import exceptions @@ -76,7 +77,7 @@ def _make_signing_request(self, message): 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: + if response.status != http_client.OK: raise exceptions.TransportError( "Error calling the IAM signBlob API: {}".format(response.data) ) diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index 901fd62fb..fb33d7726 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -33,7 +33,11 @@ access tokens. """ -from collections.abc import Mapping +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping import io import json import os diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index 2704bfdd2..b8a6c49a1 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -28,9 +28,11 @@ import base64 import copy from datetime import datetime -import http.client import json +import six +from six.moves import http_client + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -98,7 +100,7 @@ def _make_iam_token_request( else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: exceptions.RefreshError(_REFRESH_ERROR, response_body) try: @@ -115,7 +117,7 @@ def _make_iam_token_request( ), response_body, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): diff --git a/google/auth/jwt.py b/google/auth/jwt.py index bb9ffae83..d56559510 100644 --- a/google/auth/jwt.py +++ b/google/auth/jwt.py @@ -40,13 +40,18 @@ """ -from collections.abc import Mapping +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping import copy import datetime import json -import urllib import cachetools +import six +from six.moves import urllib from google.auth import _helpers from google.auth import _service_account_info @@ -118,7 +123,7 @@ def _decode_jwt_segment(encoded_section): return json.loads(section_bytes.decode("utf-8")) except ValueError as caught_exc: new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def _unverified_decode(token): @@ -244,16 +249,19 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= try: verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] - except KeyError as caught_exc: + except KeyError as exc: if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: - msg = ( - "The key algorithm {} requires the cryptography package " - "to be installed." + six.raise_from( + ValueError( + "The key algorithm {} requires the cryptography package " + "to be installed.".format(key_alg) + ), + exc, ) else: - msg = "Unsupported signature algorithm {}" - new_exc = ValueError(msg.format(key_alg)) - raise new_exc from caught_exc + six.raise_from( + ValueError("Unsupported signature algorithm {}".format(key_alg)), exc + ) # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. diff --git a/google/auth/transport/__init__.py b/google/auth/transport/__init__.py index d1b035df9..374e7b4d7 100644 --- a/google/auth/transport/__init__.py +++ b/google/auth/transport/__init__.py @@ -25,9 +25,11 @@ """ import abc -import http.client -DEFAULT_REFRESH_STATUS_CODES = (http.client.UNAUTHORIZED,) +import six +from six.moves import http_client + +DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) """Sequence[int]: Which HTTP status code indicate that credentials should be refreshed and a request should be retried. """ @@ -36,7 +38,8 @@ """int: How many times to refresh the credentials and retry a request.""" -class Response(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Response(object): """HTTP Response data.""" @abc.abstractproperty @@ -55,7 +58,8 @@ def data(self): raise NotImplementedError("data must be implemented.") -class Request(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Request(object): """Interface for a callable that makes HTTP requests. Specific transport implementations should provide an implementation of diff --git a/google/auth/transport/_aiohttp_requests.py b/google/auth/transport/_aiohttp_requests.py index ee9404330..ab7dfef67 100644 --- a/google/auth/transport/_aiohttp_requests.py +++ b/google/auth/transport/_aiohttp_requests.py @@ -24,6 +24,7 @@ import functools import aiohttp +import six import urllib3 from google.auth import exceptions @@ -190,11 +191,11 @@ async def __call__( except aiohttp.ClientError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) except asyncio.TimeoutError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class AuthorizedSession(aiohttp.ClientSession): diff --git a/google/auth/transport/_http_client.py b/google/auth/transport/_http_client.py index 679087f06..c153763ef 100644 --- a/google/auth/transport/_http_client.py +++ b/google/auth/transport/_http_client.py @@ -14,10 +14,12 @@ """Transport adapter for http.client, for internal use only.""" -import http.client import logging import socket -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import transport @@ -96,7 +98,7 @@ def __call__( "was specified".format(parts.scheme) ) - connection = http.client.HTTPConnection(parts.netloc, timeout=timeout) + connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: _LOGGER.debug("Making request: %s %s", method, url) @@ -105,9 +107,9 @@ def __call__( response = connection.getresponse() return Response(response) - except (http.client.HTTPException, socket.error) as caught_exc: + except (http_client.HTTPException, socket.error) as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) finally: connection.close() diff --git a/google/auth/transport/_mtls_helper.py b/google/auth/transport/_mtls_helper.py index 1b9b9c285..4dccb1062 100644 --- a/google/auth/transport/_mtls_helper.py +++ b/google/auth/transport/_mtls_helper.py @@ -20,6 +20,8 @@ import re import subprocess +import six + from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" @@ -80,7 +82,7 @@ def _read_dca_metadata_file(metadata_path): metadata = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return metadata @@ -108,7 +110,7 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): stdout, stderr = process.communicate() except OSError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # Check cert provider command execution error. if process.returncode != 0: diff --git a/google/auth/transport/grpc.py b/google/auth/transport/grpc.py index 160dc946b..c47cb3dda 100644 --- a/google/auth/transport/grpc.py +++ b/google/auth/transport/grpc.py @@ -19,6 +19,8 @@ import logging import os +import six + from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -27,11 +29,13 @@ try: import grpc except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "gRPC is not installed, please install the grpcio package " - "to use the gRPC transport." + six.raise_from( + ImportError( + "gRPC is not installed, please install the grpcio package " + "to use the gRPC transport." + ), + caught_exc, ) - raise new_exc from caught_exc _LOGGER = logging.getLogger(__name__) @@ -84,7 +88,7 @@ def _get_authorization_headers(self, context): self._request, context.method_name, context.service_url, headers ) - return list(headers.items()) + return list(six.iteritems(headers)) def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -333,7 +337,7 @@ def ssl_credentials(self): ) except exceptions.ClientCertError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/google/auth/transport/mtls.py b/google/auth/transport/mtls.py index c5707617f..b40bfbedf 100644 --- a/google/auth/transport/mtls.py +++ b/google/auth/transport/mtls.py @@ -14,6 +14,8 @@ """Utilites for mutual TLS.""" +import six + from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -51,7 +53,7 @@ def callback(): _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() except (OSError, RuntimeError, ValueError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return cert_bytes, key_bytes @@ -96,7 +98,7 @@ def callback(): key_file.write(key_bytes) except (exceptions.ClientCertError, OSError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return cert_path, key_path, passphrase_bytes diff --git a/google/auth/transport/requests.py b/google/auth/transport/requests.py index 2cb694247..817176bef 100644 --- a/google/auth/transport/requests.py +++ b/google/auth/transport/requests.py @@ -25,16 +25,21 @@ try: import requests except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "The requests library is not installed, please install the " - "requests package to use the requests transport." + import six + + six.raise_from( + ImportError( + "The requests library is not installed, please install the " + "requests package to use the requests transport." + ), + caught_exc, ) - raise new_exc from caught_exc import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports from requests.packages.urllib3.util.ssl_ import ( create_urllib3_context, ) # pylint: disable=ungrouped-imports +import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -181,7 +186,7 @@ def __call__( return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class _MutualTlsAdapter(requests.adapters.HTTPAdapter): @@ -391,7 +396,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) try: ( @@ -411,7 +416,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def request( self, diff --git a/google/auth/transport/urllib3.py b/google/auth/transport/urllib3.py index aa7188c55..6a2504d97 100644 --- a/google/auth/transport/urllib3.py +++ b/google/auth/transport/urllib3.py @@ -34,11 +34,16 @@ try: import urllib3 except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "The urllib3 library is not installed, please install the " - "urllib3 package to use the urllib3 transport." + import six + + six.raise_from( + ImportError( + "The urllib3 library is not installed, please install the " + "urllib3 package to use the urllib3 transport." + ), + caught_exc, ) - raise new_exc from caught_exc +import six import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import environment_vars @@ -137,7 +142,7 @@ def __call__( return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def _make_default_http(): @@ -329,7 +334,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) try: found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( @@ -346,7 +351,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/google/oauth2/_client.py b/google/oauth2/_client.py index f819371af..2f4e8474b 100644 --- a/google/oauth2/_client.py +++ b/google/oauth2/_client.py @@ -24,9 +24,11 @@ """ import datetime -import http.client import json -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -118,7 +120,7 @@ def _token_endpoint_request_no_throw( ) response_data = json.loads(response_body) - if response.status == http.client.OK: + if response.status == http_client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -129,9 +131,9 @@ def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data def _token_endpoint_request( @@ -194,7 +196,7 @@ def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) expiry = _parse_expiry(response_data) @@ -234,7 +236,7 @@ def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) @@ -263,7 +265,7 @@ def _handle_refresh_grant_response(response_data, refresh_token): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) refresh_token = response_data.get("refresh_token", refresh_token) expiry = _parse_expiry(response_data) diff --git a/google/oauth2/_client_async.py b/google/oauth2/_client_async.py index 8849023e7..cf5121137 100644 --- a/google/oauth2/_client_async.py +++ b/google/oauth2/_client_async.py @@ -24,9 +24,11 @@ """ import datetime -import http.client import json -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import jwt @@ -83,7 +85,7 @@ async def _token_endpoint_request_no_throw( response_data = json.loads(response_body) - if response.status == http.client.OK: + if response.status == http_client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -94,9 +96,9 @@ async def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data async def _token_endpoint_request( @@ -159,7 +161,7 @@ async def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) expiry = client._parse_expiry(response_data) @@ -199,7 +201,7 @@ async def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) diff --git a/google/oauth2/_id_token_async.py b/google/oauth2/_id_token_async.py index a4a526dc0..31fcbc623 100644 --- a/google/oauth2/_id_token_async.py +++ b/google/oauth2/_id_token_async.py @@ -58,10 +58,12 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ -import http.client import json import os +import six +from six.moves import http_client + from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -86,7 +88,7 @@ async def _fetch_certs(request, certs_url): """ response = await request(certs_url, method="GET") - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -241,7 +243,7 @@ async def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # 2. Try to fetch ID token from metada server if it exists. The code works # for GAE and Cloud Run metadata server as well. diff --git a/google/oauth2/_reauth_async.py b/google/oauth2/_reauth_async.py index f74f50b43..0276ddd0b 100644 --- a/google/oauth2/_reauth_async.py +++ b/google/oauth2/_reauth_async.py @@ -34,6 +34,8 @@ import sys +from six.moves import range + from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import _client_async diff --git a/google/oauth2/challenges.py b/google/oauth2/challenges.py index 0baff62e0..95e76cb32 100644 --- a/google/oauth2/challenges.py +++ b/google/oauth2/challenges.py @@ -20,6 +20,8 @@ import getpass import sys +import six + from google.auth import _helpers from google.auth import exceptions @@ -45,7 +47,8 @@ def get_user_password(text): return getpass.getpass(text) -class ReauthChallenge(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReauthChallenge(object): """Base class for reauth challenges.""" @property diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 6d34edf04..9b59f8cf5 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -35,6 +35,8 @@ import io import json +import six + from google.auth import _cloud_sdk from google.auth import _helpers from google.auth import credentials @@ -262,7 +264,7 @@ def refresh(self, request): if self._refresh_token is None and self.refresh_handler: token, expiry = self.refresh_handler(request, scopes=scopes) # Validate returned data. - if not isinstance(token, str): + if not isinstance(token, six.string_types): raise exceptions.RefreshError( "The refresh_handler returned token is not a string." ) @@ -344,7 +346,7 @@ def from_authorized_user_info(cls, info, scopes=None): ValueError: If the info is not in the expected format. """ keys_needed = set(("refresh_token", "client_id", "client_secret")) - missing = keys_needed.difference(info) + missing = keys_needed.difference(six.iterkeys(info)) if missing: raise ValueError( @@ -364,7 +366,7 @@ def from_authorized_user_info(cls, info, scopes=None): # process scopes, which needs to be a seq if scopes is None and "scopes" in info: scopes = info.get("scopes") - if isinstance(scopes, str): + if isinstance(scopes, six.string_types): scopes = scopes.split(" ") return cls( diff --git a/google/oauth2/id_token.py b/google/oauth2/id_token.py index 25492ca6c..8d0f85a59 100644 --- a/google/oauth2/id_token.py +++ b/google/oauth2/id_token.py @@ -55,10 +55,12 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ -import http.client import json import os +import six +from six.moves import http_client + from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -95,7 +97,7 @@ def _fetch_certs(request, certs_url): """ response = request(certs_url, method="GET") - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -240,7 +242,7 @@ def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # 2. Try to fetch ID token from metada server if it exists. The code # works for GAE and Cloud Run metadata server as well. diff --git a/google/oauth2/reauth.py b/google/oauth2/reauth.py index 1e496d12e..cbf1d7f09 100644 --- a/google/oauth2/reauth.py +++ b/google/oauth2/reauth.py @@ -34,6 +34,8 @@ import sys +from six.moves import range + from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import challenges diff --git a/google/oauth2/sts.py b/google/oauth2/sts.py index 9f2d68af3..ae3c0146b 100644 --- a/google/oauth2/sts.py +++ b/google/oauth2/sts.py @@ -31,9 +31,10 @@ .. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 """ -import http.client import json -import urllib + +from six.moves import http_client +from six.moves import urllib from google.oauth2 import utils @@ -145,7 +146,7 @@ def exchange_token( ) # If non-200 response received, translate to OAuthError exception. - if response.status != http.client.OK: + if response.status != http_client.OK: utils.handle_error_response(response_body) response_data = json.loads(response_body) diff --git a/google/oauth2/utils.py b/google/oauth2/utils.py index c57833daf..593f03236 100644 --- a/google/oauth2/utils.py +++ b/google/oauth2/utils.py @@ -45,6 +45,8 @@ import enum import json +import six + from google.auth import exceptions @@ -75,7 +77,8 @@ def __init__(self, client_auth_type, client_id, client_secret=None): self.client_secret = client_secret -class OAuthClientAuthHandler(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class OAuthClientAuthHandler(object): """Abstract class for handling client authentication in OAuth-based operations. """ diff --git a/noxfile.py b/noxfile.py index e238c973c..885dbd61a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -81,6 +81,23 @@ def unit(session): ) +@nox.session(python=["2.7"]) +def unit_prev_versions(session): + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + session.install("-r", "testing/requirements.txt", "-c", constraints_path) + session.install("-e", ".", "-c", constraints_path) + session.run( + "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "tests", + ) + + @nox.session(python="3.7") def cover(session): session.install("-r", "testing/requirements.txt") diff --git a/setup.py b/setup.py index 343e660f1..301e99643 100644 --- a/setup.py +++ b/setup.py @@ -20,16 +20,25 @@ DEPENDENCIES = ( - "cachetools >= 2.0.0, < 5.0", - "pyasn1-modules >= 0.2.1", - "rsa >= 3.1.4, < 5", - "setuptools >= 40.3.0", + "cachetools>=2.0.0,<5.0", + "pyasn1-modules>=0.2.1", + # rsa==4.5 is the last version to support 2.7 + # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 + 'rsa<4.6; python_version < "3.6"', + 'rsa>=3.1.4,<5; python_version >= "3.6"', + # install enum34 to support 2.7. enum34 only works up to python version 3.3. + 'enum34>=1.1.10; python_version < "3.4"', + "setuptools>=40.3.0", + "six>=1.9.0", ) extras = { - "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0dev", "requests >= 2.20.0, < 3.0.0dev"], - "pyopenssl": "pyopenssl >= 20.0.0", - "reauth": "pyu2f >= 0.1.5", + "aiohttp": [ + "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", + "requests >= 2.20.0, < 3.0.0dev", + ], + "pyopenssl": "pyopenssl>=20.0.0", + "reauth": "pyu2f>=0.1.5", } with io.open("README.rst", "r") as fh: @@ -54,7 +63,7 @@ namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">= 3.6", + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ diff --git a/system_tests/noxfile.py b/system_tests/noxfile.py index 540727e48..459b71c78 100644 --- a/system_tests/noxfile.py +++ b/system_tests/noxfile.py @@ -171,7 +171,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] -PYTHON_VERSIONS_SYNC = ["3.7"] +PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] def default(session, *test_paths): @@ -287,6 +287,50 @@ def compute_engine(session): ) +@nox.session(python=["2.7"]) +def app_engine(session): + if SKIP_GAE_TEST_ENV in os.environ: + session.log("Skipping App Engine tests.") + return + + session.install(LIBRARY_DIR) + # Unlike the default tests above, the App Engine system test require a + # 'real' gcloud sdk installation that is configured to deploy to an + # app engine project. + # Grab the project ID from the cloud sdk. + project_id = ( + subprocess.check_output( + ["gcloud", "config", "list", "project", "--format", "value(core.project)"] + ) + .decode("utf-8") + .strip() + ) + + if not project_id: + session.error( + "The Cloud SDK must be installed and configured to deploy to App " "Engine." + ) + + application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) + + # Vendor in the test application's dependencies + session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) + session.install(*TEST_DEPENDENCIES_SYNC) + session.run( + "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True + ) + + # Deploy the application. + session.run("gcloud", "app", "deploy", "-q", "app.yaml") + + # Run the tests + session.env["TEST_APP_URL"] = application_url + session.chdir(HERE) + default( + session, "system_tests_sync/test_app_engine.py", + ) + + @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) @@ -339,9 +383,8 @@ def mtls_http(session): def external_accounts(session): session.install( *TEST_DEPENDENCIES_SYNC, - "google-auth", + LIBRARY_DIR, "google-api-python-client", - "enum34", ) default( session, @@ -354,7 +397,7 @@ def external_accounts(session): def downscoping(session): session.install( *TEST_DEPENDENCIES_SYNC, - "google-auth", + LIBRARY_DIR, "google-cloud-storage", ) default( diff --git a/system_tests/system_tests_sync/app_engine_test_app/.gitignore b/system_tests/system_tests_sync/app_engine_test_app/.gitignore new file mode 100644 index 000000000..7951405f8 --- /dev/null +++ b/system_tests/system_tests_sync/app_engine_test_app/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/system_tests/system_tests_sync/app_engine_test_app/app.yaml b/system_tests/system_tests_sync/app_engine_test_app/app.yaml new file mode 100644 index 000000000..06f227030 --- /dev/null +++ b/system_tests/system_tests_sync/app_engine_test_app/app.yaml @@ -0,0 +1,12 @@ +api_version: 1 +service: google-auth-system-tests +runtime: python27 +threadsafe: true + +handlers: +- url: .* + script: main.app + +libraries: +- name: ssl + version: 2.7.11 \ No newline at end of file diff --git a/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py b/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py new file mode 100644 index 000000000..1197ab526 --- /dev/null +++ b/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py @@ -0,0 +1,30 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add("lib") + + +# Patch os.path.expanduser. This should be fixed in GAE +# versions released after Nov 2016. +import os.path + + +def patched_expanduser(path): + return path + + +os.path.expanduser = patched_expanduser \ No newline at end of file diff --git a/system_tests/system_tests_sync/app_engine_test_app/main.py b/system_tests/system_tests_sync/app_engine_test_app/main.py new file mode 100644 index 000000000..f44ed4c79 --- /dev/null +++ b/system_tests/system_tests_sync/app_engine_test_app/main.py @@ -0,0 +1,129 @@ +# Copyright 2016 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""App Engine standard application that runs basic system tests for +google.auth.app_engine. +This application has to run tests manually instead of using pytest because +pytest currently doesn't work on App Engine standard. +""" + +import contextlib +import json +import sys +from StringIO import StringIO +import traceback + +from google.appengine.api import app_identity +import google.auth +from google.auth import _helpers +from google.auth import app_engine +import google.auth.transport.urllib3 +import urllib3.contrib.appengine +import webapp2 + +FAILED_TEST_TMPL = """ +Test {} failed: {} +Stacktrace: +{} +Captured output: +{} +""" +TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" +EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email" +HTTP = urllib3.contrib.appengine.AppEngineManager() +HTTP_REQUEST = google.auth.transport.urllib3.Request(HTTP) + + +def test_credentials(): + credentials = app_engine.Credentials() + scoped_credentials = credentials.with_scopes([EMAIL_SCOPE]) + + scoped_credentials.refresh(None) + + assert scoped_credentials.valid + assert scoped_credentials.token is not None + + # Get token info and verify scope + url = _helpers.update_query( + TOKEN_INFO_URL, {"access_token": scoped_credentials.token} + ) + response = HTTP_REQUEST(url=url, method="GET") + token_info = json.loads(response.data.decode("utf-8")) + + assert token_info["scope"] == EMAIL_SCOPE + + +def test_default(): + credentials, project_id = google.auth.default() + + assert isinstance(credentials, app_engine.Credentials) + assert project_id == app_identity.get_application_id() + + +@contextlib.contextmanager +def capture(): + """Context manager that captures stderr and stdout.""" + oldout, olderr = sys.stdout, sys.stderr + try: + out = StringIO() + sys.stdout, sys.stderr = out, out + yield out + finally: + sys.stdout, sys.stderr = oldout, olderr + + +def run_test_func(func): + with capture() as capsys: + try: + func() + return True, "" + except Exception as exc: + output = FAILED_TEST_TMPL.format( + func.func_name, exc, traceback.format_exc(), capsys.getvalue() + ) + return False, output + + +def run_tests(): + """Runs all tests. + Returns: + Tuple[bool, str]: A tuple containing True if all tests pass, False + otherwise, and any captured output from the tests. + """ + status = True + output = "" + + tests = (test_credentials, test_default) + + for test in tests: + test_status, test_output = run_test_func(test) + status = status and test_status + output += test_output + + return status, output + + +class MainHandler(webapp2.RequestHandler): + def get(self): + self.response.headers["content-type"] = "text/plain" + + status, output = run_tests() + + if not status: + self.response.status = 500 + + self.response.write(output) + + +app = webapp2.WSGIApplication([("/", MainHandler)], debug=True) \ No newline at end of file diff --git a/system_tests/system_tests_sync/app_engine_test_app/requirements.txt b/system_tests/system_tests_sync/app_engine_test_app/requirements.txt new file mode 100644 index 000000000..cb8a38216 --- /dev/null +++ b/system_tests/system_tests_sync/app_engine_test_app/requirements.txt @@ -0,0 +1,3 @@ +urllib3 +# Relative path to google-auth-python's source. +../../.. \ No newline at end of file diff --git a/system_tests/system_tests_sync/test_app_engine.py b/system_tests/system_tests_sync/test_app_engine.py new file mode 100644 index 000000000..79776ce27 --- /dev/null +++ b/system_tests/system_tests_sync/test_app_engine.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +TEST_APP_URL = os.environ["TEST_APP_URL"] + + +def test_live_application(http_request): + response = http_request(method="GET", url=TEST_APP_URL) + assert response.status == 200, response.data.decode("utf-8") \ No newline at end of file diff --git a/system_tests/system_tests_sync/test_downscoping.py b/system_tests/system_tests_sync/test_downscoping.py index 77224aeae..fdb4efaed 100644 --- a/system_tests/system_tests_sync/test_downscoping.py +++ b/system_tests/system_tests_sync/test_downscoping.py @@ -28,7 +28,7 @@ # The object prefix used to test access to files beginning with this prefix. _OBJECT_PREFIX = "customer-a" # The object name of the object inaccessible by the downscoped token. -_ACCESSIBLE_OBJECT_NAME = f"{_OBJECT_PREFIX}-data.txt" +_ACCESSIBLE_OBJECT_NAME = "{0}-data.txt".format(_OBJECT_PREFIX) # The content of the object accessible by the downscoped token. _ACCESSIBLE_CONTENT = "hello world" # The content of the object inaccessible by the downscoped token. @@ -76,13 +76,13 @@ def get_token_from_broker(bucket_name, object_prefix): Tuple[str, datetime.datetime]: The downscoped access token and its expiry date. """ # Initialize the Credential Access Boundary rules. - available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}" + available_resource = "//storage.googleapis.com/projects/_/buckets/{0}".format(bucket_name) # Downscoped credentials will have readonly access to the resource. available_permissions = ["inRole:roles/storage.objectViewer"] # Only objects starting with the specified prefix string in the object name # will be allowed read access. availability_expression = ( - f"resource.name.startsWith('projects/_/buckets/{bucket_name}/objects/{object_prefix}')" + "resource.name.startsWith('projects/_/buckets/{0}/objects/{1}')".format(bucket_name, object_prefix) ) availability_condition = downscoped.AvailabilityCondition(availability_expression) # Define the single access boundary rule using the above properties. diff --git a/system_tests/system_tests_sync/test_external_accounts.py b/system_tests/system_tests_sync/test_external_accounts.py index c2855a2c3..e24c7b40a 100644 --- a/system_tests/system_tests_sync/test_external_accounts.py +++ b/system_tests/system_tests_sync/test_external_accounts.py @@ -32,21 +32,19 @@ # original service account key. -from http.server import BaseHTTPRequestHandler -from http.server import HTTPServer import json import os import socket -import sys from tempfile import NamedTemporaryFile import threading -import pytest -from mock import patch - +import sys import google.auth from googleapiclient import discovery +from six.moves import BaseHTTPServer from google.oauth2 import service_account +import pytest +from mock import patch # Populate values from the output of scripts/setup_external_accounts.sh. _AUDIENCE_OIDC = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/oidc-73wslmxn" @@ -177,7 +175,7 @@ def test_file_based_external_account( # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): - class TestResponseHandler(BaseHTTPRequestHandler): + class TestResponseHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): if self.headers["my-header"] != "expected-value": self.send_response(400) @@ -201,7 +199,7 @@ def do_GET(self): json.dumps({"access_token": oidc_credentials.token}).encode("utf-8") ) - class TestHTTPServer(HTTPServer, object): + class TestHTTPServer(BaseHTTPServer.HTTPServer, object): def __init__(self): self.port = self._find_open_port() super(TestHTTPServer, self).__init__(("", self.port), TestResponseHandler) diff --git a/testing/constraints-2.7.txt b/testing/constraints-2.7.txt new file mode 100644 index 000000000..dcc09f75e --- /dev/null +++ b/testing/constraints-2.7.txt @@ -0,0 +1 @@ +rsa==3.1.4 \ No newline at end of file diff --git a/tests/compute_engine/test__metadata.py b/tests/compute_engine/test__metadata.py index 0bb07b007..852822dc0 100644 --- a/tests/compute_engine/test__metadata.py +++ b/tests/compute_engine/test__metadata.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime -import http.client -import importlib import json import os import mock import pytest +from six.moves import http_client +from six.moves import reload_module from google.auth import _helpers from google.auth import environment_vars @@ -30,7 +30,7 @@ PATH = "instance/service-accounts/default" -def make_request(data, status=http.client.OK, headers=None, retry=False): +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) @@ -90,13 +90,13 @@ def test_ping_success_custom_root(): fake_ip = "1.2.3.4" os.environ[environment_vars.GCE_METADATA_IP] = fake_ip - importlib.reload(_metadata) + reload_module(_metadata) try: assert _metadata.ping(request) finally: del os.environ[environment_vars.GCE_METADATA_IP] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -203,13 +203,13 @@ def test_get_success_custom_root_new_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_HOST] = fake_root - importlib.reload(_metadata) + reload_module(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_HOST] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -223,13 +223,13 @@ def test_get_success_custom_root_old_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root - importlib.reload(_metadata) + reload_module(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_ROOT] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -239,7 +239,7 @@ def test_get_success_custom_root_old_variable(): def test_get_failure(): - request = make_request("Metadata error", status=http.client.NOT_FOUND) + request = make_request("Metadata error", status=http_client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) diff --git a/tests/crypt/test__cryptography_rsa.py b/tests/crypt/test__cryptography_rsa.py index 41dfc3693..dbf07c780 100644 --- a/tests/crypt/test__cryptography_rsa.py +++ b/tests/crypt/test__cryptography_rsa.py @@ -60,7 +60,7 @@ class TestRSAVerifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -68,8 +68,8 @@ def test_verify_bytes_success(self): verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index 9ef29ee15..886ee55a2 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import json import os @@ -20,6 +19,7 @@ from pyasn1_modules import pem import pytest import rsa +import six from google.auth import _helpers from google.auth.crypt import _python_rsa @@ -63,7 +63,7 @@ class TestRSAVerifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -71,8 +71,8 @@ def test_verify_bytes_success(self): verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -141,7 +141,7 @@ def test_from_string_pkcs8(self): def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( - io.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER + six.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER ) key_info, remaining = None, "extra" diff --git a/tests/crypt/test_es256.py b/tests/crypt/test_es256.py index 720f74ca2..5bb9050cd 100644 --- a/tests/crypt/test_es256.py +++ b/tests/crypt/test_es256.py @@ -50,7 +50,7 @@ class TestES256Verifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -58,8 +58,8 @@ def test_verify_bytes_success(self): verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/tests/oauth2/test__client.py b/tests/oauth2/test__client.py index 690a87bc4..54686df59 100644 --- a/tests/oauth2/test__client.py +++ b/tests/oauth2/test__client.py @@ -13,13 +13,14 @@ # limitations under the License. import datetime -import http.client import json import os -import urllib import mock import pytest +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import crypt @@ -74,7 +75,7 @@ def test__parse_expiry_none(): assert _client._parse_expiry({}) is None -def make_request(response_data, status=http.client.OK): +def make_request(response_data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(response_data).encode("utf-8") @@ -129,7 +130,7 @@ def test__token_endpoint_request_use_json(): def test__token_endpoint_request_error(): - request = make_request({}, status=http.client.BAD_REQUEST) + request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request(request, "http://example.com", {}) @@ -137,7 +138,7 @@ def test__token_endpoint_request_error(): def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -146,7 +147,7 @@ def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http.client.BAD_REQUEST + {"error": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -159,7 +160,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in params.items(): + for key, value in six.iteritems(params): assert request_params[key][0] == value diff --git a/tests/oauth2/test_sts.py b/tests/oauth2/test_sts.py index b516c8a5b..e8e008df5 100644 --- a/tests/oauth2/test_sts.py +++ b/tests/oauth2/test_sts.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import transport @@ -67,7 +67,7 @@ def make_client(cls, client_auth=None): return sts.Client(cls.TOKEN_EXCHANGE_ENDPOINT, client_auth) @classmethod - def make_mock_request(cls, data, status=http.client.OK): + def make_mock_request(cls, data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -110,7 +110,7 @@ def test_exchange_token_full_success_without_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -145,7 +145,7 @@ def test_exchange_token_partial_success_without_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -165,7 +165,7 @@ def test_exchange_token_non200_without_auth(self): """ client = self.make_client() request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -209,7 +209,7 @@ def test_exchange_token_full_success_with_basic_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -247,7 +247,7 @@ def test_exchange_token_partial_success_with_basic_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -268,7 +268,7 @@ def test_exchange_token_non200_with_basic_auth(self): """ client = self.make_client(self.CLIENT_AUTH_BASIC) request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -313,7 +313,7 @@ def test_exchange_token_full_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -350,7 +350,7 @@ def test_exchange_token_partial_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -371,7 +371,7 @@ def test_exchange_token_non200_with_reqbody_auth(self): """ client = self.make_client(self.CLIENT_AUTH_REQUEST_BODY) request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: diff --git a/tests/test__helpers.py b/tests/test__helpers.py index 906cf126e..0c0bad2d2 100644 --- a/tests/test__helpers.py +++ b/tests/test__helpers.py @@ -13,9 +13,9 @@ # limitations under the License. import datetime -import urllib import pytest +from six.moves import urllib from google.auth import _helpers @@ -65,8 +65,8 @@ def test_to_bytes_with_bytes(): assert _helpers.to_bytes(value) == value -def test_to_bytes_with_text(): - value = "string-val" +def test_to_bytes_with_unicode(): + value = u"string-val" encoded_value = b"string-val" assert _helpers.to_bytes(value) == encoded_value @@ -76,14 +76,14 @@ def test_to_bytes_with_nonstring_type(): _helpers.to_bytes(object()) -def test_from_bytes_with_text(): - value = "bytes-val" +def test_from_bytes_with_unicode(): + value = u"bytes-val" assert _helpers.from_bytes(value) == value def test_from_bytes_with_bytes(): value = b"string-val" - decoded_value = "string-val" + decoded_value = u"string-val" assert _helpers.from_bytes(value) == decoded_value diff --git a/tests/test__oauth2client.py b/tests/test__oauth2client.py index aa06eced2..6b1112b50 100644 --- a/tests/test__oauth2client.py +++ b/tests/test__oauth2client.py @@ -13,7 +13,6 @@ # limitations under the License. import datetime -import importlib import os import sys @@ -22,6 +21,7 @@ import oauth2client.contrib.gce import oauth2client.service_account import pytest +from six.moves import reload_module from google.auth import _oauth2client @@ -152,19 +152,19 @@ def test_convert_not_found(): @pytest.fixture def reset__oauth2client_module(): """Reloads the _oauth2client module after a test.""" - importlib.reload(_oauth2client) + reload_module(_oauth2client) def test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): - importlib.reload(_oauth2client) + reload_module(_oauth2client) assert _oauth2client._HAS_APPENGINE def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): monkeypatch.setitem(sys.modules, "oauth2client", None) with pytest.raises(ImportError) as excinfo: - importlib.reload(_oauth2client) + reload_module(_oauth2client) assert excinfo.match("oauth2client") diff --git a/tests/test__service_account_info.py b/tests/test__service_account_info.py index fd2d8c8be..13b2f85a2 100644 --- a/tests/test__service_account_info.py +++ b/tests/test__service_account_info.py @@ -16,6 +16,7 @@ import os import pytest +import six from google.auth import _service_account_info from google.auth import crypt @@ -54,7 +55,7 @@ def test_from_dict_bad_format(): def test_from_filename(): info, signer = _service_account_info.from_filename(SERVICE_ACCOUNT_JSON_FILE) - for key, value in SERVICE_ACCOUNT_INFO.items(): + for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): assert info[key] == value assert isinstance(signer, crypt.RSASigner) diff --git a/tests/test_aws.py b/tests/test_aws.py index 86594376e..9ca08d5b2 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import aws @@ -952,11 +952,11 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -987,9 +987,9 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( # Retrieve subject_token again. Region should not be queried again. new_request = self.make_mock_request( - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) @@ -1020,11 +1020,11 @@ def test_retrieve_subject_token_success_permanent_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=security_creds_response, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1136,7 +1136,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( ) # Region will be queried since it is not found in envvars. request = self.make_mock_request( - region_status=http.client.OK, region_name=self.AWS_REGION + region_status=http_client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1152,7 +1152,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( def test_retrieve_subject_token_error_determining_aws_region(self): # Simulate error in retrieving the AWS region. - request = self.make_mock_request(region_status=http.client.BAD_REQUEST) + request = self.make_mock_request(region_status=http_client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -1163,9 +1163,9 @@ def test_retrieve_subject_token_error_determining_aws_region(self): def test_retrieve_subject_token_error_determining_aws_role(self): # Simulate error in retrieving the AWS role name. request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.BAD_REQUEST, + role_status=http_client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1180,7 +1180,7 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): credential_source = self.CREDENTIAL_SOURCE.copy() credential_source.pop("url") request = self.make_mock_request( - region_status=http.client.OK, region_name=self.AWS_REGION + region_status=http_client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=credential_source) @@ -1194,11 +1194,11 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): def test_retrieve_subject_token_error_determining_aws_security_creds(self): # Simulate error in retrieving the AWS security credentials. request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.BAD_REQUEST, + security_credentials_status=http_client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1232,13 +1232,13 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1288,13 +1288,13 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1362,15 +1362,15 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1446,15 +1446,15 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1488,7 +1488,7 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): assert credentials.default_scopes == SCOPES def test_refresh_with_retrieve_subject_token_error(self): - request = self.make_mock_request(region_status=http.client.BAD_REQUEST) + request = self.make_mock_request(region_status=http_client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: diff --git a/tests/test_downscoped.py b/tests/test_downscoped.py index a686391c0..9ca95f5aa 100644 --- a/tests/test_downscoped.py +++ b/tests/test_downscoped.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import credentials @@ -461,7 +461,7 @@ def make_credentials(source_credentials=SourceCredentials(), quota_project_id=No ) @staticmethod - def make_mock_request(data, status=http.client.OK): + def make_mock_request(data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -521,7 +521,7 @@ def test_refresh(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) source_credentials = SourceCredentials() credentials = self.make_credentials(source_credentials=source_credentials) @@ -563,7 +563,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_credentials(source_credentials=source_credentials) # Spy on calls to source credentials refresh to confirm the expected request @@ -583,7 +583,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): def test_refresh_token_exchange_error(self): request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=ERROR_RESPONSE ) credentials = self.make_credentials() @@ -612,7 +612,7 @@ def test_refresh_source_credentials_refresh_error(self): def test_apply_without_quota_project_id(self): headers = {} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.refresh(request) @@ -624,7 +624,7 @@ def test_apply_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials(quota_project_id=QUOTA_PROJECT_ID) credentials.refresh(request) @@ -638,7 +638,7 @@ def test_apply_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() # First call should call refresh, setting the token. @@ -662,7 +662,7 @@ def test_before_request(self): @mock.patch("google.auth._helpers.utcnow") def test_before_request_expired(self, utcnow): headers = {} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.token = "token" utcnow.return_value = datetime.datetime.min diff --git a/tests/test_external_account.py b/tests/test_external_account.py index 97f1564ef..3c34f998c 100644 --- a/tests/test_external_account.py +++ b/tests/test_external_account.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -208,7 +208,7 @@ def make_workforce_pool_credentials( @classmethod def make_mock_request( cls, - status=http.client.OK, + status=http_client.OK, data=None, impersonation_status=None, impersonation_data=None, @@ -605,7 +605,7 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): "subject_token": "subject_token_0", "subject_token_type": self.SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_credentials() credentials.refresh(request) @@ -635,7 +635,7 @@ def test_refresh_workforce_without_client_auth_success(self, unused_utcnow): json.dumps({"userProject": self.WORKFORCE_POOL_USER_PROJECT}) ), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT ) @@ -667,7 +667,7 @@ def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): "subject_token": "subject_token_0", "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) # Client Auth will have higher priority over workforce_pool_user_project. credentials = self.make_workforce_pool_credentials( client_id=CLIENT_ID, @@ -704,7 +704,7 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( "subject_token": "subject_token_0", "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) # Client Auth will be sufficient for user project determination. credentials = self.make_workforce_pool_credentials( client_id=CLIENT_ID, @@ -754,9 +754,9 @@ def test_refresh_impersonation_without_client_auth_success(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -821,9 +821,9 @@ def test_refresh_workforce_impersonation_without_client_auth_success(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -865,7 +865,7 @@ def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=["scope1", "scope2"], @@ -893,7 +893,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=None, @@ -911,7 +911,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): def test_refresh_without_client_auth_error(self): request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) credentials = self.make_credentials() @@ -926,9 +926,9 @@ def test_refresh_without_client_auth_error(self): def test_refresh_impersonation_without_client_auth_error(self): request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.BAD_REQUEST, + impersonation_status=http_client.BAD_REQUEST, impersonation_data=self.IMPERSONATION_ERROR_RESPONSE, ) credentials = self.make_credentials( @@ -956,7 +956,7 @@ def test_refresh_with_client_auth_success(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( client_id=CLIENT_ID, client_secret=CLIENT_SECRET @@ -1006,9 +1006,9 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -1077,9 +1077,9 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -1114,7 +1114,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) def test_apply_without_quota_project_id(self): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -1128,7 +1128,7 @@ def test_apply_without_quota_project_id(self): def test_apply_workforce_without_quota_project_id(self): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT @@ -1153,9 +1153,9 @@ def test_apply_impersonation_without_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -1175,7 +1175,7 @@ def test_apply_impersonation_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials(quota_project_id=self.QUOTA_PROJECT_ID) @@ -1200,9 +1200,9 @@ def test_apply_impersonation_with_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -1225,7 +1225,7 @@ def test_apply_impersonation_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -1248,7 +1248,7 @@ def test_before_request(self): def test_before_request_workforce(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT @@ -1282,9 +1282,9 @@ def test_before_request_impersonation(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) headers = {"other": "header-value"} @@ -1312,7 +1312,7 @@ def test_before_request_impersonation(self): def test_before_request_expired(self, utcnow): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() credentials.token = "token" @@ -1360,9 +1360,9 @@ def test_before_request_impersonation_expired(self, utcnow): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1491,11 +1491,11 @@ def test_get_project_id_cloud_resource_manager_success(self): # Initialize mock request to handle token exchange, service account # impersonation and cloud resource manager request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, - cloud_resource_manager_status=http.client.OK, + cloud_resource_manager_status=http_client.OK, cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1562,9 +1562,9 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): # Initialize mock request to handle token exchange and cloud resource # manager request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - cloud_resource_manager_status=http.client.OK, + cloud_resource_manager_status=http_client.OK, cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, ) credentials = self.make_workforce_pool_credentials( @@ -1611,9 +1611,9 @@ def test_get_project_id_cloud_resource_manager_error(self): # Simulate resource doesn't have sufficient permissions to access # cloud resource manager. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - cloud_resource_manager_status=http.client.UNAUTHORIZED, + cloud_resource_manager_status=http_client.UNAUTHORIZED, ) credentials = self.make_credentials(scopes=self.SCOPES) diff --git a/tests/test_iam.py b/tests/test_iam.py index e9eca583c..bc71225b1 100644 --- a/tests/test_iam.py +++ b/tests/test_iam.py @@ -14,11 +14,11 @@ import base64 import datetime -import http.client import json import mock import pytest +from six.moves import http_client from google.auth import _helpers from google.auth import exceptions @@ -81,7 +81,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http.client.OK, data={"signedBlob": encoded_signature}) + request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) @@ -93,7 +93,7 @@ def test_sign_bytes(self): assert kwargs["headers"]["Content-Type"] == "application/json" def test_sign_bytes_failure(self): - request = make_request(http.client.UNAUTHORIZED) + request = make_request(http_client.UNAUTHORIZED) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) diff --git a/tests/test_identity_pool.py b/tests/test_identity_pool.py index e90e2880d..87e343be4 100644 --- a/tests/test_identity_pool.py +++ b/tests/test_identity_pool.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime -import http.client import json import os -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -92,7 +92,7 @@ def make_mock_response(cls, status, data): @classmethod def make_mock_request( - cls, token_status=http.client.OK, token_data=None, *extra_requests + cls, token_status=http_client.OK, token_data=None, *extra_requests ): responses = [] responses.append(cls.make_mock_response(token_status, token_data)) @@ -218,14 +218,14 @@ def assert_underlying_credentials_refresh( # service account impersonation request. requests = [] if credential_data: - requests.append((http.client.OK, credential_data)) + requests.append((http_client.OK, credential_data)) token_request_index = len(requests) - requests.append((http.client.OK, token_response)) + requests.append((http_client.OK, token_response)) if service_account_impersonation_url: impersonation_request_index = len(requests) - requests.append((http.client.OK, impersonation_response)) + requests.append((http_client.OK, impersonation_response)) request = cls.make_mock_request(*[el for req in requests for el in req]) diff --git a/tests/test_impersonated_credentials.py b/tests/test_impersonated_credentials.py index 3dbb6caa6..bceaebaa5 100644 --- a/tests/test_impersonated_credentials.py +++ b/tests/test_impersonated_credentials.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json import os import mock import pytest +from six.moves import http_client from google.auth import _helpers from google.auth import crypt @@ -79,7 +79,7 @@ def mock_authorizedsession_sign(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"keyId": "1", "signedBlob": "c2lnbmF0dXJl"} - auth_session.return_value = MockResponse(data, http.client.OK) + auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session @@ -89,7 +89,7 @@ def mock_authorizedsession_idtoken(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"token": ID_TOKEN_DATA} - auth_session.return_value = MockResponse(data, http.client.OK) + auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session @@ -141,7 +141,7 @@ def test_default_state(self): def make_request( self, data, - status=http.client.OK, + status=http_client.OK, headers=None, side_effect=None, use_data_bytes=True, @@ -169,7 +169,7 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -194,7 +194,7 @@ def test_refresh_success_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -229,7 +229,7 @@ def test_refresh_source_credentials(self, time_skew): ).isoformat("T") + "Z" response_body = {"accessToken": "token", "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -254,7 +254,7 @@ def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -277,7 +277,7 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials): } request = self.make_request( - data=json.dumps(response_body), status=http.client.UNAUTHORIZED + data=json.dumps(response_body), status=http_client.UNAUTHORIZED ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -294,7 +294,7 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): response_body = {} request = self.make_request( - data=json.dumps(response_body), status=http.client.HTTPException + data=json.dumps(response_body), status=http_client.HTTPException ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -331,7 +331,7 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): token_response_body = {"accessToken": token, "expireTime": expire_time} response = mock.create_autospec(transport.Response, instance=False) - response.status = http.client.OK + response.status = http_client.OK response.data = _helpers.to_bytes(json.dumps(token_response_body)) request = mock.create_autospec(transport.Request, instance=False) @@ -369,7 +369,7 @@ def test_with_quota_project_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -394,7 +394,7 @@ def test_id_token_success( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -423,7 +423,7 @@ def test_id_token_from_credential( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -453,7 +453,7 @@ def test_id_token_with_target_audience( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -494,7 +494,7 @@ def test_id_token_with_include_email( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -523,7 +523,7 @@ def test_id_token_with_quota_project( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index ba7277cdc..c0e1184dc 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -288,9 +288,9 @@ def test_decode_no_key_id(token_factory): def test_decode_unknown_alg(): - headers = json.dumps({"kid": "1", "alg": "fakealg"}) + headers = json.dumps({u"kid": u"1", u"alg": u"fakealg"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) ) with pytest.raises(ValueError) as excinfo: @@ -300,9 +300,9 @@ def test_decode_unknown_alg(): def test_decode_missing_crytography_alg(monkeypatch): monkeypatch.delitem(jwt._ALGORITHM_TO_VERIFIER_CLASS, "ES256") - headers = json.dumps({"kid": "1", "alg": "ES256"}) + headers = json.dumps({u"kid": u"1", u"alg": u"ES256"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) ) with pytest.raises(ValueError) as excinfo: diff --git a/tests/transport/compliance.py b/tests/transport/compliance.py index a5cb678c3..e093d761d 100644 --- a/tests/transport/compliance.py +++ b/tests/transport/compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer +from six.moves import http_client from google.auth import exceptions @@ -43,11 +43,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http.client.OK, headers + return "Basic Content", http_client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http.client.INTERNAL_SERVER_ERROR + return "Error", http_client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -65,7 +65,7 @@ def test_request_basic(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -73,7 +73,7 @@ def test_request_with_timeout_success(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -91,7 +91,7 @@ def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "hello world" assert response.data == b"Basic Content" @@ -99,7 +99,7 @@ def test_request_error(self, server): request = self.make_request() response = request(url=server.url + "/server_error", method="GET") - assert response.status == http.client.INTERNAL_SERVER_ERROR + assert response.status == http_client.INTERNAL_SERVER_ERROR assert response.data == b"Error" def test_connection_error(self): diff --git a/tests/transport/test_requests.py b/tests/transport/test_requests.py index 8b57e0b4e..ed9300d76 100644 --- a/tests/transport/test_requests.py +++ b/tests/transport/test_requests.py @@ -14,7 +14,6 @@ import datetime import functools -import http.client import os import sys @@ -24,6 +23,7 @@ import pytest import requests import requests.adapters +from six.moves import http_client from google.auth import environment_vars from google.auth import exceptions @@ -188,7 +188,7 @@ def test_import_error(self): ) -def make_response(status=http.client.OK, data=None): +def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status response._content = data @@ -249,10 +249,10 @@ def test_request_no_refresh(self): def test_request_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = make_response(status=http.client.OK) + final_response = make_response(status=http_client.OK) # First request will 401, second request will succeed. adapter = AdapterStub( - [make_response(status=http.client.UNAUTHORIZED), final_response] + [make_response(status=http_client.UNAUTHORIZED), final_response] ) authed_session = google.auth.transport.requests.AuthorizedSession( @@ -282,7 +282,7 @@ def test_request_max_allowed_time_timeout_error(self, frozen_time): wraps=TimeTickCredentialsStub(time_tick=tick_one_second) ) adapter = TimeTickAdapterStub( - time_tick=tick_one_second, responses=[make_response(status=http.client.OK)] + time_tick=tick_one_second, responses=[make_response(status=http_client.OK)] ) authed_session = google.auth.transport.requests.AuthorizedSession(credentials) @@ -304,8 +304,8 @@ def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) @@ -328,8 +328,8 @@ def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) @@ -355,8 +355,8 @@ def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) diff --git a/tests/transport/test_urllib3.py b/tests/transport/test_urllib3.py index 995d3dccd..e3848c177 100644 --- a/tests/transport/test_urllib3.py +++ b/tests/transport/test_urllib3.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import os import sys import mock import OpenSSL import pytest +from six.moves import http_client import urllib3 from google.auth import environment_vars @@ -84,7 +84,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): class ResponseStub(object): - def __init__(self, status=http.client.OK, data=None): + def __init__(self, status=http_client.OK, data=None): self.status = status self.data = data @@ -141,12 +141,12 @@ def test_urlopen_no_refresh(self): def test_urlopen_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = ResponseStub(status=http.client.OK) + final_response = ResponseStub(status=http_client.OK) # First request will 401, second request will succeed. - stub = HttpStub([ResponseStub(status=http.client.UNAUTHORIZED), final_response]) + http = HttpStub([ResponseStub(status=http_client.UNAUTHORIZED), final_response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - credentials, http=stub + credentials, http=http ) authed_http = authed_http.urlopen("GET", "http://example.com") @@ -154,7 +154,7 @@ def test_urlopen_refresh(self): assert authed_http == final_response assert credentials.before_request.call_count == 2 assert credentials.refresh.called - assert stub.requests == [ + assert http.requests == [ ("GET", self.TEST_URL, None, {"authorization": "token"}, {}), ("GET", self.TEST_URL, None, {"authorization": "token1"}, {}), ] diff --git a/tests_async/oauth2/test__client_async.py b/tests_async/oauth2/test__client_async.py index 66338d56c..6e48c4590 100644 --- a/tests_async/oauth2/test__client_async.py +++ b/tests_async/oauth2/test__client_async.py @@ -13,12 +13,13 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import _jwt_async as jwt @@ -28,7 +29,7 @@ from tests.oauth2 import test__client as test_client -def make_request(response_data, status=http.client.OK): +def make_request(response_data, status=http_client.OK): response = mock.AsyncMock(spec=["transport.Response"]) response.status = status data = json.dumps(response_data).encode("utf-8") @@ -92,7 +93,7 @@ async def test__token_endpoint_request_json(): @pytest.mark.asyncio async def test__token_endpoint_request_error(): - request = make_request({}, status=http.client.BAD_REQUEST) + request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): await _client._token_endpoint_request(request, "http://example.com", {}) @@ -101,7 +102,7 @@ async def test__token_endpoint_request_error(): @pytest.mark.asyncio async def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -110,7 +111,7 @@ async def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http.client.BAD_REQUEST + {"error": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -123,7 +124,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in params.items(): + for key, value in six.iteritems(params): assert request_params[key][0] == value diff --git a/tests_async/transport/async_compliance.py b/tests_async/transport/async_compliance.py index 385a9236a..9c4b173c2 100644 --- a/tests_async/transport/async_compliance.py +++ b/tests_async/transport/async_compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer +from six.moves import http_client from google.auth import exceptions from tests.transport import compliance @@ -41,11 +41,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http.client.OK, headers + return "Basic Content", http_client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http.client.INTERNAL_SERVER_ERROR + return "Error", http_client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -63,7 +63,7 @@ def wait(): async def test_request_basic(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -75,7 +75,7 @@ async def test_request_basic(self, server): async def test_request_basic_with_http(self, server): request = self.make_with_parameter_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -88,7 +88,7 @@ async def test_request_with_timeout_success(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" data = await response.data.read(13) @@ -110,7 +110,7 @@ async def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "hello world" data = await response.data.read(13) @@ -121,7 +121,7 @@ async def test_request_error(self, server): request = self.make_request() response = await request(url=server.url + "/server_error", method="GET") - assert response.status == http.client.INTERNAL_SERVER_ERROR + assert response.status == http_client.INTERNAL_SERVER_ERROR data = await response.data.read(5) assert data == b"Error"