Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions firebase_admin/_auth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@


EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
TENANT_EMULATOR_HOST_ENV_VAR = 'FIREBASE_TENANT_EMULATOR_HOST'
MAX_CLAIMS_PAYLOAD_SIZE = 1000
RESERVED_CLAIMS = set([
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
Expand Down Expand Up @@ -78,6 +79,15 @@ def get_emulator_host():
'It must follow format "host:port".')
return emulator_host

def get_tenant_emulator_host():
emulator_host = os.getenv(TENANT_EMULATOR_HOST_ENV_VAR, '')
if not emulator_host:
emulator_host = get_emulator_host()
if emulator_host and '//' in emulator_host:
raise ValueError(
f'Invalid {TENANT_EMULATOR_HOST_ENV_VAR}: "{emulator_host}". '
'It must follow format "host:port".')
return emulator_host
Comment thread
KetanHegde marked this conversation as resolved.

def is_emulated():
return get_emulator_host() != ''
Expand Down
14 changes: 12 additions & 2 deletions firebase_admin/tenant_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,19 @@ class _TenantManagementService:
TENANT_MGT_URL = 'https://identitytoolkit.googleapis.com/v2'

def __init__(self, app):
credential = app.credential.get_credential()
tenant_emulator_host = _auth_utils.get_tenant_emulator_host()

if tenant_emulator_host:
credential = _utils.EmulatorAdminCredentials()
tenant_mgt_url = (
f'http://{tenant_emulator_host}/identitytoolkit.googleapis.com/v2'
)
else:
credential = app.credential.get_credential()
tenant_mgt_url = self.TENANT_MGT_URL

version_header = f'Python/Admin/{firebase_admin.__version__}'
base_url = f'{self.TENANT_MGT_URL}/projects/{app.project_id}'
base_url = f'{tenant_mgt_url}/projects/{app.project_id}'
self.app = app
self.client = _http_client.JsonHttpClient(
credential=credential, base_url=base_url, headers={'X-Client-Version': version_header})
Expand Down
79 changes: 59 additions & 20 deletions tests/test_tenant_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,40 @@

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

TENANT_MGT_URL = 'https://identitytoolkit.googleapis.com/v2'
TENANT_MGT_URL_PREFIX = f'{TENANT_MGT_URL}/projects/mock-project-id'
TENANT_MGT_URLS = {
'URL': TENANT_MGT_URL,
'PREFIX': TENANT_MGT_URL_PREFIX,
}

@pytest.fixture(scope='module')
def tenant_mgt_app():
TENANT_EMULATOR_HOST_ENV_VAR = 'FIREBASE_TENANT_EMULATOR_HOST'
TENANT_EMULATOR_HOST = 'localhost:9099'

EMULATED_TENANT_MGT_URL = f'http://{TENANT_EMULATOR_HOST}/identitytoolkit.googleapis.com/v2'
EMULATED_TENANT_MGT_URL_PREFIX = f'{EMULATED_TENANT_MGT_URL}/projects/mock-project-id'

@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
def tenant_mgt_app(request):
monkeypatch = testutils.new_monkeypatch()
if request.param['emulated']:
monkeypatch.setenv(TENANT_EMULATOR_HOST_ENV_VAR, TENANT_EMULATOR_HOST)
monkeypatch.setitem(TENANT_MGT_URLS, 'URL', EMULATED_TENANT_MGT_URL)
monkeypatch.setitem(TENANT_MGT_URLS, 'PREFIX', EMULATED_TENANT_MGT_URL_PREFIX)
app = firebase_admin.initialize_app(
testutils.MockCredential(), name='tenantMgt', options={'projectId': 'mock-project-id'})
yield app
firebase_admin.delete_app(app)
monkeypatch.undo()


def _instrument_tenant_mgt(app, status, payload):
service = tenant_mgt._get_tenant_mgt_service(app)
recorder = []
mount_url = service.client._base_url.rsplit('/projects/', 1)[0]
service.client.session.mount(
tenant_mgt._TenantManagementService.TENANT_MGT_URL,
mount_url,
testutils.MockAdapter(payload, status, recorder))
return service, recorder

Expand Down Expand Up @@ -197,10 +215,13 @@ def test_get_tenant(self, tenant_mgt_app):
assert len(recorder) == 1
req = recorder[0]
assert req.method == 'GET'
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id'
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers

def test_tenant_not_found(self, tenant_mgt_app):
_instrument_tenant_mgt(tenant_mgt_app, 500, TENANT_NOT_FOUND_RESPONSE)
Expand Down Expand Up @@ -290,10 +311,13 @@ def _assert_request(self, recorder, body):
assert len(recorder) == 1
req = recorder[0]
assert req.method == 'POST'
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants'
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants'
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers
got = json.loads(req.body.decode())
assert got == body

Expand Down Expand Up @@ -390,10 +414,16 @@ def _assert_request(self, recorder, body, mask):
assert len(recorder) == 1
req = recorder[0]
assert req.method == 'PATCH'
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id?updateMask={",".join(mask)}'
assert req.url == (
f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
f'?updateMask={",".join(mask)}'
)
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers
got = json.loads(req.body.decode())
assert got == body

Expand All @@ -413,10 +443,13 @@ def test_delete_tenant(self, tenant_mgt_app):
assert len(recorder) == 1
req = recorder[0]
assert req.method == 'DELETE'
assert req.url == f'{TENANT_MGT_URL_PREFIX}/tenants/tenant-id'
assert req.url == f'{TENANT_MGT_URLS["PREFIX"]}/tenants/tenant-id'
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers

def test_tenant_not_found(self, tenant_mgt_app):
_instrument_tenant_mgt(tenant_mgt_app, 500, TENANT_NOT_FOUND_RESPONSE)
Expand Down Expand Up @@ -560,8 +593,11 @@ def _assert_request(self, recorder, expected=None):
req = recorder[0]
assert req.method == 'GET'
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers
request = dict(parse.parse_qsl(parse.urlsplit(req.url).query))
assert request == expected

Expand Down Expand Up @@ -944,8 +980,11 @@ def _assert_request(
assert req.method == method
assert req.url == f'{prefix}/tenants/tenant-id{want_url}'
assert req.headers['X-Client-Version'] == f'Python/Admin/{firebase_admin.__version__}'
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
assert req.headers['x-goog-api-client'] == expected_metrics_header
expected_metrics_headers = [
_utils.get_metrics_header(),
_utils.get_metrics_header() + ' mock-cred-metric-tag',
]
assert req.headers['x-goog-api-client'] in expected_metrics_headers
body = json.loads(req.body.decode())
assert body == want_body

Expand Down