Skip to content

Commit 1d817f5

Browse files
committed
Accomodate auth emulator behaviour in tests.
Where possible, tests are modified to account for the current behaviour in emulator mode (e.g., invalid or expired tokens or cookies still work). In one case, signer verification didn't account for padding being stripped in JWT tokens, and was fixed to do so.
1 parent f687f38 commit 1d817f5

File tree

2 files changed

+87
-30
lines changed

2 files changed

+87
-30
lines changed

firebase_admin/_auth_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def verify_id_token(self, id_token, check_revoked=False):
127127
raise _auth_utils.TenantIdMismatchError(
128128
'Invalid tenant ID: {0}'.format(token_tenant_id))
129129

130-
if not self.emulated and check_revoked:
130+
if check_revoked:
131131
self._check_jwt_revoked(verified_claims, _token_gen.RevokedIdTokenError, 'ID token')
132132
return verified_claims
133133

tests/test_token_gen.py

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,18 @@ def _merge_jwt_claims(defaults, overrides):
7676

7777
def verify_custom_token(custom_token, expected_claims, tenant_id=None):
7878
assert isinstance(custom_token, bytes)
79-
token = google.oauth2.id_token.verify_token(
80-
custom_token,
81-
testutils.MockRequest(200, MOCK_PUBLIC_CERTS),
82-
_token_gen.FIREBASE_AUDIENCE)
79+
expected_email = MOCK_SERVICE_ACCOUNT_EMAIL
80+
if _is_emulated():
81+
expected_email = _token_gen.AUTH_EMULATOR_EMAIL
82+
token = jwt.decode(custom_token, verify=False)
83+
else:
84+
token = google.oauth2.id_token.verify_token(
85+
custom_token,
86+
testutils.MockRequest(200, MOCK_PUBLIC_CERTS),
87+
_token_gen.FIREBASE_AUDIENCE)
8388
assert token['uid'] == MOCK_UID
84-
assert token['iss'] == MOCK_SERVICE_ACCOUNT_EMAIL
85-
assert token['sub'] == MOCK_SERVICE_ACCOUNT_EMAIL
89+
assert token['iss'] == expected_email
90+
assert token['sub'] == expected_email
8691
if tenant_id is None:
8792
assert 'tenant_id' not in token
8893
else:
@@ -141,7 +146,15 @@ def _overwrite_iam_request(app, request):
141146
client = auth._get_client(app)
142147
client._token_generator.request = request
143148

