Skip to content

Commit 5688dff

Browse files
committed
Support FIREBASE_TENANT_EMULATOR_HOST in TenantManagementService
1 parent c5e66f8 commit 5688dff

3 files changed

Lines changed: 79 additions & 22 deletions

File tree

firebase_admin/_auth_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525

2626
EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
27+
TENANT_EMULATOR_HOST_ENV_VAR = 'FIREBASE_TENANT_EMULATOR_HOST'
2728
MAX_CLAIMS_PAYLOAD_SIZE = 1000
2829
RESERVED_CLAIMS = set([
2930
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
@@ -78,6 +79,13 @@ def get_emulator_host():
7879
'It must follow format "host:port".')
7980
return emulator_host
8081

82+
def get_tenant_emulator_host():
83+
emulator_host = os.getenv(TENANT_EMULATOR_HOST_ENV_VAR, '')
84+
if emulator_host and '//' in emulator_host:
85+
raise ValueError(
86+
f'Invalid {TENANT_EMULATOR_HOST_ENV_VAR}: "{emulator_host}". '
87+
'It must follow format "host:port".')
88+
return emulator_host
8189

8290
def is_emulated():
8391
return get_emulator_host() != ''

firebase_admin/tenant_mgt.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,19 @@ class _TenantManagementService:
235235
TENANT_MGT_URL = 'https://identitytoolkit.googleapis.com/v2'
236236

237237
def __init__(self, app):
238-
credential = app.credential.get_credential()
238+
tenant_emulator_host = _auth_utils.get_tenant_emulator_host()
239+
240+
if tenant_emulator_host:
241+
credential = _utils.EmulatorAdminCredentials()
242+
tenant_mgt_url = (
243+
f'http://{tenant_emulator_host}/identitytoolkit.googleapis.com/v2'
244+
)
245+
else:
246+
credential = app.credential.get_credential()
247+
tenant_mgt_url = self.TENANT_MGT_URL
248+
239249
version_header = f'Python/Admin/{firebase_admin.__version__}'
240-
base_url = f'{self.TENANT_MGT_URL}/projects/{app.project_id}'
250+
base_url = f'{tenant_mgt_url}/projects/{app.project_id}'
241251
self.app = app
242252
self.client = _http_client.JsonHttpClient(
243253
credential=credential, base_url=base_url, headers={'X-Client-Version': version_header})

tests/test_tenant_mgt.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -112,22 +112,40 @@
112112

113113
USER_MGT_URL_PREFIX = 'https://identitytoolkit.googleapis.com/v1/projects/mock-project-id'
114114
PROVIDER_MGT_URL_PREFIX = 'https://identitytoolkit.googleapis.com/v2/projects/mock-project-id'
115-
TENANT_MGT_URL_PREFIX = 'https://identitytoolkit.googleapis.com/v2/projects/mock-project-id'
116115

116+
TENANT_MGT_URL = 'https://identitytoolkit.googleapis.com/v2'
117+
TENANT_MGT_URL_PREFIX = f'{TENANT_MGT_URL}/projects/mock-project-id'
118+
TENANT_MGT_URLS = {
119+
'URL': TENANT_MGT_URL,
120+
'PREFIX': TENANT_MGT_URL_PREFIX,
121+
}
117122

118-
@pytest.fixture(scope='module')
119-
def tenant_mgt_app():
123+
TENANT_EMULATOR_HOST_ENV_VAR = 'FIREBASE_TENANT_EMULATOR_HOST'
124+
TENANT_EMULATOR_HOST = 'localhost:9099'
125+
126+
EMULATED_TENANT_MGT_URL = f'http://{TENANT_EMULATOR_HOST}/identitytoolkit.googleapis.com/v2'
127+
EMULATED_TENANT_MGT_URL_PREFIX = f'{EMULATED_TENANT_MGT_URL}/projects/mock-project-id'
128+
129+
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
130+
def tenant_mgt_app(request):
131+
monkeypatch = testutils.new_monkeypatch()
132+
if request.param['emulated']:
133+
monkeypatch.setenv(TENANT_EMULATOR_HOST_ENV_VAR, TENANT_EMULATOR_HOST)
134+
monkeypatch.setitem(TENANT_MGT_URLS, 'URL', EMULATED_TENANT_MGT_URL)
135+
monkeypatch.setitem(TENANT_MGT_URLS, 'PREFIX', EMULATED_TENANT_MGT_URL_PREFIX)
120136
app = firebase_admin.initialize_app(
121137
testutils.MockCredential(), name='tenantMgt', options={'projectId': 'mock-project-id'})
122138
yield app
123139
firebase_admin.delete_app(app)
140+
monkeypatch.undo()
124141

125142

126143
def _instrument_tenant_mgt(app, status, payload):
127144
service = tenant_mgt._get_tenant_mgt_service(app)
128145
recorder = []
146+
mount_url = service.client._base_url.rsplit('/projects/', 1)[0]
129147
service.client.session.mount(
130-
tenant_mgt._TenantManagementService.TENANT_MGT_URL,
148+
mount_url,
131149
testutils.MockAdapter(payload, status, recorder))
132150
return service, recorder
133151

@@ -197,10 +215,13 @@ def test_get_tenant(self, tenant_mgt_app):
197215
assert len(recorder) == 1
198216
req = recorder[0]
199217
assert req.method == 'GET'
200-
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id'
218+
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
201219
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
202-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
203-
assert req.headers['x-goog-api-client'] == expected_metrics_header
220+
expected_metrics_headers = [
221+
_utils.get_metrics_header(),
222+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
223+
]
224+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
204225

205226
def test_tenant_not_found(self, tenant_mgt_app):
206227
_instrument_tenant_mgt(tenant_mgt_app, 500, TENANT_NOT_FOUND_RESPONSE)
@@ -290,10 +311,13 @@ def _assert_request(self, recorder, body):
290311
assert len(recorder) == 1
291312
req = recorder[0]
292313
assert req.method == 'POST'
293-
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants'
314+
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants'
294315
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
295-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
296-
assert req.headers['x-goog-api-client'] == expected_metrics_header
316+
expected_metrics_headers = [
317+
_utils.get_metrics_header(),
318+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
319+
]
320+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
297321
got = json.loads(req.body.decode())
298322
assert got == body
299323

@@ -390,10 +414,16 @@ def _assert_request(self, recorder, body, mask):
390414
assert len(recorder) == 1
391415
req = recorder[0]
392416
assert req.method == 'PATCH'
393-
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id?updateMask={",".join(mask)}'
417+
assert req.url == (
418+
f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
419+
f'?updateMask={",".join(mask)}'
420+
)
394421
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
395-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
396-
assert req.headers['x-goog-api-client'] == expected_metrics_header
422+
expected_metrics_headers = [
423+
_utils.get_metrics_header(),
424+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
425+
]
426+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
397427
got = json.loads(req.body.decode())
398428
assert got == body
399429

@@ -413,10 +443,13 @@ def test_delete_tenant(self, tenant_mgt_app):
413443
assert len(recorder) == 1
414444
req = recorder[0]
415445
assert req.method == 'DELETE'
416-
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id'
446+
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
417447
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
418-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
419-
assert req.headers['x-goog-api-client'] == expected_metrics_header
448+
expected_metrics_headers = [
449+
_utils.get_metrics_header(),
450+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
451+
]
452+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
420453

421454
def test_tenant_not_found(self, tenant_mgt_app):
422455
_instrument_tenant_mgt(tenant_mgt_app, 500, TENANT_NOT_FOUND_RESPONSE)
@@ -560,8 +593,11 @@ def _assert_request(self, recorder, expected=None):
560593
req = recorder[0]
561594
assert req.method == 'GET'
562595
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
563-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
564-
assert req.headers['x-goog-api-client'] == expected_metrics_header
596+
expected_metrics_headers = [
597+
_utils.get_metrics_header(),
598+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
599+
]
600+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
565601
request = dict(parse.parse_qsl(parse.urlsplit(req.url).query))
566602
assert request == expected
567603

@@ -944,8 +980,11 @@ def _assert_request(
944980
assert req.method == method
945981
assert req.url == f'{prefix}/tenants/tenant-id{want_url}'
946982
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
947-
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
948-
assert req.headers['x-goog-api-client'] == expected_metrics_header
983+
expected_metrics_headers = [
984+
_utils.get_metrics_header(),
985+
_utils.get_metrics_header() + ' mock-cred-metric-tag',
986+
]
987+
assert req.headers['x-goog-api-client'] in expected_metrics_headers
949988
body = json.loads(req.body.decode())
950989
assert body == want_body
951990

0 commit comments

Comments
 (0)