From 5a48ad55abe31886b8c8314205789edf000bae4d Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 2 Aug 2022 09:56:28 -0700 Subject: [PATCH] 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):