Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 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,13 @@ 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 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