diff --git a/google/auth/__init__.py b/google/auth/__init__.py index 861abe7ea..2875772b3 100644 --- a/google/auth/__init__.py +++ b/google/auth/__init__.py @@ -17,13 +17,17 @@ import logging from google.auth import version as google_auth_version -from google.auth._default import default, load_credentials_from_file +from google.auth._default import ( + default, + load_credentials_from_dict, + load_credentials_from_file, +) __version__ = google_auth_version.__version__ -__all__ = ["default", "load_credentials_from_file"] +__all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"] # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/google/auth/_default.py b/google/auth/_default.py index 4effeec9e..1ae26b4eb 100644 --- a/google/auth/_default.py +++ b/google/auth/_default.py @@ -130,6 +130,50 @@ def load_credentials_from_file( ) +def load_credentials_from_dict( + info, scopes=None, default_scopes=None, quota_project_id=None, request=None +): + """Loads Google credentials from a dict. + + The credentials file must be a service account key, stored authorized + user credentials, external account credentials, or impersonated service + account credentials. + + Args: + info (Dict[str, Any]): A dict object containing the credentials + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + if not isinstance(info, dict): + raise exceptions.DefaultCredentialsError( + "info object was of type {} but dict type was expected.".format(type(info)) + ) + + return _load_credentials_from_info( + "dict object", info, scopes, default_scopes, quota_project_id, request + ) + + def _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ): diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 04a24e425..efa90e0ea 100644 Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ diff --git a/tests/test__default.py b/tests/test__default.py index 36adf0d33..affbb7624 100644 --- a/tests/test__default.py +++ b/tests/test__default.py @@ -188,6 +188,28 @@ def test_load_credentials_from_missing_file(): assert excinfo.match(r"not found") +def test_load_credentials_from_dict_non_dict_object(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_dict("") + assert excinfo.match(r"dict type was expected") + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_dict(None) + assert excinfo.match(r"dict type was expected") + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_dict(1) + assert excinfo.match(r"dict type was expected") + + +def test_load_credentials_from_dict_authorized_user(): + credentials, project_id = _default.load_credentials_from_dict( + AUTHORIZED_USER_FILE_DATA + ) + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + + def test_load_credentials_from_file_invalid_json(tmpdir): jsonfile = tmpdir.join("invalid.json") jsonfile.write("{")