Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-google-auth for openSUSE:Factory checked in at 2022-12-03 10:03:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-google-auth (Old) and /work/SRC/openSUSE:Factory/.python-google-auth.new.1835 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-google-auth" Sat Dec 3 10:03:18 2022 rev:25 rq:1039574 version:2.15.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-google-auth/python-google-auth.changes 2022-11-17 17:23:32.840761627 +0100 +++ /work/SRC/openSUSE:Factory/.python-google-auth.new.1835/python-google-auth.changes 2022-12-03 10:03:25.227168258 +0100 @@ -1,0 +2,11 @@ +Fri Dec 2 10:25:44 UTC 2022 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 2.15.0 + * Add api_key credentials (#1184) + * Introduce a way to provide scopes granted by user (#1189) + * Allow mtls sts endpoint for external account token urls. (#1185) + * CI broken by removal of py.path (#1194) + * Ensure JWT segments have the right types (#1162) + * Updated the lower bound of interactive timeout and fix the kwarg⦠(#1182) + +------------------------------------------------------------------- Old: ---- google-auth-2.14.1.tar.gz New: ---- google-auth-2.15.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-google-auth.spec ++++++ --- /var/tmp/diff_new_pack.TFFXKe/_old 2022-12-03 10:03:25.939172215 +0100 +++ /var/tmp/diff_new_pack.TFFXKe/_new 2022-12-03 10:03:25.943172237 +0100 @@ -18,7 +18,7 @@ %define skip_python2 1 Name: python-google-auth -Version: 2.14.1 +Version: 2.15.0 Release: 0 Summary: Google Authentication Library License: Apache-2.0 ++++++ google-auth-2.14.1.tar.gz -> google-auth-2.15.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/PKG-INFO new/google-auth-2.15.0/PKG-INFO --- old/google-auth-2.14.1/PKG-INFO 2022-11-08 00:45:12.815617300 +0100 +++ new/google-auth-2.15.0/PKG-INFO 2022-12-01 21:44:10.579201200 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-auth -Version: 2.14.1 +Version: 2.15.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/_default.py new/google-auth-2.15.0/google/auth/_default.py --- old/google-auth-2.14.1/google/auth/_default.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/_default.py 2022-12-01 21:40:45.000000000 +0100 @@ -479,6 +479,13 @@ return credentials, info.get("project") +def get_api_key_credentials(key): + """Return credentials with the given API key.""" + from google.auth import api_key + + return api_key.Credentials(key) + + def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/api_key.py new/google-auth-2.15.0/google/auth/api_key.py --- old/google-auth-2.14.1/google/auth/api_key.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/api_key.py 2022-12-01 21:40:45.000000000 +0100 @@ -0,0 +1,75 @@ +# Copyright 2022 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. + """ + super(Credentials, self).__init__() + if not token: + raise ValueError("Token must be a non-empty API key string") + 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/external_account.py new/google-auth-2.15.0/google/auth/external_account.py --- old/google-auth-2.14.1/google/auth/external_account.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/external_account.py 2022-12-01 21:40:45.000000000 +0100 @@ -437,11 +437,11 @@ @staticmethod def validate_token_url(token_url, url_type="token"): _TOKEN_URL_PATTERNS = [ - "^[^\\.\\s\\/\\\\]+\\.sts\\.googleapis\\.com$", - "^sts\\.googleapis\\.com$", - "^sts\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", - "^[^\\.\\s\\/\\\\]+\\-sts\\.googleapis\\.com$", - "^sts\\-[^\\.\\s\\/\\\\]+\\.p\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\.sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts\\.[^\\.\\s\\/\\\\]+(?:\\.mtls)?\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\-sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts\\-[^\\.\\s\\/\\\\]+\\.p(?:\\.mtls)?\\.googleapis\\.com$", ] if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/jwt.py new/google-auth-2.15.0/google/auth/jwt.py --- old/google-auth-2.14.1/google/auth/jwt.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/jwt.py 2022-12-01 21:40:45.000000000 +0100 @@ -133,11 +133,12 @@ token (Union[str, bytes]): The encoded JWT. Returns: - Tuple[str, str, str, str]: header, payload, signed_section, and + Tuple[Mapping, Mapping, str, str]: header, payload, signed_section, and signature. Raises: - ValueError: if there are an incorrect amount of segments in the token. + ValueError: if there are an incorrect amount of segments in the token or + segments of the wrong type. """ token = _helpers.to_bytes(token) @@ -152,6 +153,16 @@ header = _decode_jwt_segment(encoded_header) payload = _decode_jwt_segment(encoded_payload) + if not isinstance(header, Mapping): + raise ValueError( + "Header segment should be a JSON object: {0}".format(encoded_header) + ) + + if not isinstance(payload, Mapping): + raise ValueError( + "Payload segment should be a JSON object: {0}".format(encoded_payload) + ) + return header, payload, signed_section, signature diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/pluggable.py new/google-auth-2.15.0/google/auth/pluggable.py --- old/google-auth-2.14.1/google/auth/pluggable.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/pluggable.py 2022-12-01 21:40:45.000000000 +0100 @@ -52,8 +52,7 @@ EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 1000 # 5 seconds EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND = 120 * 1000 # 2 minutes -EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT = 5 * 60 * 1000 # 5 minutes -EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 60 * 1000 # 5 minutes +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 30 * 1000 # 30 seconds EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND = 30 * 60 * 1000 # 30 minutes @@ -132,7 +131,9 @@ self._credential_source_executable_output_file = self._credential_source_executable.get( "output_file" ) - self._tokeninfo_username = kwargs.get("tokeninfo_username", "") # dummy value + + # Dummy value. This variable is only used via injection, not exposed to ctor + self._tokeninfo_username = "" if not self._credential_source_executable_command: raise ValueError( @@ -150,17 +151,16 @@ ): raise ValueError("Timeout must be between 5 and 120 seconds.") - if not self._credential_source_executable_interactive_timeout_millis: - self._credential_source_executable_interactive_timeout_millis = ( - EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT - ) - elif ( - self._credential_source_executable_interactive_timeout_millis - < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND - or self._credential_source_executable_interactive_timeout_millis - > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND - ): - raise ValueError("Interactive timeout must be between 5 and 30 minutes.") + if self._credential_source_executable_interactive_timeout_millis: + if ( + self._credential_source_executable_interactive_timeout_millis + < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_interactive_timeout_millis + > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND + ): + raise ValueError( + "Interactive timeout must be between 30 seconds and 30 minutes." + ) @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): @@ -400,5 +400,13 @@ "An output_file must be specified in the credential configuration for interactive mode." ) + if ( + self.interactive + and not self._credential_source_executable_interactive_timeout_millis + ): + raise ValueError( + "Interactive mode cannot run without an interactive timeout." + ) + if self.interactive and not self.is_workforce_pool: raise ValueError("Interactive mode is only enabled for workforce pool.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/auth/version.py new/google-auth-2.15.0/google/auth/version.py --- old/google-auth-2.14.1/google/auth/version.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/auth/version.py 2022-12-01 21:40:45.000000000 +0100 @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.14.1" +__version__ = "2.15.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google/oauth2/credentials.py new/google-auth-2.15.0/google/oauth2/credentials.py --- old/google-auth-2.14.1/google/oauth2/credentials.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/google/oauth2/credentials.py 2022-12-01 21:40:45.000000000 +0100 @@ -34,6 +34,7 @@ from datetime import datetime import io import json +import logging import six @@ -43,6 +44,8 @@ from google.auth import exceptions from google.oauth2 import reauth +_LOGGER = logging.getLogger(__name__) + # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" @@ -79,6 +82,7 @@ rapt_token=None, refresh_handler=None, enable_reauth_refresh=False, + granted_scopes=None, ): """ Args: @@ -117,6 +121,9 @@ retrieving downscoped tokens from a token broker. enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow should be used. This flag is for gcloud to use only. + granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user. + This could be different from the requested scopes and it could be empty if granted + and requested scopes were same. """ super(Credentials, self).__init__() self.token = token @@ -125,6 +132,7 @@ self._id_token = id_token self._scopes = scopes self._default_scopes = default_scopes + self._granted_scopes = granted_scopes self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret @@ -155,6 +163,7 @@ self._id_token = d.get("_id_token") self._scopes = d.get("_scopes") self._default_scopes = d.get("_default_scopes") + self._granted_scopes = d.get("_granted_scopes") self._token_uri = d.get("_token_uri") self._client_id = d.get("_client_id") self._client_secret = d.get("_client_secret") @@ -175,6 +184,11 @@ return self._scopes @property + def granted_scopes(self): + """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user.""" + return self._granted_scopes + + @property def token_uri(self): """Optional[str]: The OAuth 2.0 authorization server's token endpoint URI.""" @@ -249,6 +263,7 @@ client_secret=self.client_secret, scopes=self.scopes, default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, quota_project_id=quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, @@ -266,6 +281,7 @@ client_secret=self.client_secret, scopes=self.scopes, default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, quota_project_id=self.quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, @@ -335,10 +351,15 @@ if scopes and "scope" in grant_response: requested_scopes = frozenset(scopes) - granted_scopes = frozenset(grant_response["scope"].split()) + self._granted_scopes = grant_response["scope"].split() + granted_scopes = frozenset(self._granted_scopes) scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: - raise exceptions.RefreshError( + # User might be presented with unbundled scopes at the time of + # consent. So it is a valid scenario to not have all the requested + # scopes as part of granted scopes but log a warning in case the + # developer wants to debug the scenario. + _LOGGER.warning( "Not all requested scopes were granted by the " "authorization server, missing scopes {}.".format( ", ".join(scopes_requested_but_not_granted) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google_auth.egg-info/PKG-INFO new/google-auth-2.15.0/google_auth.egg-info/PKG-INFO --- old/google-auth-2.14.1/google_auth.egg-info/PKG-INFO 2022-11-08 00:45:12.000000000 +0100 +++ new/google-auth-2.15.0/google_auth.egg-info/PKG-INFO 2022-12-01 21:44:10.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-auth -Version: 2.14.1 +Version: 2.15.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/google_auth.egg-info/SOURCES.txt new/google-auth-2.15.0/google_auth.egg-info/SOURCES.txt --- old/google-auth-2.14.1/google_auth.egg-info/SOURCES.txt 2022-11-08 00:45:12.000000000 +0100 +++ new/google-auth-2.15.0/google_auth.egg-info/SOURCES.txt 2022-12-01 21:44:10.000000000 +0100 @@ -14,6 +14,7 @@ google/auth/_jwt_async.py google/auth/_oauth2client.py google/auth/_service_account_info.py +google/auth/api_key.py google/auth/app_engine.py google/auth/aws.py google/auth/credentials.py @@ -76,6 +77,7 @@ tests/test__helpers.py tests/test__oauth2client.py tests/test__service_account_info.py +tests/test_api_key.py tests/test_app_engine.py tests/test_aws.py tests/test_credentials.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/oauth2/test_credentials.py new/google-auth-2.15.0/tests/oauth2/test_credentials.py --- old/google-auth-2.14.1/tests/oauth2/test_credentials.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/tests/oauth2/test_credentials.py 2022-12-01 21:40:45.000000000 +0100 @@ -449,6 +449,7 @@ assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes # Check that the credentials are valid (have a token and are not # expired.) @@ -466,7 +467,7 @@ token = "token" new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {"id_token": mock.sentinel.id_token} + grant_response = {"id_token": mock.sentinel.id_token, "scope": "email profile"} refresh_grant.return_value = ( # Access token token, @@ -513,6 +514,7 @@ assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(default_scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == default_scopes # Check that the credentials are valid (have a token and are not # expired.) @@ -530,10 +532,7 @@ token = "token" new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = { - "id_token": mock.sentinel.id_token, - "scopes": " ".join(scopes), - } + grant_response = {"id_token": mock.sentinel.id_token, "scope": " ".join(scopes)} refresh_grant.return_value = ( # Access token token, @@ -580,6 +579,72 @@ assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + ) + def test_credentials_with_only_default_scopes_requested_different_granted_scopes( + self, unused_utcnow, refresh_grant + ): + default_scopes = ["email", "profile"] + token = "token" + new_rapt_token = "new_rapt_token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token, "scope": "email"} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + # rapt token + new_rapt_token, + ) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + default_scopes=default_scopes, + rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, + ) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + default_scopes, + self.RAPT_TOKEN, + True, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(default_scopes) + assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == ["email"] # Check that the credentials are valid (have a token and are not # expired.) @@ -590,7 +655,7 @@ "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) - def test_credentials_with_scopes_refresh_failure_raises_refresh_error( + def test_credentials_with_scopes_refresh_different_granted_scopes( self, unused_utcnow, refresh_grant ): scopes = ["email", "profile"] @@ -628,10 +693,7 @@ ) # Refresh credentials - with pytest.raises( - exceptions.RefreshError, match="Not all requested scopes were granted" - ): - creds.refresh(request) + creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( @@ -651,6 +713,7 @@ assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes_returned # Check that the credentials are valid (have a token and are not # expired.) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/test__default.py new/google-auth-2.15.0/tests/test__default.py --- old/google-auth-2.14.1/tests/test__default.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/tests/test__default.py 2022-12-01 21:40:45.000000000 +0100 @@ -19,6 +19,7 @@ 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 @@ -683,6 +684,12 @@ assert excinfo.match("Failed to load GDCH service account credentials") +def test_get_api_key_credentials(): + creds = _default.get_api_key_credentials("api_key") + assert isinstance(creds, api_key.Credentials) + assert creds.token == "api_key" + + class _AppIdentityModule(object): """The interface of the App Idenity app engine module. See https://cloud.google.com/appengine/docs/standard/python/refdocs\ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/test_api_key.py new/google-auth-2.15.0/tests/test_api_key.py --- old/google-auth-2.14.1/tests/test_api_key.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google-auth-2.15.0/tests/test_api_key.py 2022-12-01 21:40:45.000000000 +0100 @@ -0,0 +1,45 @@ +# Copyright 2022 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/test_external_account.py new/google-auth-2.15.0/tests/test_external_account.py --- old/google-auth-2.14.1/tests/test_external_account.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/tests/test_external_account.py 2022-12-01 21:40:45.000000000 +0100 @@ -67,23 +67,31 @@ VALID_TOKEN_URLS = [ "https://sts.googleapis.com", + "https://sts.mtls.googleapis.com", "https://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.mtls.googleapis.com", "https://US-EAST-1.sts.googleapis.com", "https://sts.us-east-1.googleapis.com", "https://sts.US-WEST-1.googleapis.com", "https://us-east-1-sts.googleapis.com", "https://US-WEST-1-sts.googleapis.com", + "https://US-WEST-1-sts.mtls.googleapis.com", "https://us-west-1-sts.googleapis.com/path?query", "https://sts-us-east-1.p.googleapis.com", + "https://sts-us-east-1.p.mtls.googleapis.com", ] INVALID_TOKEN_URLS = [ "https://iamcredentials.googleapis.com", + "https://mtls.iamcredentials.googleapis.com", "sts.googleapis.com", + "mtls.sts.googleapis.com", + "mtls.googleapis.com", "https://", "http://sts.googleapis.com", "https://st.s.googleapis.com", "https://us-eas\t-1.sts.googleapis.com", "https:/us-east-1.sts.googleapis.com", + "https:/us-east-1.mtls.sts.googleapis.com", "https://US-WE/ST-1-sts.googleapis.com", "https://sts-us-east-1.googleapis.com", "https://sts-US-WEST-1.googleapis.com", @@ -95,16 +103,20 @@ "hhttps://us-east-1.sts.googleapis.com", "https://us- -1.sts.googleapis.com", "https://-sts.googleapis.com", + "https://-mtls.googleapis.com", "https://us-east-1.sts.googleapis.com.evil.com", "https://sts.pgoogleapis.com", "https://p.googleapis.com", "https://sts.p.com", + "https://sts.p.mtls.com", "http://sts.p.googleapis.com", "https://xyz-sts.p.googleapis.com", "https://sts-xyz.123.p.googleapis.com", "https://sts-xyz.p1.googleapis.com", "https://sts-xyz.p.foo.com", "https://sts-xyz.p.foo.googleapis.com", + "https://sts-xyz.mtls.p.foo.googleapis.com", + "https://sts-xyz.p.mtls.foo.googleapis.com", ] VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ "https://iamcredentials.googleapis.com", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/test_jwt.py new/google-auth-2.15.0/tests/test_jwt.py --- old/google-auth-2.14.1/tests/test_jwt.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/tests/test_jwt.py 2022-12-01 21:40:45.000000000 +0100 @@ -126,6 +126,29 @@ assert payload["metadata"]["meta"] == "data" +def test_decode_header_object(token_factory): + payload = token_factory() + # Create a malformed JWT token with a number as a header instead of a + # dictionary (3 == base64d(M7==)) + payload = b"M7." + b".".join(payload.split(b".")[1:]) + + with pytest.raises(ValueError) as excinfo: + jwt.decode(payload, certs=PUBLIC_CERT_BYTES) + assert excinfo.match(r"Header segment should be a JSON object: " + str(b"M7")) + + +def test_decode_payload_object(signer): + # Create a malformed JWT token with a payload containing both "iat" and + # "exp" strings, although not as fields of a dictionary + payload = jwt.encode(signer, "iatexp") + + with pytest.raises(ValueError) as excinfo: + jwt.decode(payload, certs=PUBLIC_CERT_BYTES) + assert excinfo.match( + r"Payload segment should be a JSON object: " + str(b"ImlhdGV4cCI") + ) + + def test_decode_valid_es256(token_factory): payload = jwt.decode( token_factory(use_es256_signer=True), certs=EC_PUBLIC_CERT_BYTES diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-auth-2.14.1/tests/test_pluggable.py new/google-auth-2.15.0/tests/test_pluggable.py --- old/google-auth-2.14.1/tests/test_pluggable.py 2022-11-08 00:41:53.000000000 +0100 +++ new/google-auth-2.15.0/tests/test_pluggable.py 2022-12-01 21:40:45.000000000 +0100 @@ -232,6 +232,21 @@ interactive=interactive, ) + def test_from_constructor_and_injection(self): + credentials = pluggable.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, + credential_source=self.CREDENTIAL_SOURCE, + interactive=True, + ) + setattr(credentials, "_tokeninfo_username", "mock_external_account_id") + + assert isinstance(credentials, pluggable.Credentials) + assert credentials.interactive + assert credentials.external_account_id == "mock_external_account_id" + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) def test_from_info_full_options(self, mock_init): credentials = pluggable.Credentials.from_info( @@ -1065,23 +1080,6 @@ assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_credential_source_interactive_timeout_missing_will_use_default_interactive_timeout_value( - self - ): - CREDENTIAL_SOURCE = { - "executable": { - "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, - "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - } - credentials = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - - assert ( - credentials._credential_source_executable_interactive_timeout_millis - == pluggable.EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_credential_source_interactive_timeout_small(self): with pytest.raises(ValueError) as excinfo: CREDENTIAL_SOURCE = { @@ -1093,7 +1091,9 @@ } _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + assert excinfo.match( + r"Interactive timeout must be between 30 seconds and 30 minutes." + ) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_credential_source_interactive_timeout_large(self): @@ -1107,7 +1107,9 @@ } _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + assert excinfo.match( + r"Interactive timeout must be between 30 seconds and 30 minutes." + ) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_executable_fail(self): @@ -1137,6 +1139,25 @@ assert excinfo.match(r"Interactive mode is only enabled for workforce pool.") @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_fail_on_validation_missing_interactive_timeout( + self + ): + CREDENTIAL_SOURCE_EXECUTABLE = { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} + credentials = self.make_pluggable( + credential_source=CREDENTIAL_SOURCE, interactive=True + ) + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Interactive mode cannot run without an interactive timeout." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_executable_fail_interactive_mode(self): with mock.patch( "subprocess.run",