Skip to content

Commit 80e4d07

Browse files
committed
Support auth emulator via FIREBASE_AUTH_EMULATOR_HOST
Modeled on firebase/firebase-admin-go#414
1 parent b35abb9 commit 80e4d07

File tree

4 files changed

+50
-10
lines changed

4 files changed

+50
-10
lines changed

firebase_admin/_auth_client.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414

1515
"""Firebase auth client sub module."""
1616

17+
import os
1718
import time
1819

20+
from google.oauth2 import credentials
21+
from google.auth.exceptions import DefaultCredentialsError
22+
1923
import firebase_admin
2024
from firebase_admin import _auth_providers
2125
from firebase_admin import _auth_utils
@@ -25,6 +29,8 @@
2529
from firebase_admin import _user_import
2630
from firebase_admin import _user_mgt
2731

32+
_EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
33+
_DEFAULT_AUTH_URL = 'https://identitytoolkit.googleapis.com'
2834

2935
class Client:
3036
"""Firebase Authentication client scoped to a specific tenant."""
@@ -36,17 +42,45 @@ def __init__(self, app, tenant_id=None):
3642
2. set the project ID explicitly via Firebase App options, or
3743
3. set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.""")
3844

39-
credential = app.credential.get_credential()
45+
credential = None
4046
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
47+
# Non-default endpoint URLs for emulator support are set in this dict later.
48+
id_toolkit_endpoints = {}
49+
50+
# If an emulator is present, check that the given value matches the expected format and set
51+
# endpoint URLs to use the emulator. Also set a fake authorization token.
52+
emulator_host = os.environ.get(_EMULATOR_HOST_ENV_VAR)
53+
if emulator_host:
54+
if '//' in emulator_host:
55+
raise ValueError(
56+
'Invalid {0}: "{1}". It must follow format "host:port".'.format(
57+
_EMULATOR_HOST_ENV_VAR, emulator_host))
58+
base_url = 'http://{0}/identitytoolkit.googleapis.com'.format(emulator_host)
59+
id_toolkit_endpoints['v1'] = base_url + '/v1'
60+
id_toolkit_endpoints['v2beta1'] = base_url + '/v2beta1'
61+
62+
try:
63+
# Use credentials if provided
64+
credential = app.credential.get_credential()
65+
except DefaultCredentialsError as credential_error:
66+
# The emulator can make do with fake credentials
67+
if emulator_host:
68+
credential = credentials.Credentials(token='owner')
69+
else:
70+
# Not emulated, no credential, raise
71+
raise credential_error
72+
4173
http_client = _http_client.JsonHttpClient(
4274
credential=credential, headers={'X-Client-Version': version_header})
4375

4476
self._tenant_id = tenant_id
45-
self._token_generator = _token_gen.TokenGenerator(app, http_client)
77+
self._token_generator = _token_gen.TokenGenerator(
78+
app, http_client, id_toolkit_endpoints.get('v1'))
4679
self._token_verifier = _token_gen.TokenVerifier(app)
47-
self._user_manager = _user_mgt.UserManager(http_client, app.project_id, tenant_id)
80+
self._user_manager = _user_mgt.UserManager(
81+
http_client, app.project_id, tenant_id, id_toolkit_endpoints.get('v1'))
4882
self._provider_manager = _auth_providers.ProviderConfigClient(
49-
http_client, app.project_id, tenant_id)
83+
http_client, app.project_id, tenant_id, id_toolkit_endpoints.get('v2beta1'))
5084

5185
@property
5286
def tenant_id(self):

firebase_admin/_auth_providers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,11 @@ class ProviderConfigClient:
166166

167167
PROVIDER_CONFIG_URL = 'https://identitytoolkit.googleapis.com/v2beta1'
168168

169-
def __init__(self, http_client, project_id, tenant_id=None):
169+
def __init__(self, http_client, project_id, tenant_id=None, provider_config_url=None):
170170
self.http_client = http_client
171-
self.base_url = '{0}/projects/{1}'.format(self.PROVIDER_CONFIG_URL, project_id)
171+
if not provider_config_url:
172+
provider_config_url = self.PROVIDER_CONFIG_URL
173+
self.base_url = '{0}/projects/{1}'.format(provider_config_url, project_id)
172174
if tenant_id:
173175
self.base_url += '/tenants/{0}'.format(tenant_id)
174176

firebase_admin/_token_gen.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,13 @@ class TokenGenerator:
8484

8585
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1'
8686

87-
def __init__(self, app, http_client):
87+
def __init__(self, app, http_client, id_toolkit_url=None):
8888
self.app = app
8989
self.http_client = http_client
9090
self.request = transport.requests.Request()
91-
self.base_url = '{0}/projects/{1}'.format(self.ID_TOOLKIT_URL, app.project_id)
91+
if not id_toolkit_url:
92+
id_toolkit_url = self.ID_TOOLKIT_URL
93+
self.base_url = '{0}/projects/{1}'.format(id_toolkit_url, app.project_id)
9294
self._signing_provider = None
9395

9496
def _init_signing_provider(self):

firebase_admin/_user_mgt.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,9 +573,11 @@ class UserManager:
573573

574574
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1'
575575

576-
def __init__(self, http_client, project_id, tenant_id=None):
576+
def __init__(self, http_client, project_id, tenant_id=None, id_toolkit_url=None):
577577
self.http_client = http_client
578-
self.base_url = '{0}/projects/{1}'.format(self.ID_TOOLKIT_URL, project_id)
578+
if not id_toolkit_url:
579+
id_toolkit_url = self.ID_TOOLKIT_URL
580+
self.base_url = '{0}/projects/{1}'.format(id_toolkit_url, project_id)
579581
if tenant_id:
580582
self.base_url += '/tenants/{0}'.format(tenant_id)
581583

0 commit comments

Comments
 (0)