diff --git a/google/auth/aws.py b/google/auth/aws.py index 873beef59..08c94427e 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 @@ -352,13 +351,8 @@ def __init__( 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, + *args, + **kwargs ): """Instantiates an AWS workload external account credentials object. @@ -369,15 +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. - 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. + 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 @@ -393,13 +380,8 @@ def __init__( 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, + *args, + **kwargs ) credential_source = credential_source or {} self._environment_id = credential_source.get("environment_id") or "" @@ -750,23 +732,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 +745,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..a87f92ea4 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 a Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The 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 a Credentials instance from an external account json file. + + Args: + filename (str): The path to the 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..5fa9faef9 100644 --- a/google/auth/identity_pool.py +++ b/google/auth/identity_pool.py @@ -56,14 +56,8 @@ def __init__( 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, + *args, + **kwargs ): """Instantiates an external account credentials object from a file/URL. @@ -91,21 +85,8 @@ 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. + 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 @@ -122,14 +103,8 @@ def __init__( 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, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_file = None @@ -257,24 +232,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 +246,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..ca583744a 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 @@ -58,14 +57,8 @@ def __init__( 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, + *args, + **kwargs ): """Instantiates an external account credentials object from a executables. @@ -86,21 +79,8 @@ def __init__( "output_file": "/path/to/generated/cached/credentials" } } - - 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. + 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 @@ -117,13 +97,8 @@ def __init__( 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, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_executable = None @@ -250,24 +225,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 +239,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/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 97fcbb0de..84604a64b 100644 Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ 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):