144-
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
149+
150+
def _is_emulated():
151+
emulator_host = os.getenv(EMULATOR_HOST_ENV_VAR, '')
152+
return emulator_host and '//' not in emulator_host
153+
154+
155+
# These fixtures are set to the default function scope as the emulator environment variable bleeds
156+
# over when in module scope.
157+
@pytest.fixture(params=[{'emulated': False}, {'emulated': True}])
145158
def auth_app(request):
146159
"""Returns an App initialized with a mock service account credential.
147160
@@ -157,7 +170,7 @@ def auth_app(request):
157170
firebase_admin.delete_app(app)
158171
monkeypatch.undo()
159172

160-
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
173+
@pytest.fixture(params=[{'emulated': False}, {'emulated': True}])
161174
def user_mgt_app(request):
162175
monkeypatch = testutils.new_monkeypatch()
163176
if request.param['emulated']:
@@ -230,6 +243,12 @@ def test_invalid_params(self, auth_app, values):
230243
auth.create_custom_token(user, claims, app=auth_app)
231244

232245
def test_noncert_credential(self, user_mgt_app):
246+
if _is_emulated():
247+
# Should work fine with the emulator, so do a condensed version of
248+
# test_sign_with_iam below.
249+
custom_token = auth.create_custom_token(MOCK_UID, app=user_mgt_app).decode()
250+
self._verify_signer(custom_token, _token_gen.AUTH_EMULATOR_EMAIL)
251+
return
233252
with pytest.raises(ValueError):
234253
auth.create_custom_token(MOCK_UID, app=user_mgt_app)
235254

@@ -304,7 +323,7 @@ def test_sign_with_discovery_failure(self):
304323
def _verify_signer(self, token, signer):
305324
segments = token.split('.')
306325
assert len(segments) == 3
307-
body = json.loads(base64.b64decode(segments[1]).decode())
326+
body = jwt.decode(token, verify=False)
308327
assert body['iss'] == signer
309328
assert body['sub'] == signer
310329

@@ -406,14 +425,23 @@ class TestVerifyIdToken:
406425
'BadFormatToken': 'foobar'
407426
}
408427

409-
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
410-
def test_valid_token(self, user_mgt_app, id_token):
411-
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
412-
claims = auth.verify_id_token(id_token, app=user_mgt_app)
428+
tokens_not_invalid_in_emulator = [
429+
'WrongKid',
430+
'FutureToken',
431+
'ExpiredToken'
432+
]
433+
434+
def _assert_valid_token(self, id_token, app):
435+
claims = auth.verify_id_token(id_token, app=app)
413436
assert claims['admin'] is True
414437
assert claims['uid'] == claims['sub']
415438
assert claims['firebase']['sign_in_provider'] == 'provider'
416439

440+
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
441+
def test_valid_token(self, user_mgt_app, id_token):
442+
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
443+
self._assert_valid_token(id_token, app=user_mgt_app)
444+
417445
def test_valid_token_with_tenant(self, user_mgt_app):
418446
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
419447
claims = auth.verify_id_token(TEST_ID_TOKEN_WITH_TENANT, app=user_mgt_app)
@@ -458,8 +486,12 @@ def test_invalid_arg(self, user_mgt_app, id_token):
458486
auth.verify_id_token(id_token, app=user_mgt_app)
459487
assert 'Illegal ID token provided' in str(excinfo.value)
460488

461-
@pytest.mark.parametrize('id_token', invalid_tokens.values(), ids=list(invalid_tokens))
462-
def test_invalid_token(self, user_mgt_app, id_token):
489+
@pytest.mark.parametrize('id_token_key', list(invalid_tokens))
490+
def test_invalid_token(self, user_mgt_app, id_token_key):
491+
id_token = self.invalid_tokens[id_token_key]
492+
if _is_emulated() and id_token_key in self.tokens_not_invalid_in_emulator:
493+
self._assert_valid_token(id_token, user_mgt_app)
494+
return
463495
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
464496
with pytest.raises(auth.InvalidIdTokenError) as excinfo:
465497
auth.verify_id_token(id_token, app=user_mgt_app)
@@ -469,6 +501,9 @@ def test_invalid_token(self, user_mgt_app, id_token):
469501
def test_expired_token(self, user_mgt_app):
470502
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
471503
id_token = self.invalid_tokens['ExpiredToken']
504+
if _is_emulated():
505+
self._assert_valid_token(id_token, user_mgt_app)
506+
return
472507
with pytest.raises(auth.ExpiredIdTokenError) as excinfo:
473508
auth.verify_id_token(id_token, app=user_mgt_app)
474509
assert isinstance(excinfo.value, auth.InvalidIdTokenError)
@@ -506,6 +541,10 @@ def test_custom_token(self, auth_app):
506541

507542
def test_certificate_request_failure(self, user_mgt_app):
508543
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
544+
if _is_emulated():
545+
# Shouldn't fetch certificates in emulator mode.
546+
self._assert_valid_token(TEST_ID_TOKEN, app=user_mgt_app)
547+
return
509548
with pytest.raises(auth.CertificateFetchError) as excinfo:
510549
auth.verify_id_token(TEST_ID_TOKEN, app=user_mgt_app)
511550
assert 'Could not fetch certificates' in str(excinfo.value)
@@ -540,20 +579,27 @@ class TestVerifySessionCookie:
540579
'IDToken': TEST_ID_TOKEN,
541580
}
542581

582+
cookies_not_invalid_in_emulator = [
583+
'WrongKid',
584+
'FutureCookie',
585+
'ExpiredCookie'
586+
]
587+
588+
def _assert_valid_cookie(self, cookie, app, check_revoked=False):
589+
claims = auth.verify_session_cookie(cookie, app=app, check_revoked=check_revoked)
590+
assert claims['admin'] is True
591+
assert claims['uid'] == claims['sub']
592+
543593
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
544594
def test_valid_cookie(self, user_mgt_app, cookie):
545595
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
546-
claims = auth.verify_session_cookie(cookie, app=user_mgt_app)
547-
assert claims['admin'] is True
548-
assert claims['uid'] == claims['sub']
596+
self._assert_valid_cookie(cookie, user_mgt_app)
549597

550598
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
551599
def test_valid_cookie_check_revoked(self, user_mgt_app, cookie):
552600
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
553601
_instrument_user_manager(user_mgt_app, 200, MOCK_GET_USER_RESPONSE)
554-
claims = auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=True)
555-
assert claims['admin'] is True
556-
assert claims['uid'] == claims['sub']
602+
self._assert_valid_cookie(cookie, app=user_mgt_app, check_revoked=True)
557603

558604
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
559605
def test_revoked_cookie_check_revoked(self, user_mgt_app, revoked_tokens, cookie):
@@ -567,9 +613,7 @@ def test_revoked_cookie_check_revoked(self, user_mgt_app, revoked_tokens, cookie
567613
def test_revoked_cookie_does_not_check_revoked(self, user_mgt_app, revoked_tokens, cookie):
568614
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
569615
_instrument_user_manager(user_mgt_app, 200, revoked_tokens)
570-
claims = auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False)
571-
assert claims['admin'] is True
572-
assert claims['uid'] == claims['sub']
616+
self._assert_valid_cookie(cookie, app=user_mgt_app, check_revoked=False)
573617

574618
@pytest.mark.parametrize('cookie', INVALID_JWT_ARGS.values(), ids=list(INVALID_JWT_ARGS))
575619
def test_invalid_args(self, user_mgt_app, cookie):
@@ -578,8 +622,12 @@ def test_invalid_args(self, user_mgt_app, cookie):
578622
auth.verify_session_cookie(cookie, app=user_mgt_app)
579623
assert 'Illegal session cookie provided' in str(excinfo.value)
580624

581-
@pytest.mark.parametrize('cookie', invalid_cookies.values(), ids=list(invalid_cookies))
582-
def test_invalid_cookie(self, user_mgt_app, cookie):
625+
@pytest.mark.parametrize('cookie_key', list(invalid_cookies))
626+
def test_invalid_cookie(self, user_mgt_app, cookie_key):
627+
cookie = self.invalid_cookies[cookie_key]
628+
if _is_emulated() and cookie_key in self.cookies_not_invalid_in_emulator:
629+
self._assert_valid_cookie(cookie, user_mgt_app)
630+
return
583631
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
584632
with pytest.raises(auth.InvalidSessionCookieError) as excinfo:
585633
auth.verify_session_cookie(cookie, app=user_mgt_app)
@@ -589,6 +637,9 @@ def test_invalid_cookie(self, user_mgt_app, cookie):
589637
def test_expired_cookie(self, user_mgt_app):
590638
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
591639
cookie = self.invalid_cookies['ExpiredCookie']
640+
if _is_emulated():
641+
self._assert_valid_cookie(cookie, user_mgt_app)
642+
return
592643
with pytest.raises(auth.ExpiredSessionCookieError) as excinfo:
593644
auth.verify_session_cookie(cookie, app=user_mgt_app)
594645
assert isinstance(excinfo.value, auth.InvalidSessionCookieError)
@@ -621,6 +672,10 @@ def test_custom_token(self, auth_app):
621672

622673
def test_certificate_request_failure(self, user_mgt_app):
623674
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
675+
if _is_emulated():
676+
# Shouldn't fetch certificates in emulator mode.
677+
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
678+
return
624679
with pytest.raises(auth.CertificateFetchError) as excinfo:
625680
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
626681
assert 'Could not fetch certificates' in str(excinfo.value)
@@ -637,9 +692,11 @@ def test_certificate_caching(self, user_mgt_app, httpserver):
637692
verifier.cookie_verifier.cert_url = httpserver.url
638693
verifier.id_token_verifier.cert_url = httpserver.url
639694
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
640-
assert len(httpserver.requests) == 1
695+
# No requests should be made in emulated mode
696+
request_count = 0 if _is_emulated() else 1
697+
assert len(httpserver.requests) == request_count
641698
# Subsequent requests should not fetch certs from the server
642699
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
643-
assert len(httpserver.requests) == 1
700+
assert len(httpserver.requests) == request_count
644701
verifier.verify_id_token(TEST_ID_TOKEN)
645-
assert len(httpserver.requests) == 1
702+
assert len(httpserver.requests) == request_count

0 commit comments

Comments
 (0)