diff --git a/firebase_admin/credentials.py b/firebase_admin/credentials.py index 5477e1cf7..850cd474a 100644 --- a/firebase_admin/credentials.py +++ b/firebase_admin/credentials.py @@ -18,10 +18,9 @@ import pathlib import google.auth +import google.auth.impersonated_credentials from google.auth.transport import requests -from google.oauth2 import credentials -from google.oauth2 import service_account - +from google.oauth2 import credentials, service_account _request = requests.Request() _scopes = [ @@ -215,6 +214,64 @@ def get_credential(self): return self._g_credential +class ImpersonatedCredentials(Base): + """A credential initialized from a google.auth.impersonated_credentials.Credentials""" + + def __init__(self, icreds: google.auth.impersonated_credentials.Credentials): + """Initializes a credential from a google.auth.impersonated_credentials.Credentials. + + Args: + icreds: A google.auth.impersonated_credentials.Credentials instance. + + Raises: + ValueError: If the impersonated credential is invalid. + + Example: + ```python + import google.auth + import firebase_admin + from firebase_admin.credentials import ImpersonatedCredentials + + creds, project_id = google.auth.default(quota_project_id=PROJECT_ID_DEFAULT, scopes=_scopes,) + logger.info(f"Obtained default credentials for the project {project_id}") + fullname_service_account = ( + f"{service_account_name_filtered}@{project_id}.iam.gserviceaccount.com" + ) + logger.info( + f"Obtained impersonated credentials for the service account {fullname_service_account}", + ) + + icreds = google.auth.impersonated_credentials.Credentials( + source_credentials=creds, + target_principal=fullname_service_account, + target_scopes=_scopes, + ) + + impersonated_creds = ImpersonatedCredentials(icreds) + + app = firebase_admin.initialize_app(impersonated_creds, name=name_firebase) + ``` + """ + if not isinstance(icreds, google.auth.impersonated_credentials.Credentials): + raise ValueError( + "Invalid impersonated credentials. Credentials must be an instance of " + "google.auth.impersonated_credentials.Credentials" + ) + super(ImpersonatedCredentials, self).__init__() + self._g_credential = icreds + + def get_credential(self): + """Returns the underlying Google credential. + + Returns: + google.auth.credentials.Credentials: A Google Auth credential instance.""" + return self._g_credential + + @property + def service_account_email(self): + return self._g_credential.service_account_email + + def _is_file_path(path): try: pathlib.Path(path) diff --git a/tests/test_credentials.py b/tests/test_credentials.py index cceb6b6f9..3fc237f3a 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -17,10 +17,11 @@ import json import os import pathlib +from unittest import mock import google.auth -from google.auth import crypt -from google.auth import exceptions + +from google.auth import crypt, exceptions, impersonated_credentials from google.oauth2 import credentials as gcredentials from google.oauth2 import service_account import pytest @@ -191,3 +192,19 @@ def _verify_credential(self, credential): access_token = credential.get_access_token() assert access_token.access_token == 'mock_access_token' assert isinstance(access_token.expiry, datetime.datetime) + + +class TestImpersonatedCredentials: + def test_init_from_valid_credentials(self) -> None: + mock_tcreds = mock.Mock(spec=impersonated_credentials.Credentials) + credential = credentials.ImpersonatedCredentials(mock_tcreds) + assert credential._g_credential == mock_tcreds + + def test_init_from_invalid_credentials(self) -> None: + with pytest.raises(ValueError): + credentials.ImpersonatedCredentials(None) + + def test_get_credential(self) -> None: + mock_tcreds = mock.Mock(spec=impersonated_credentials.Credentials) + credential = credentials.ImpersonatedCredentials(mock_tcreds) + assert credential.get_credential() == mock_tcreds