diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index cb89b2e32..eecb84c21 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:ec49167c606648a063d1222220b48119c912562849a0528f35bfb592a9f72737 + digest: sha256:ae600f36b6bc972b368367b6f83a1d91ec2c82a4a116b383d67d547c56fe6de3 diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 967bc917e..638176950 100755 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -26,7 +26,7 @@ python3 -m pip install --upgrade twine wheel setuptools export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secret_manager/google-cloud-pypi-token") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") cd github/google-auth-library-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg index 07334fd50..9ec8d102f 100644 --- a/.kokoro/release/common.cfg +++ b/.kokoro/release/common.cfg @@ -23,8 +23,18 @@ env_vars: { value: "github/google-auth-library-python/.kokoro/release.sh" } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + # Tokens needed to report release status back to GitHub env_vars: { key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem,google-cloud-pypi-token" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } diff --git a/.repo-metadata.json b/.repo-metadata.json index 9d799aa38..f6a3d96a0 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -1,11 +1,11 @@ { - "name": "google-auth", - "name_pretty": "Google Auth Python Library", - "client_documentation": "https://googleapis.dev/python/google-auth/latest", - "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", - "release_level": "ga", - "language": "python", - "library_type": "AUTH", - "repo": "googleapis/google-auth-library-python", - "distribution_name": "google-auth" + "name": "google-auth", + "name_pretty": "Google Auth Python Library", + "client_documentation": "https://googleapis.dev/python/google-auth/latest", + "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", + "release_level": "stable", + "language": "python", + "library_type": "AUTH", + "repo": "googleapis/google-auth-library-python", + "distribution_name": "google-auth" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 73440f7e3..43376ec7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.4.0](https://github.com/googleapis/google-auth-library-python/compare/v2.3.3...v2.4.0) (2022-01-20) + + +### Features + +* add 'py.typed' declaration ([#919](https://github.com/googleapis/google-auth-library-python/issues/919)) ([c993504](https://github.com/googleapis/google-auth-library-python/commit/c99350455d0f7fd3aab950ac47b43000c73dd312)) +* add api key support ([#826](https://github.com/googleapis/google-auth-library-python/issues/826)) ([3b15092](https://github.com/googleapis/google-auth-library-python/commit/3b15092b3461278400e4683060f64a96d50587c4)) + + +### Bug Fixes + +* **deps:** allow cachetools 5.0 for python 3.7+ ([#937](https://github.com/googleapis/google-auth-library-python/issues/937)) ([1eae37d](https://github.com/googleapis/google-auth-library-python/commit/1eae37db7f6fceb32d6ef0041962ce1755d2116c)) +* fix the message format for metadata server exception ([#916](https://github.com/googleapis/google-auth-library-python/issues/916)) ([e756f08](https://github.com/googleapis/google-auth-library-python/commit/e756f08dc78616040ab8fbd7db20903137ccf0c7)) + + +### Documentation + +* fix intersphinx link for 'requests-oauthlib' ([#921](https://github.com/googleapis/google-auth-library-python/issues/921)) ([967be4f](https://github.com/googleapis/google-auth-library-python/commit/967be4f4e2a43ba7e240d7acb01b6b992d40e6ec)) +* note ValueError in `verify_oauth2_token` ([#928](https://github.com/googleapis/google-auth-library-python/issues/928)) ([82bc5f0](https://github.com/googleapis/google-auth-library-python/commit/82bc5f08111de78a2b475b0310d3f35470680dbe)) + ### [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) diff --git a/docs/conf.py b/docs/conf.py index 58e5b9a99..652d808bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -363,7 +363,10 @@ "python": ("https://docs.python.org/3.5", None), "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), "requests": ("https://requests.kennethreitz.org/en/master/", None), - "requests-oauthlib": ("https://requests-oauthlib.readthedocs.io/en/stable/", None), + "requests-oauthlib": ( + "https://requests-oauthlib.readthedocs.io/en/v1.3.0-docs/", + None, + ), } # Autodoc config diff --git a/google/__init__.py b/google/__init__.py index 0d0a4c3ab..70a7bd995 100644 --- a/google/__init__.py +++ b/google/__init__.py @@ -21,4 +21,4 @@ except ImportError: import pkgutil - __path__ = pkgutil.extend_path(__path__, __name__) + __path__ = pkgutil.extend_path(__path__, __name__) # type: ignore diff --git a/google/auth/_default.py b/google/auth/_default.py index 4ae7c8c06..54d656164 100644 --- a/google/auth/_default.py +++ b/google/auth/_default.py @@ -353,6 +353,24 @@ def _get_external_account_credentials( return credentials, credentials.get_project_id(request=request) +def _get_api_key_credentials(quota_project_id=None): + """Gets API key credentials and project ID.""" + from google.auth import api_key + + api_key_value = os.environ.get(environment_vars.API_KEY) + if api_key_value: + return api_key.Credentials(api_key_value), quota_project_id + else: + return None, None + + +def get_api_key_credentials(api_key_value): + """Gets API key credentials using the given api key value.""" + from google.auth import api_key + + return api_key.Credentials(api_key_value) + + def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. @@ -361,7 +379,14 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non This function acquires credentials from the environment in the following order: - 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variables are set, throw an exception. + + If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. + The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or + ``GCLOUD_PROJECT`` environment variables. + + If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -409,6 +434,7 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -444,16 +470,25 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non invalid. """ from google.auth.credentials import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject explicit_project_id = os.environ.get( environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) + if os.environ.get(environment_vars.API_KEY) and os.environ.get( + environment_vars.CREDENTIALS + ): + raise exceptions.DefaultCredentialsError( + "Environment variables GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + checkers = ( # Avoid passing scopes here to prevent passing scopes to user credentials. # with_scopes_if_required() below will ensure scopes/default scopes are # safely set on the returned credentials since requires_scopes will # guard against setting scopes on user credentials. + lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, @@ -477,7 +512,9 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non request = google.auth.transport.requests.Request() project_id = credentials.get_project_id(request=request) - if quota_project_id: + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): credentials = credentials.with_quota_project(quota_project_id) effective_project_id = explicit_project_id or project_id diff --git a/google/auth/_default_async.py b/google/auth/_default_async.py index fb277c54e..a6f7d7777 100644 --- a/google/auth/_default_async.py +++ b/google/auth/_default_async.py @@ -161,6 +161,24 @@ def _get_gae_credentials(): return _default._get_gae_credentials() +def _get_api_key_credentials(quota_project_id=None): + """Gets API key credentials and project ID.""" + from google.auth import api_key + + api_key_value = os.environ.get(environment_vars.API_KEY) + if api_key_value: + return api_key.Credentials(api_key_value), quota_project_id + else: + return None, None + + +def get_api_key_credentials(api_key_value): + """Gets API key credentials using the given api key value.""" + from google.auth import api_key + + return api_key.Credentials(api_key_value) + + def _get_gce_credentials(request=None): """Gets credentials and project ID from the GCE Metadata Service.""" # Ping requires a transport, but we want application default credentials @@ -182,7 +200,14 @@ def default_async(scopes=None, request=None, quota_project_id=None): This function acquires credentials from the environment in the following order: - 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variables are set, throw an exception. + + If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. + The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or + ``GCLOUD_PROJECT`` environment variables. + + If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -221,6 +246,7 @@ def default_async(scopes=None, request=None, quota_project_id=None): .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -250,12 +276,21 @@ def default_async(scopes=None, request=None, quota_project_id=None): invalid. """ from google.auth._credentials_async import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject explicit_project_id = os.environ.get( environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) + if os.environ.get(environment_vars.API_KEY) and os.environ.get( + environment_vars.CREDENTIALS + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + checkers = ( + lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, @@ -265,9 +300,11 @@ def default_async(scopes=None, request=None, quota_project_id=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = with_scopes_if_required( - credentials, scopes - ).with_quota_project(quota_project_id) + credentials = with_scopes_if_required(credentials, scopes) + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): + credentials = credentials.with_quota_project(quota_project_id) effective_project_id = explicit_project_id or project_id if not effective_project_id: _default._LOGGER.warning( diff --git a/google/auth/_jwt_async.py b/google/auth/_jwt_async.py index 49e3026e5..3a1abc5b8 100644 --- a/google/auth/_jwt_async.py +++ b/google/auth/_jwt_async.py @@ -43,7 +43,7 @@ change in minor releases. """ -import google.auth +from google.auth import _credentials_async from google.auth import jwt @@ -91,9 +91,7 @@ def decode(token, certs=None, verify=True, audience=None): class Credentials( - jwt.Credentials, - google.auth._credentials_async.Signing, - google.auth._credentials_async.Credentials, + jwt.Credentials, _credentials_async.Signing, _credentials_async.Credentials ): """Credentials that use a JWT as the bearer token. @@ -146,9 +144,7 @@ class Credentials( class OnDemandCredentials( - jwt.OnDemandCredentials, - google.auth._credentials_async.Signing, - google.auth._credentials_async.Credentials, + jwt.OnDemandCredentials, _credentials_async.Signing, _credentials_async.Credentials ): """On-demand JWT credentials. diff --git a/google/auth/_oauth2client.py b/google/auth/_oauth2client.py index 95a9876f3..a86ba8dd6 100644 --- a/google/auth/_oauth2client.py +++ b/google/auth/_oauth2client.py @@ -30,14 +30,14 @@ import google.oauth2.service_account try: - import oauth2client.client - import oauth2client.contrib.gce - import oauth2client.service_account + import oauth2client.client # type: ignore + import oauth2client.contrib.gce # type: ignore + import oauth2client.service_account # type: ignore except ImportError as caught_exc: six.raise_from(ImportError("oauth2client is not installed."), caught_exc) try: - import oauth2client.contrib.appengine # pytype: disable=import-error + import oauth2client.contrib.appengine # type: ignore _HAS_APPENGINE = True except ImportError: diff --git a/google/auth/api_key.py b/google/auth/api_key.py new file mode 100644 index 000000000..7cdef429c --- /dev/null +++ b/google/auth/api_key.py @@ -0,0 +1,83 @@ +# Copyright 2021 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. + +"""Google API key support. + +This module provides authentication using the `API key`_. + + +.. _API key: + https://cloud.google.com/docs/authentication/api-keys/ +""" + +from google.auth import _helpers +from google.auth import credentials + + +class Credentials(credentials.Credentials): + """API key credentials. + + These credentials use API key to provide authorization to applications. + """ + + def __init__(self, token): + """ + Args: + token (str): API key string + + Raises: + ValueError: If the provided API key is not a non-empty string. + """ + if not token: + raise ValueError("Token must be a non-empty API key string") + super(Credentials, self).__init__() + self.token = token + + @property + def expired(self): + return False + + @property + def valid(self): + return True + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + return + + def apply(self, headers, token=None): + """Apply the API key token to the x-goog-api-key header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["x-goog-api-key"] = token or self.token + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the x-goog-api-key header. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + self.apply(headers) diff --git a/google/auth/app_engine.py b/google/auth/app_engine.py index 81aef73b4..1460a7d1a 100644 --- a/google/auth/app_engine.py +++ b/google/auth/app_engine.py @@ -30,9 +30,9 @@ # pytype: disable=import-error try: - from google.appengine.api import app_identity + from google.appengine.api import app_identity # type: ignore except ImportError: - app_identity = None + app_identity = None # type: ignore # pytype: enable=import-error @@ -168,12 +168,12 @@ def with_quota_project(self, quota_project_id): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self.service_account_email - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer diff --git a/google/auth/aws.py b/google/auth/aws.py index 925b1ddfb..2fd96d058 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -45,14 +45,9 @@ import posixpath import re -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 six.moves.urllib.parse import urljoin from google.auth import _helpers from google.auth import environment_vars diff --git a/google/auth/compute_engine/_metadata.py b/google/auth/compute_engine/_metadata.py index 9db7bea92..d57c22a15 100644 --- a/google/auth/compute_engine/_metadata.py +++ b/google/auth/compute_engine/_metadata.py @@ -161,7 +161,7 @@ def get( retries += 1 else: raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine" + "Failed to retrieve {} from the Google Compute Engine " "metadata service. Compute Engine Metadata server unavailable".format(url) ) @@ -172,7 +172,7 @@ def get( return json.loads(content) except ValueError as caught_exc: new_exc = exceptions.TransportError( - "Received invalid JSON from the Google Compute Engine" + "Received invalid JSON from the Google Compute Engine " "metadata service: {:.20}".format(content) ) six.raise_from(new_exc, caught_exc) @@ -180,7 +180,7 @@ def get( return content else: raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine" + "Failed to retrieve {} from the Google Compute Engine " "metadata service. Status: {} Response:\n{}".format( url, response.status, response.data ), diff --git a/google/auth/compute_engine/credentials.py b/google/auth/compute_engine/credentials.py index b39ac50ae..59b48dae6 100644 --- a/google/auth/compute_engine/credentials.py +++ b/google/auth/compute_engine/credentials.py @@ -379,7 +379,7 @@ def refresh(self, request): self.token = access_token self.expiry = expiry - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer diff --git a/google/auth/crypt/__init__.py b/google/auth/crypt/__init__.py index 15ac95068..9f91f0d0b 100644 --- a/google/auth/crypt/__init__.py +++ b/google/auth/crypt/__init__.py @@ -45,7 +45,7 @@ try: from google.auth.crypt import es256 except ImportError: # pragma: NO COVER - es256 = None + es256 = None # type: ignore if es256 is not None: # pragma: NO COVER __all__ = [ diff --git a/google/auth/crypt/_cryptography_rsa.py b/google/auth/crypt/_cryptography_rsa.py index 916c9d80a..4f2d61166 100644 --- a/google/auth/crypt/_cryptography_rsa.py +++ b/google/auth/crypt/_cryptography_rsa.py @@ -101,7 +101,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/google/auth/crypt/_python_rsa.py b/google/auth/crypt/_python_rsa.py index ec30dd09a..797a2592b 100644 --- a/google/auth/crypt/_python_rsa.py +++ b/google/auth/crypt/_python_rsa.py @@ -21,11 +21,11 @@ from __future__ import absolute_import -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 +from pyasn1.codec.der import decoder # type: ignore +from pyasn1_modules import pem # type: ignore +from pyasn1_modules.rfc2459 import Certificate # type: ignore +from pyasn1_modules.rfc5208 import PrivateKeyInfo # type: ignore +import rsa # type: ignore import six from google.auth import _helpers @@ -125,7 +125,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/google/auth/crypt/es256.py b/google/auth/crypt/es256.py index 42823a7a5..7920cc7ff 100644 --- a/google/auth/crypt/es256.py +++ b/google/auth/crypt/es256.py @@ -15,7 +15,7 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ -from cryptography import utils +from cryptography import utils # type: ignore import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes @@ -117,7 +117,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/google/auth/crypt/rsa.py b/google/auth/crypt/rsa.py index 8b2d64c10..ed842d1eb 100644 --- a/google/auth/crypt/rsa.py +++ b/google/auth/crypt/rsa.py @@ -26,5 +26,5 @@ # unavailable. from google.auth.crypt import _python_rsa - RSASigner = _python_rsa.RSASigner - RSAVerifier = _python_rsa.RSAVerifier + RSASigner = _python_rsa.RSASigner # type: ignore + RSAVerifier = _python_rsa.RSAVerifier # type: ignore diff --git a/google/auth/environment_vars.py b/google/auth/environment_vars.py index c076dc59d..d872c95f9 100644 --- a/google/auth/environment_vars.py +++ b/google/auth/environment_vars.py @@ -33,6 +33,9 @@ """Environment variable defining the location of Google application default credentials.""" +API_KEY = "GOOGLE_API_KEY" +"""Environment variable defining the API key value.""" + # The environment variable name which can replace ~/.config if set. CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG" """Environment variable defines the location of Google Cloud SDK's config diff --git a/google/auth/jwt.py b/google/auth/jwt.py index d56559510..9d7b76e73 100644 --- a/google/auth/jwt.py +++ b/google/auth/jwt.py @@ -44,7 +44,7 @@ from collections.abc import Mapping # Python 2.7 compatibility except ImportError: # pragma: NO COVER - from collections import Mapping + from collections import Mapping # type: ignore import copy import datetime import json @@ -62,7 +62,7 @@ try: from google.auth.crypt import es256 except ImportError: # pragma: NO COVER - es256 = None + es256 = None # type: ignore _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_MAX_CACHE_SIZE = 10 @@ -70,7 +70,7 @@ _CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"]) if es256 is not None: # pragma: NO COVER - _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier + _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier # type: ignore def encode(signer, payload, header=None, key_id=None): @@ -557,12 +557,12 @@ def refresh(self, request): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer_email(self): return self._issuer - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer @@ -846,12 +846,12 @@ def before_request(self, request, method, url, headers): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer_email(self): return self._issuer - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer diff --git a/google/auth/py.typed b/google/auth/py.typed new file mode 100644 index 000000000..aa7b68923 --- /dev/null +++ b/google/auth/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-auth package uses inline types. diff --git a/google/auth/transport/_aiohttp_requests.py b/google/auth/transport/_aiohttp_requests.py index ab7dfef67..8a20e7abe 100644 --- a/google/auth/transport/_aiohttp_requests.py +++ b/google/auth/transport/_aiohttp_requests.py @@ -23,9 +23,9 @@ import asyncio import functools -import aiohttp +import aiohttp # type: ignore import six -import urllib3 +import urllib3 # type: ignore from google.auth import exceptions from google.auth import transport diff --git a/google/auth/transport/grpc.py b/google/auth/transport/grpc.py index c47cb3dda..87fa5042f 100644 --- a/google/auth/transport/grpc.py +++ b/google/auth/transport/grpc.py @@ -27,7 +27,7 @@ from google.oauth2 import service_account try: - import grpc + import grpc # type: ignore except ImportError as caught_exc: # pragma: NO COVER six.raise_from( ImportError( diff --git a/google/auth/transport/requests.py b/google/auth/transport/requests.py index 817176bef..46ca669ff 100644 --- a/google/auth/transport/requests.py +++ b/google/auth/transport/requests.py @@ -36,10 +36,10 @@ ) import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports -from requests.packages.urllib3.util.ssl_ import ( +import six # pylint: disable=ungrouped-imports +from 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 @@ -205,7 +205,7 @@ class _MutualTlsAdapter(requests.adapters.HTTPAdapter): def __init__(self, cert, key): import certifi from OpenSSL import crypto - import urllib3.contrib.pyopenssl + import urllib3.contrib.pyopenssl # type: ignore urllib3.contrib.pyopenssl.inject_into_urllib3() diff --git a/google/auth/transport/urllib3.py b/google/auth/transport/urllib3.py index 6a2504d97..ad67327a4 100644 --- a/google/auth/transport/urllib3.py +++ b/google/auth/transport/urllib3.py @@ -29,13 +29,14 @@ try: import certifi except ImportError: # pragma: NO COVER - certifi = None + certifi = None # type: ignore + +import six try: - import urllib3 + import urllib3 # type: ignore + import urllib3.exceptions # type: ignore except ImportError as caught_exc: # pragma: NO COVER - import six - six.raise_from( ImportError( "The urllib3 library is not installed, please install the " @@ -43,8 +44,6 @@ ), caught_exc, ) -import six -import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -169,7 +168,7 @@ def _make_mutual_tls_http(cert, key): """ import certifi from OpenSSL import crypto - import urllib3.contrib.pyopenssl + import urllib3.contrib.pyopenssl # type: ignore urllib3.contrib.pyopenssl.inject_into_urllib3() ctx = urllib3.util.ssl_.create_urllib3_context() diff --git a/google/auth/version.py b/google/auth/version.py index ad9a0c7a4..562d21480 100644 --- a/google/auth/version.py +++ b/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.3.3" +__version__ = "2.4.0" diff --git a/google/oauth2/challenges.py b/google/oauth2/challenges.py index 95e76cb32..bb523e6ca 100644 --- a/google/oauth2/challenges.py +++ b/google/oauth2/challenges.py @@ -114,9 +114,9 @@ def is_locally_eligible(self): @_helpers.copy_docstring(ReauthChallenge) def obtain_challenge_input(self, metadata): try: - import pyu2f.convenience.authenticator - import pyu2f.errors - import pyu2f.model + import pyu2f.convenience.authenticator # type: ignore + import pyu2f.errors # type: ignore + import pyu2f.model # type: ignore except ImportError: raise exceptions.ReauthFailError( "pyu2f dependency is required to use Security key reauth feature. " diff --git a/google/oauth2/id_token.py b/google/oauth2/id_token.py index 74899ae55..48f5b0a59 100644 --- a/google/oauth2/id_token.py +++ b/google/oauth2/id_token.py @@ -158,6 +158,7 @@ def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds= Raises: exceptions.GoogleAuthError: If the issuer is invalid. + ValueError: If token verification fails """ idinfo = verify_token( id_token, diff --git a/google/oauth2/py.typed b/google/oauth2/py.typed new file mode 100644 index 000000000..d82ed62c2 --- /dev/null +++ b/google/oauth2/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-oauth2 package uses inline types. diff --git a/google/oauth2/service_account.py b/google/oauth2/service_account.py index ecaac038c..5c4f340fa 100644 --- a/google/oauth2/service_account.py +++ b/google/oauth2/service_account.py @@ -444,12 +444,12 @@ def _create_self_signed_jwt(self, audience): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self._service_account_email @@ -676,12 +676,12 @@ def service_account_email(self): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self._service_account_email diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..4505b4854 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +python_version = 3.6 +namespace_packages = True diff --git a/noxfile.py b/noxfile.py index efb367e83..1032be641 100644 --- a/noxfile.py +++ b/noxfile.py @@ -62,6 +62,24 @@ def blacken(session): session.run("black", *BLACK_PATHS) +@nox.session(python="3.6") +def mypy(session): + """Verify type hints are mypy compatible.""" + session.install("-e", ".") + session.install( + "mypy", + "types-cachetools", + "types-certifi", + "types-freezegun", + "types-pyOpenSSL", + "types-requests", + "types-setuptools", + "types-six", + "types-mock", + ) + session.run("mypy", "google/", "tests/", "tests_async/") + + @nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) def unit(session): constraints_path = str( @@ -115,23 +133,6 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.7") -def docgen(session): - session.env["SPHINX_APIDOC_OPTIONS"] = "members,inherited-members,show-inheritance" - session.install("-r", "testing/requirements.txt") - session.install("sphinx") - session.install("-e", ".") - session.run("rm", "-r", "docs/reference") - session.run( - "sphinx-apidoc", - "--output-dir", - "docs/reference", - "--separate", - "--module-first", - "google", - ) - - @nox.session(python="3.8") def docs(session): """Build the docs for this library.""" diff --git a/setup.py b/setup.py index 301e99643..22f627b99 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ DEPENDENCIES = ( - "cachetools>=2.0.0,<5.0", + "cachetools>=2.0.0,<6.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 @@ -28,7 +28,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", ) diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 5f20b1e4c..550f89847 100644 Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ diff --git a/tests/compute_engine/test__metadata.py b/tests/compute_engine/test__metadata.py index 852822dc0..568812056 100644 --- a/tests/compute_engine/test__metadata.py +++ b/tests/compute_engine/test__metadata.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import reload_module diff --git a/tests/compute_engine/test_credentials.py b/tests/compute_engine/test_credentials.py index 81cc6db31..ff01720c4 100644 --- a/tests/compute_engine/test_credentials.py +++ b/tests/compute_engine/test_credentials.py @@ -15,8 +15,8 @@ import datetime import mock -import pytest -import responses +import pytest # type: ignore +import responses # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/tests/conftest.py b/tests/conftest.py index cf8a0f9e5..8080ec3fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ import sys import mock -import pytest +import pytest # type: ignore def pytest_configure(): diff --git a/tests/crypt/test__cryptography_rsa.py b/tests/crypt/test__cryptography_rsa.py index dbf07c780..99d8fc37c 100644 --- a/tests/crypt/test__cryptography_rsa.py +++ b/tests/crypt/test__cryptography_rsa.py @@ -16,7 +16,7 @@ import os from cryptography.hazmat.primitives.asymmetric import rsa -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth.crypt import _cryptography_rsa @@ -55,7 +55,7 @@ # The service account JSON file can be generated from the Google Cloud Console. SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/tests/crypt/test__python_rsa.py b/tests/crypt/test__python_rsa.py index 886ee55a2..9d832f044 100644 --- a/tests/crypt/test__python_rsa.py +++ b/tests/crypt/test__python_rsa.py @@ -16,9 +16,9 @@ import os import mock -from pyasn1_modules import pem -import pytest -import rsa +from pyasn1_modules import pem # type: ignore +import pytest # type: ignore +import rsa # type: ignore import six from google.auth import _helpers @@ -58,7 +58,7 @@ # The service account JSON file can be generated from the Google Cloud Console. SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/tests/crypt/test_es256.py b/tests/crypt/test_es256.py index 5bb9050cd..33465ce6d 100644 --- a/tests/crypt/test_es256.py +++ b/tests/crypt/test_es256.py @@ -17,7 +17,7 @@ import os from cryptography.hazmat.primitives.asymmetric import ec -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth.crypt import base @@ -45,7 +45,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "es256_service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/tests/oauth2/test__client.py b/tests/oauth2/test__client.py index 54686df59..5485bed84 100644 --- a/tests/oauth2/test__client.py +++ b/tests/oauth2/test__client.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore import six from six.moves import http_client from six.moves import urllib diff --git a/tests/oauth2/test_challenges.py b/tests/oauth2/test_challenges.py index 412895ada..9e35d88af 100644 --- a/tests/oauth2/test_challenges.py +++ b/tests/oauth2/test_challenges.py @@ -18,8 +18,8 @@ import sys import mock -import pytest -import pyu2f +import pytest # type: ignore +import pyu2f # type: ignore from google.auth import exceptions from google.oauth2 import challenges diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py index 243f97de8..e5f71def0 100644 --- a/tests/oauth2/test_credentials.py +++ b/tests/oauth2/test_credentials.py @@ -19,7 +19,7 @@ import sys import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/tests/oauth2/test_id_token.py b/tests/oauth2/test_id_token.py index ccfaaaf8c..40204f9d4 100644 --- a/tests/oauth2/test_id_token.py +++ b/tests/oauth2/test_id_token.py @@ -16,7 +16,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/tests/oauth2/test_reauth.py b/tests/oauth2/test_reauth.py index 58d649d83..ae64be009 100644 --- a/tests/oauth2/test_reauth.py +++ b/tests/oauth2/test_reauth.py @@ -15,7 +15,7 @@ import copy import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import reauth diff --git a/tests/oauth2/test_service_account.py b/tests/oauth2/test_service_account.py index 531fc4c9e..1d1438485 100644 --- a/tests/oauth2/test_service_account.py +++ b/tests/oauth2/test_service_account.py @@ -38,7 +38,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") diff --git a/tests/oauth2/test_sts.py b/tests/oauth2/test_sts.py index e8e008df5..f61a1d338 100644 --- a/tests/oauth2/test_sts.py +++ b/tests/oauth2/test_sts.py @@ -15,7 +15,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/tests/oauth2/test_utils.py b/tests/oauth2/test_utils.py index 6de9ff533..543a693a9 100644 --- a/tests/oauth2/test_utils.py +++ b/tests/oauth2/test_utils.py @@ -14,7 +14,7 @@ import json -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import utils diff --git a/tests/test__cloud_sdk.py b/tests/test__cloud_sdk.py index 31cb6c22c..c05c44320 100644 --- a/tests/test__cloud_sdk.py +++ b/tests/test__cloud_sdk.py @@ -18,7 +18,7 @@ import subprocess import mock -import pytest +import pytest # type: ignore from google.auth import _cloud_sdk from google.auth import environment_vars @@ -28,12 +28,12 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") -with io.open(AUTHORIZED_USER_FILE) as fh: +with io.open(AUTHORIZED_USER_FILE, "rb") as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") -with io.open(SERVICE_ACCOUNT_FILE) as fh: +with io.open(SERVICE_ACCOUNT_FILE, "rb") as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) with io.open(os.path.join(DATA_DIR, "cloud_sdk_config.json"), "rb") as fh: @@ -63,7 +63,7 @@ def test_get_project_id(data, expected_project_id): @mock.patch( "subprocess.check_output", autospec=True, - side_effect=subprocess.CalledProcessError(-1, None), + side_effect=subprocess.CalledProcessError(-1, "testing"), ) def test_get_project_id_call_error(check_output): project_id = _cloud_sdk.get_project_id() diff --git a/tests/test__default.py b/tests/test__default.py index 1ce03cfe8..67795ef02 100644 --- a/tests/test__default.py +++ b/tests/test__default.py @@ -16,9 +16,10 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _default +from google.auth import api_key from google.auth import app_engine from google.auth import aws from google.auth import compute_engine @@ -994,3 +995,46 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE credentials, project_id = _default.default(quota_project_id="project-foo") + + +def test__get_api_key_credentials_no_env_var(): + cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") + assert cred is None + assert project_id is None + + +def test__get_api_key_credentials_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default._get_api_key_credentials( + quota_project_id="project-foo" + ) + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id == "project-foo" + + +def test_exception_with_api_key_and_adc_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + with mock.patch.dict( + os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.default() + + assert excinfo.match( + r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + + +def test_default_api_key_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default.default() + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id is None + + +def test_get_api_key_credentials(): + cred = _default.get_api_key_credentials("api-key") + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" diff --git a/tests/test__helpers.py b/tests/test__helpers.py index 0c0bad2d2..8c71f3e51 100644 --- a/tests/test__helpers.py +++ b/tests/test__helpers.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from six.moves import urllib from google.auth import _helpers diff --git a/tests/test__oauth2client.py b/tests/test__oauth2client.py index 6b1112b50..f1fac511b 100644 --- a/tests/test__oauth2client.py +++ b/tests/test__oauth2client.py @@ -17,10 +17,10 @@ import sys import mock -import oauth2client.client -import oauth2client.contrib.gce -import oauth2client.service_account -import pytest +import oauth2client.client # type: ignore +import oauth2client.contrib.gce # type: ignore +import oauth2client.service_account # type: ignore +import pytest # type: ignore from six.moves import reload_module from google.auth import _oauth2client @@ -109,7 +109,7 @@ def test__convert_appengine_app_assertion_credentials( app_identity, mock_oauth2client_gae_imports ): - import oauth2client.contrib.appengine + import oauth2client.contrib.appengine # type: ignore service_account_id = "service_account_id" old_credentials = oauth2client.contrib.appengine.AppAssertionCredentials( diff --git a/tests/test__service_account_info.py b/tests/test__service_account_info.py index 13b2f85a2..d5529bcce 100644 --- a/tests/test__service_account_info.py +++ b/tests/test__service_account_info.py @@ -15,7 +15,7 @@ import json import os -import pytest +import pytest # type: ignore import six from google.auth import _service_account_info diff --git a/tests/test_api_key.py b/tests/test_api_key.py new file mode 100644 index 000000000..9721731be --- /dev/null +++ b/tests/test_api_key.py @@ -0,0 +1,45 @@ +# Copyright 2021 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 pytest # type: ignore + +from google.auth import api_key + + +def test_credentials_constructor(): + with pytest.raises(ValueError) as excinfo: + api_key.Credentials("") + + assert excinfo.match(r"Token must be a non-empty API key string") + + +def test_expired_and_valid(): + credentials = api_key.Credentials("api-key") + + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + credentials.refresh(None) + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + +def test_before_request(): + credentials = api_key.Credentials("api-key") + headers = {} + + credentials.before_request(None, "http://example.com", "GET", headers) + assert headers["x-goog-api-key"] == "api-key" diff --git a/tests/test_app_engine.py b/tests/test_app_engine.py index 6a788b9e9..ca085bd69 100644 --- a/tests/test_app_engine.py +++ b/tests/test_app_engine.py @@ -15,7 +15,7 @@ import datetime import mock -import pytest +import pytest # type: ignore from google.auth import app_engine diff --git a/tests/test_aws.py b/tests/test_aws.py index 9ca08d5b2..d37131afb 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/tests/test_credentials.py b/tests/test_credentials.py index 2de638840..da074143a 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import credentials diff --git a/tests/test_downscoped.py b/tests/test_downscoped.py index 9ca95f5aa..7d0768a18 100644 --- a/tests/test_downscoped.py +++ b/tests/test_downscoped.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/tests/test_external_account.py b/tests/test_external_account.py index 3c34f998c..3897aef15 100644 --- a/tests/test_external_account.py +++ b/tests/test_external_account.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/tests/test_iam.py b/tests/test_iam.py index bc71225b1..ae482765b 100644 --- a/tests/test_iam.py +++ b/tests/test_iam.py @@ -17,7 +17,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from google.auth import _helpers diff --git a/tests/test_identity_pool.py b/tests/test_identity_pool.py index 87e343be4..664c317d0 100644 --- a/tests/test_identity_pool.py +++ b/tests/test_identity_pool.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/tests/test_impersonated_credentials.py b/tests/test_impersonated_credentials.py index bc404e36b..58d159a59 100644 --- a/tests/test_impersonated_credentials.py +++ b/tests/test_impersonated_credentials.py @@ -16,8 +16,11 @@ import json import os +# Because Python 2.7 +# from typing import List + import mock -import pytest +import pytest # type: ignore from six.moves import http_client from google.auth import _helpers @@ -46,7 +49,7 @@ ) ID_TOKEN_EXPIRY = 1564475051 -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") @@ -98,7 +101,9 @@ class TestImpersonatedCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" TARGET_PRINCIPAL = "impersonated@project.iam.gserviceaccount.com" TARGET_SCOPES = ["https://www.googleapis.com/auth/devstorage.read_only"] - DELEGATES = [] + # DELEGATES: List[str] = [] + # Because Python 2.7: + DELEGATES = [] # type: ignore LIFETIME = 3600 SOURCE_CREDENTIALS = service_account.Credentials( SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI diff --git a/tests/test_jwt.py b/tests/test_jwt.py index c0e1184dc..bc01ebfc7 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -18,7 +18,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import crypt @@ -45,7 +45,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/tests/transport/compliance.py b/tests/transport/compliance.py index e093d761d..faf39b9ba 100644 --- a/tests/transport/compliance.py +++ b/tests/transport/compliance.py @@ -14,9 +14,9 @@ import time -import flask -import pytest -from pytest_localserver.http import WSGIServer +import flask # type: ignore +import pytest # type: ignore +from pytest_localserver.http import WSGIServer # type: ignore from six.moves import http_client from google.auth import exceptions diff --git a/tests/transport/test__http_client.py b/tests/transport/test__http_client.py index c176cb2f4..202276323 100644 --- a/tests/transport/test__http_client.py +++ b/tests/transport/test__http_client.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import pytest # type: ignore from google.auth import exceptions import google.auth.transport._http_client diff --git a/tests/transport/test__mtls_helper.py b/tests/transport/test__mtls_helper.py index 3b6349a1d..1621a0530 100644 --- a/tests/transport/test__mtls_helper.py +++ b/tests/transport/test__mtls_helper.py @@ -17,15 +17,13 @@ import mock from OpenSSL import crypto -import pytest +import pytest # type: ignore from google.auth import exceptions from google.auth.transport import _mtls_helper CONTEXT_AWARE_METADATA = {"cert_provider_command": ["some command"]} -CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND = {} - ENCRYPTED_EC_PRIVATE_KEY = b"""-----BEGIN ENCRYPTED PRIVATE KEY----- MIHkME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAgl2/yVgs1h3QICCAAw DAYIKoZIhvcNAgkFADAVBgkrBgEEAZdVAQIECJk2GRrvxOaJBIGQXIBnMU4wmciT diff --git a/tests/transport/test_grpc.py b/tests/transport/test_grpc.py index 3437658a3..f62ab0eae 100644 --- a/tests/transport/test_grpc.py +++ b/tests/transport/test_grpc.py @@ -17,7 +17,7 @@ import time import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import credentials @@ -28,7 +28,7 @@ try: # pylint: disable=ungrouped-imports - import grpc + import grpc # type: ignore import google.auth.transport.grpc HAS_GRPC = True diff --git a/tests/transport/test_mtls.py b/tests/transport/test_mtls.py index ff70bb3c2..b62063e47 100644 --- a/tests/transport/test_mtls.py +++ b/tests/transport/test_mtls.py @@ -13,7 +13,7 @@ # limitations under the License. import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.auth.transport import mtls diff --git a/tests/transport/test_requests.py b/tests/transport/test_requests.py index ed9300d76..60d44a5f4 100644 --- a/tests/transport/test_requests.py +++ b/tests/transport/test_requests.py @@ -20,7 +20,7 @@ import freezegun import mock import OpenSSL -import pytest +import pytest # type: ignore import requests import requests.adapters from six.moves import http_client diff --git a/tests/transport/test_urllib3.py b/tests/transport/test_urllib3.py index e3848c177..396961c39 100644 --- a/tests/transport/test_urllib3.py +++ b/tests/transport/test_urllib3.py @@ -17,9 +17,9 @@ import mock import OpenSSL -import pytest +import pytest # type: ignore from six.moves import http_client -import urllib3 +import urllib3 # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/tests_async/conftest.py b/tests_async/conftest.py index b4e90f0e8..f13dec0e4 100644 --- a/tests_async/conftest.py +++ b/tests_async/conftest.py @@ -16,7 +16,7 @@ import sys import mock -import pytest +import pytest # type: ignore def pytest_configure(): diff --git a/tests_async/oauth2/test__client_async.py b/tests_async/oauth2/test__client_async.py index 6e48c4590..91874cdd4 100644 --- a/tests_async/oauth2/test__client_async.py +++ b/tests_async/oauth2/test__client_async.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore import six from six.moves import http_client from six.moves import urllib diff --git a/tests_async/oauth2/test_credentials_async.py b/tests_async/oauth2/test_credentials_async.py index 06c91419c..a328cc3cb 100644 --- a/tests_async/oauth2/test_credentials_async.py +++ b/tests_async/oauth2/test_credentials_async.py @@ -19,7 +19,7 @@ import sys import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/tests_async/oauth2/test_id_token.py b/tests_async/oauth2/test_id_token.py index 2aee7676b..b84e74db2 100644 --- a/tests_async/oauth2/test_id_token.py +++ b/tests_async/oauth2/test_id_token.py @@ -15,7 +15,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/tests_async/oauth2/test_reauth_async.py b/tests_async/oauth2/test_reauth_async.py index d982e13a1..8f51bd3a7 100644 --- a/tests_async/oauth2/test_reauth_async.py +++ b/tests_async/oauth2/test_reauth_async.py @@ -15,7 +15,7 @@ import copy import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import _reauth_async diff --git a/tests_async/oauth2/test_service_account_async.py b/tests_async/oauth2/test_service_account_async.py index 3dce13d82..176992f77 100644 --- a/tests_async/oauth2/test_service_account_async.py +++ b/tests_async/oauth2/test_service_account_async.py @@ -15,7 +15,7 @@ import datetime import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import crypt diff --git a/tests_async/test__default_async.py b/tests_async/test__default_async.py index 69a50d69a..2a1921081 100644 --- a/tests_async/test__default_async.py +++ b/tests_async/test__default_async.py @@ -16,10 +16,11 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _credentials_async as credentials from google.auth import _default_async as _default +from google.auth import api_key from google.auth import app_engine from google.auth import compute_engine from google.auth import environment_vars @@ -561,3 +562,46 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE credentials, project_id = _default.default_async(quota_project_id="project-foo") + + +def test__get_api_key_credentials_no_env_var(): + cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") + assert cred is None + assert project_id is None + + +def test__get_api_key_credentials_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default._get_api_key_credentials( + quota_project_id="project-foo" + ) + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id == "project-foo" + + +def test_exception_with_api_key_and_adc_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + with mock.patch.dict( + os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.default_async() + + assert excinfo.match( + r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + + +def test_default_api_key_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default.default_async() + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id is None + + +def test_get_api_key_credentials(): + cred = _default.get_api_key_credentials("api-key") + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" diff --git a/tests_async/test_credentials_async.py b/tests_async/test_credentials_async.py index 5315483da..9db5fc9ae 100644 --- a/tests_async/test_credentials_async.py +++ b/tests_async/test_credentials_async.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from google.auth import _credentials_async as credentials from google.auth import _helpers diff --git a/tests_async/test_jwt_async.py b/tests_async/test_jwt_async.py index a35b837b7..f24a0a99d 100644 --- a/tests_async/test_jwt_async.py +++ b/tests_async/test_jwt_async.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from google.auth import _jwt_async as jwt_async from google.auth import crypt diff --git a/tests_async/transport/async_compliance.py b/tests_async/transport/async_compliance.py index 9c4b173c2..36fe7a301 100644 --- a/tests_async/transport/async_compliance.py +++ b/tests_async/transport/async_compliance.py @@ -14,9 +14,9 @@ import time -import flask -import pytest -from pytest_localserver.http import WSGIServer +import flask # type: ignore +import pytest # type: ignore +from pytest_localserver.http import WSGIServer # type: ignore from six.moves import http_client from google.auth import exceptions diff --git a/tests_async/transport/test_aiohttp_requests.py b/tests_async/transport/test_aiohttp_requests.py index a64a4eec9..d00955a7d 100644 --- a/tests_async/transport/test_aiohttp_requests.py +++ b/tests_async/transport/test_aiohttp_requests.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import aiohttp -from aioresponses import aioresponses, core +import aiohttp # type: ignore +from aioresponses import aioresponses, core # type: ignore import mock -import pytest +import pytest # type: ignore from tests_async.transport import async_compliance import google.auth._credentials_async