Skip to content

Commit fc355af

Browse files
authored
Tenant-scoped user management operations (#431)
* Adding tenant_mgt.auth_for_tenant() API * Added more tenant-aware user mgt tests * Full test coverage for tenant-aware user mgt APIs * Updated docstring to fix lint error * Removed unused var; Fixing lint error
1 parent 71d33c0 commit fc355af

File tree

3 files changed

+290
-11
lines changed

3 files changed

+290
-11
lines changed

firebase_admin/auth.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -523,12 +523,13 @@ def generate_sign_in_with_email_link(email, action_code_settings, app=None):
523523
email, action_code_settings=action_code_settings)
524524

525525

526+
# TODO: Rename to public type Client
526527
class _AuthService:
527528
"""Firebase Authentication service."""
528529

529530
ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1/projects/'
530531

531-
def __init__(self, app):
532+
def __init__(self, app, tenant_id=None):
532533
credential = app.credential.get_credential()
533534
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
534535

@@ -538,12 +539,21 @@ def __init__(self, app):
538539
2. set the project ID explicitly via Firebase App options, or
539540
3. set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.""")
540541

541-
client = _http_client.JsonHttpClient(
542-
credential=credential, base_url=self.ID_TOOLKIT_URL + app.project_id,
542+
url_path = app.project_id
543+
if tenant_id:
544+
url_path += '/tenants/{0}'.format(tenant_id)
545+
546+
http_client = _http_client.JsonHttpClient(
547+
credential=credential, base_url=self.ID_TOOLKIT_URL + url_path,
543548
headers={'X-Client-Version': version_header})
544-
self._token_generator = _token_gen.TokenGenerator(app, client)
549+
self._tenant_id = tenant_id
550+
self._token_generator = _token_gen.TokenGenerator(app, http_client)
545551
self._token_verifier = _token_gen.TokenVerifier(app)
546-
self._user_manager = _user_mgt.UserManager(client)
552+
self._user_manager = _user_mgt.UserManager(http_client)
553+
554+
@property
555+
def tenant_id(self):
556+
return self._tenant_id
547557

548558
def create_custom_token(self, uid, developer_claims=None):
549559
return self._token_generator.create_custom_token(uid, developer_claims)

firebase_admin/tenant_mgt.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
Google Cloud Identity Platform (GCIP) instance.
1919
"""
2020

21+
import threading
22+
2123
import requests
2224

2325
import firebase_admin
26+
from firebase_admin import auth
2427
from firebase_admin import _auth_utils
2528
from firebase_admin import _http_client
2629
from firebase_admin import _utils
@@ -35,6 +38,7 @@
3538
'Tenant',
3639
'TenantNotFoundError',
3740

41+
'auth_for_tenant',
3842
'create_tenant',
3943
'delete_tenant',
4044
'get_tenant',
@@ -45,6 +49,23 @@
4549
TenantNotFoundError = _auth_utils.TenantNotFoundError
4650

4751

52+
def auth_for_tenant(tenant_id, app=None):
53+
"""Gets an Auth Client instance scoped to the given tenant ID.
54+
55+
Args:
56+
tenant_id: A tenant ID string.
57+
app: An App instance (optional).
58+
59+
Returns:
60+
_AuthService: An _AuthService object.
61+
62+
Raises:
63+
ValueError: If the tenant ID is None, empty or not a string.
64+
"""
65+
tenant_mgt_service = _get_tenant_mgt_service(app)
66+
return tenant_mgt_service.auth_for_tenant(tenant_id)
67+
68+
4869
def get_tenant(tenant_id, app=None):
4970
"""Gets the tenant corresponding to the given ``tenant_id``.
5071
@@ -211,8 +232,25 @@ def __init__(self, app):
211232
credential = app.credential.get_credential()
212233
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
213234
base_url = '{0}/projects/{1}'.format(self.TENANT_MGT_URL, app.project_id)
235+
self.app = app
214236
self.client = _http_client.JsonHttpClient(
215237
credential=credential, base_url=base_url, headers={'X-Client-Version': version_header})
238+
self.tenant_clients = {}
239+
self.lock = threading.RLock()
240+
241+
def auth_for_tenant(self, tenant_id):
242+
"""Gets an Auth Client instance scoped to the given tenant ID."""
243+
if not isinstance(tenant_id, str) or not tenant_id:
244+
raise ValueError(
245+
'Invalid tenant ID: {0}. Tenant ID must be a non-empty string.'.format(tenant_id))
246+
247+
with self.lock:
248+
if tenant_id in self.tenant_clients:
249+
return self.tenant_clients[tenant_id]
250+
251+
client = auth._AuthService(self.app, tenant_id=tenant_id) # pylint: disable=protected-access
252+
self.tenant_clients[tenant_id] = client
253+
return client
216254

217255
def get_tenant(self, tenant_id):
218256
"""Gets the tenant corresponding to the given ``tenant_id``."""

0 commit comments

Comments
 (0)