From 84779170a7056943f34ddb0d3a2925c1fd392316 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 10:07:09 -0700 Subject: [PATCH 1/7] fix: refactor credential subclass parameters --- google/auth/aws.py | 55 ++-------------------------- google/auth/external_account.py | 54 ++++++++++++++++++++++++++- google/auth/identity_pool.py | 65 ++------------------------------- google/auth/pluggable.py | 65 ++------------------------------- tests/test_aws.py | 4 ++ 5 files changed, 69 insertions(+), 174 deletions(-) diff --git a/google/auth/aws.py b/google/auth/aws.py index 873beef59..753e18147 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -39,7 +39,6 @@ import hashlib import hmac -import io import json import os import posixpath @@ -349,35 +348,15 @@ class Credentials(external_account.Credentials): def __init__( self, audience, - subject_token_type, - token_url, credential_source=None, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, + **kwargs, ): """Instantiates an AWS workload external account credentials object. Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. - service_account_impersonation_url (Optional[str]): The optional - service account impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during - the authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -390,16 +369,8 @@ def __init__( """ super(Credentials, self).__init__( audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - service_account_impersonation_options=service_account_impersonation_options, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, + **kwargs, ) credential_source = credential_source or {} self._environment_id = credential_source.get("environment_id") or "" @@ -750,23 +721,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -779,6 +734,4 @@ def from_file(cls, filename, **kwargs): Returns: google.auth.aws.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) diff --git a/google/auth/external_account.py b/google/auth/external_account.py index 5c6ce2a40..40e6b9227 100644 --- a/google/auth/external_account.py +++ b/google/auth/external_account.py @@ -30,6 +30,7 @@ import abc import copy import datetime +import io import json import re @@ -70,7 +71,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, - service_account_impersonation_options={}, + service_account_impersonation_options=None, client_id=None, client_secret=None, quota_project_id=None, @@ -482,3 +483,54 @@ def is_valid_url(patterns, url): return False return any(re.compile(p).match(uri.hostname.lower()) for p in patterns) + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an Identity Pool Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The Identity Pool external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an IdentityPool Credentials instance from an external account json file. + + Args: + filename (str): The path to the IdentityPool external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index a086d283e..1ae2e5071 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -52,25 +52,12 @@ class Credentials(external_account.Credentials): def __init__( self, - audience, - subject_token_type, - token_url, credential_source, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, + **kwargs, ): """Instantiates an external account credentials object from a file/URL. Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. @@ -92,21 +79,6 @@ def __init__( "file": "/path/to/token/file.txt" } - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - workforce_pool_user_project (Optona[str]): The optional workforce pool user - project number when the credential corresponds to a workforce pool and not - a workload identity pool. The underlying principal must still have - serviceusage.services.use IAM permission to use the project for - billing/quota. - Raises: google.auth.exceptions.RefreshError: If an error is encountered during access token retrieval logic. @@ -118,18 +90,8 @@ def __init__( """ super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - service_account_impersonation_options=service_account_impersonation_options, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, + **kwargs, ) if not isinstance(credential_source, Mapping): self._credential_source_file = None @@ -257,24 +219,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - workforce_pool_user_project=info.get("workforce_pool_user_project"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -288,6 +233,4 @@ def from_file(cls, filename, **kwargs): google.auth.identity_pool.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) diff --git a/google/auth/pluggable.py b/google/auth/pluggable.py index 96ccd9ca8..5fe7042bf 100644 --- a/google/auth/pluggable.py +++ b/google/auth/pluggable.py @@ -35,7 +35,6 @@ # Python 2.7 compatibility except ImportError: # pragma: NO COVER from collections import Mapping -import io import json import os import subprocess @@ -54,25 +53,12 @@ class Credentials(external_account.Credentials): def __init__( self, - audience, - subject_token_type, - token_url, credential_source, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, + **kwargs, ): """Instantiates an external account credentials object from a executables. Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. @@ -87,21 +73,6 @@ def __init__( } } - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - workforce_pool_user_project (Optona[str]): The optional workforce pool user - project number when the credential corresponds to a workforce pool and not - a workload Pluggable. The underlying principal must still have - serviceusage.services.use IAM permission to use the project for - billing/quota. - Raises: google.auth.exceptions.RefreshError: If an error is encountered during access token retrieval logic. @@ -113,17 +84,8 @@ def __init__( """ super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, + **kwargs, ) if not isinstance(credential_source, Mapping): self._credential_source_executable = None @@ -250,24 +212,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - workforce_pool_user_project=info.get("workforce_pool_user_project"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -281,9 +226,7 @@ def from_file(cls, filename, **kwargs): google.auth.pluggable.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) def _parse_subject_token(self, response): if "version" not in response: diff --git a/tests/test_aws.py b/tests/test_aws.py index 26c49e197..0a451f3eb 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -817,6 +817,7 @@ def test_from_info_full_options(self, mock_init): client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -842,6 +843,7 @@ def test_from_info_required_options_only(self, mock_init): client_secret=None, credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, + workforce_pool_user_project=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -873,6 +875,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -899,6 +902,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): client_secret=None, credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, + workforce_pool_user_project=None, ) def test_constructor_invalid_credential_source(self): From b8d144ae0be8f0de1a7557552218370ea6c3b27d Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 12:06:52 -0700 Subject: [PATCH 2/7] Changes requested by @clundin25 --- google/auth/aws.py | 15 +++++++++++++-- google/auth/identity_pool.py | 17 +++++++++++++++-- google/auth/pluggable.py | 17 +++++++++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/google/auth/aws.py b/google/auth/aws.py index 753e18147..dbe2e75ca 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -348,15 +348,23 @@ class Credentials(external_account.Credentials): def __init__( self, audience, + subject_token_type, + token_url, credential_source=None, - **kwargs, + *args, + **kwargs ): """Instantiates an AWS workload external account credentials object. Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. + args (List): Optional positional arguments passed into the underlying __init__ method + kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -369,8 +377,11 @@ def __init__( """ super(Credentials, self).__init__( audience=audience, + subject_token_type=subject_token_type, + token_url=tokenurl, credential_source=credential_source, - **kwargs, + *args, + **kwargs ) credential_source = credential_source or {} self._environment_id = credential_source.get("environment_id") or "" diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index 1ae2e5071..ff10f819f 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -52,12 +52,19 @@ class Credentials(external_account.Credentials): def __init__( self, + audience, + subject_token_type, + token_url, credential_source, - **kwargs, + *args, + **kwargs ): """Instantiates an external account credentials object from a file/URL. Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. @@ -78,6 +85,8 @@ def __init__( { "file": "/path/to/token/file.txt" } + args (List): Optional positional arguments passed into the underlying __init__ method + kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -90,8 +99,12 @@ def __init__( """ super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=tokenurl, credential_source=credential_source, - **kwargs, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_file = None diff --git a/google/auth/pluggable.py b/google/auth/pluggable.py index 5fe7042bf..6332bea4d 100644 --- a/google/auth/pluggable.py +++ b/google/auth/pluggable.py @@ -53,12 +53,19 @@ class Credentials(external_account.Credentials): def __init__( self, + audience, + subject_token_type, + token_url, credential_source, - **kwargs, + *args, + **kwargs ): """Instantiates an external account credentials object from a executables. Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. @@ -72,6 +79,8 @@ def __init__( "output_file": "/path/to/generated/cached/credentials" } } + args (List): Optional positional arguments passed into the underlying __init__ method + kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -84,8 +93,12 @@ def __init__( """ super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=tokenurl, credential_source=credential_source, - **kwargs, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_executable = None From 1b3a1f153130b0db250c87d944e9cc6885395c32 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 12:20:24 -0700 Subject: [PATCH 3/7] hotlinking __init__ method --- google/auth/aws.py | 4 ++-- google/auth/identity_pool.py | 4 ++-- google/auth/pluggable.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/google/auth/aws.py b/google/auth/aws.py index dbe2e75ca..665f2297a 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -363,8 +363,8 @@ def __init__( credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. - args (List): Optional positional arguments passed into the underlying __init__ method - kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. method + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method Raises: google.auth.exceptions.RefreshError: If an error is encountered during diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index ff10f819f..4690e3f95 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -85,8 +85,8 @@ def __init__( { "file": "/path/to/token/file.txt" } - args (List): Optional positional arguments passed into the underlying __init__ method - kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method Raises: google.auth.exceptions.RefreshError: If an error is encountered during diff --git a/google/auth/pluggable.py b/google/auth/pluggable.py index 6332bea4d..00346c312 100644 --- a/google/auth/pluggable.py +++ b/google/auth/pluggable.py @@ -79,8 +79,8 @@ def __init__( "output_file": "/path/to/generated/cached/credentials" } } - args (List): Optional positional arguments passed into the underlying __init__ method - kwargs (Mapping): Optional keyword arguments passed into the underlying __init__ method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method Raises: google.auth.exceptions.RefreshError: If an error is encountered during From 3c08ed9bb4b430de6c00c46bd84de0c56340add0 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 12:22:32 -0700 Subject: [PATCH 4/7] punctuation --- google/auth/aws.py | 4 ++-- google/auth/identity_pool.py | 4 ++-- google/auth/pluggable.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/google/auth/aws.py b/google/auth/aws.py index 665f2297a..6a8d0f518 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -363,8 +363,8 @@ def __init__( credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. - args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. method - kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index 4690e3f95..ca3893d66 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -85,8 +85,8 @@ def __init__( { "file": "/path/to/token/file.txt" } - args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method - kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during diff --git a/google/auth/pluggable.py b/google/auth/pluggable.py index 00346c312..1f10233a0 100644 --- a/google/auth/pluggable.py +++ b/google/auth/pluggable.py @@ -79,8 +79,8 @@ def __init__( "output_file": "/path/to/generated/cached/credentials" } } - args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method - kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during From 0fc7d1566ab2ade17cd0eb21ffe32c461c41ee40 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 12:26:31 -0700 Subject: [PATCH 5/7] getting unit tests to pass --- google/auth/aws.py | 2 +- google/auth/identity_pool.py | 2 +- google/auth/pluggable.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/google/auth/aws.py b/google/auth/aws.py index 6a8d0f518..08c94427e 100644 --- a/google/auth/aws.py +++ b/google/auth/aws.py @@ -378,7 +378,7 @@ def __init__( super(Credentials, self).__init__( audience=audience, subject_token_type=subject_token_type, - token_url=tokenurl, + token_url=token_url, credential_source=credential_source, *args, **kwargs diff --git a/google/auth/identity_pool.py b/google/auth/identity_pool.py index ca3893d66..5fa9faef9 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -101,7 +101,7 @@ def __init__( super(Credentials, self).__init__( audience=audience, subject_token_type=subject_token_type, - token_url=tokenurl, + token_url=token_url, credential_source=credential_source, *args, **kwargs diff --git a/google/auth/pluggable.py b/google/auth/pluggable.py index 1f10233a0..ca583744a 100644 --- a/google/auth/pluggable.py +++ b/google/auth/pluggable.py @@ -95,7 +95,7 @@ def __init__( super(Credentials, self).__init__( audience=audience, subject_token_type=subject_token_type, - token_url=tokenurl, + token_url=token_url, credential_source=credential_source, *args, **kwargs From 8fe493cd93715253bdaf48cc02e0120f757ac03b Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 12:30:33 -0700 Subject: [PATCH 6/7] fixing comments --- google/auth/external_account.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google/auth/external_account.py b/google/auth/external_account.py index 40e6b9227..a87f92ea4 100644 --- a/google/auth/external_account.py +++ b/google/auth/external_account.py @@ -486,10 +486,10 @@ def is_valid_url(patterns, url): @classmethod def from_info(cls, info, **kwargs): - """Creates an Identity Pool Credentials instance from parsed external account info. + """Creates a Credentials instance from parsed external account info. Args: - info (Mapping[str, str]): The Identity Pool external account info in Google + info (Mapping[str, str]): The external account info in Google format. kwargs: Additional arguments to pass to the constructor. @@ -521,10 +521,10 @@ def from_info(cls, info, **kwargs): @classmethod def from_file(cls, filename, **kwargs): - """Creates an IdentityPool Credentials instance from an external account json file. + """Creates a Credentials instance from an external account json file. Args: - filename (str): The path to the IdentityPool external account json file. + filename (str): The path to the external account json file. kwargs: Additional arguments to pass to the constructor. Returns: From 410f1ad237db4aff980046dba0fc11e20bb4a93c Mon Sep 17 00:00:00 2001 From: Carl Lundin Date: Tue, 2 Aug 2022 20:07:12 +0000 Subject: [PATCH 7/7] Refresh system test token. --- system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 97fcbb0dead259761d8dfa34c8031713dae74b0d..84604a64bb03894af031ae960d793489f24b5b7a 100644 GIT binary patch literal 10324 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 literal 10324 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 mX0s8}zG_aK^N7Np<