8000 Tenant-aware ID token verification support (#432) · firebase/firebase-admin-python@19628b7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 19628b7

Browse files
authored
Tenant-aware ID token verification support (#432)
* Tenant-aware ID token verification support * Extended InvalidArgumentError in TenantIdMismatchError * Fixing lint errors
1 parent fc355af commit 19628b7

File tree

5 files changed

+59
-1
lines changed

5 files changed

+59
-1
lines changed

firebase_admin/_auth_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ def __init__(self, message, cause=None, http_response=None):
300300
exceptions.NotFoundError.__init__(self, message, cause, http_response)
301301

302302

303+
class TenantIdMismatchError(exceptions.InvalidArgumentError):
304+
"""Missing or invalid tenant ID field in the given JWT."""
305+
306+
def __init__(self, message):
307+
exceptions.InvalidArgumentError.__init__(self, message)
308+
309+
303310
_CODE_TO_EXC_TYPE = {
304311
'DUPLICATE_EMAIL': EmailAlreadyExistsError,
305312
'DUPLICATE_LOCAL_ID': UidAlreadyExistsError,

firebase_admin/auth.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,12 +559,19 @@ def create_custom_token(self, uid, developer_claims=None):
559559
return self._token_generator.create_custom_token(uid, developer_claims)
560560

561561
def verify_id_token(self, id_token, check_revoked=False):
562+
"""Verifies the signature and data for the provided ID token."""
562563
if not isinstance(check_revoked, bool):
563564
# guard against accidental wrong assignment.
564565
raise ValueError('Illegal check_revoked argument. Argument must be of type '
565566
' bool, but given "{0}".'.format(type(check_revoked)))
566567

567568
verified_claims = self._token_verifier.verify_id_token(id_token)
569+
if self.tenant_id:
570+
token_tenant_id = verified_claims.get('firebase', {}).get('tenant')
571+
if self.tenant_id != token_tenant_id:
572+
raise _auth_utils.TenantIdMismatchError(
573+
'Invalid tenant ID: {0}'.format(token_tenant_id))
574+
568575
if check_revoked:
569576
self._check_jwt_revoked(verified_claims, RevokedIdTokenError, 'ID token')
570577
return verified_claims

firebase_admin/tenant_mgt.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
__all__ = [
3737
'ListTenantsPage',
3838
'Tenant',
39+
'TenantIdMismatchError',
3940
'TenantNotFoundError',
4041

4142
'auth_for_tenant',
@@ -46,6 +47,8 @@
4647
'update_tenant',
4748
]
4849

50+
51+
TenantIdMismatchError = _auth_utils.TenantIdMismatchError
4952
TenantNotFoundError = _auth_utils.TenantNotFoundError
5053

5154

tests/test_tenant_mgt.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from firebase_admin import exceptions
2525
from firebase_admin import tenant_mgt
2626
from tests import testutils
27+
from tests import test_token_gen
2728

2829

2930
GET_TENANT_RESPONSE = """{
@@ -696,7 +697,6 @@ def test_tenant_not_found(self, tenant_mgt_app):
696697
assert excinfo.value.http_response is not None
697698
assert excinfo.value.cause is not None
698699

699-
700700
def _assert_request(self, recorder, want_url, want_body):
701701
assert len(recorder) == 1
702702
req = recorder[0]
@@ -706,6 +706,31 @@ def _assert_request(self, recorder, want_url, want_body):
706706
assert body == want_body
707707

708708

709+
class TestVerifyIdToken:
710+
711+
def test_valid_token(self, tenant_mgt_app):
712+
client = tenant_mgt.auth_for_tenant('test-tenant', app=tenant_mgt_app)
713+
client._token_verifier.request = test_token_gen.MOCK_REQUEST
714+
715+
claims = client.verify_id_token(test_token_gen.TEST_ID_TOKEN_WITH_TENANT)
716+
717+
assert claims['admin'] is True
718+
assert claims['uid'] == claims['sub']
719+
assert claims['firebase']['tenant'] == 'test-tenant'
720+
721+
def test_invalid_tenant_id(self, tenant_mgt_app):
722+
client = tenant_mgt.auth_for_tenant('other-tenant', app=tenant_mgt_app)
723+
client._token_verifier.request = test_token_gen.MOCK_REQUEST
724+
725+
with pytest.raises(tenant_mgt.TenantIdMismatchError) as excinfo:
726+
client.verify_id_token(test_token_gen.TEST_ID_TOKEN_WITH_TENANT)
727+
728+
assert 'Invalid tenant ID: test-tenant' in str(excinfo.value)
729+
assert isinstance(excinfo.value, exceptions.InvalidArgumentError)
730+
assert excinfo.value.cause is None
731+
assert excinfo.value.http_response is None
732+
733+
709734
def _assert_tenant(tenant, tenant_id='tenant-id'):
710735
assert isinstance(tenant, tenant_mgt.Tenant)
711736
assert tenant.tenant_id == tenant_id

tests/test_token_gen.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def _get_id_token(payload_overrides=None, header_overrides=None):
9494
'exp': int(time.time()) + 3600,
9595
'sub': '1234567890',
9696
'admin': True,
97+
'firebase': {
98+
'sign_in_provider': 'provider',
99+
},
97100
}
98101
if header_overrides:
99102
headers = _merge_jwt_claims(headers, header_overrides)
@@ -346,6 +349,11 @@ def test_unexpected_response(self, user_mgt_app):
346349

347350
MOCK_GET_USER_RESPONSE = testutils.resource('get_user.json')
348351
TEST_ID_TOKEN = _get_id_token()
352+
TEST_ID_TOKEN_WITH_TENANT = _get_id_token({
353+
'firebase': {
354+
'tenant': 'test-tenant',
355+
}
356+
})
349357
TEST_SESSION_COOKIE = _get_session_cookie()
350358

351359

@@ -380,6 +388,14 @@ def test_valid_token(self, user_mgt_app, id_token):
380388
claims = auth.verify_id_token(id_token, app=user_mgt_app)
381389
assert claims['admin'] is True
382390
assert claims['uid'] == claims['sub']
391+
assert claims['firebase']['sign_in_provider'] == 'provider'
392+
393+
def test_valid_token_with_tenant(self, user_mgt_app):
394+
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
395+
claims = auth.verify_id_token(TEST_ID_TOKEN_WITH_TENANT, app=user_mgt_app)
396+
assert claims['admin'] is True
397+
assert claims['uid'] == claims['sub']
398+
assert claims['firebase']['tenant'] == 'test-tenant'
383399

384400
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
385401
def test_valid_token_check_revoked(self, user_mgt_app, id_token):

0 commit comments

Comments
 (0)
0