Skip to content

Commit 1e65c84

Browse files
committed
Accommodate 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). Fixtures were changed to function scope to avoid problems caused by overlap when some fixtures being in emulator mode and some in normal mode concurrently.
1 parent b70cd9f commit 1e65c84

File tree

3 files changed

+91
-32
lines changed

3 files changed

+91
-32
lines changed

firebase_admin/_auth_client.py

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

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

tests/test_token_gen.py

Lines changed: 88 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,24 @@ 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_accepted_in_emulator = [
429+
'NoKid',
430+
'WrongKid',
431+
'FutureToken',
432+
'ExpiredToken'
433+
]
434+
435+
def _assert_valid_token(self, id_token, app):
436+
claims = auth.verify_id_token(id_token, app=app)
413437
assert claims['admin'] is True
414438
assert claims['uid'] == claims['sub']
415439
assert claims['firebase']['sign_in_provider'] == 'provider'
416440

441+
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
442+
def test_valid_token(self, user_mgt_app, id_token):
443+
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
444+
self._assert_valid_token(id_token, app=user_mgt_app)
445+
417446
def test_valid_token_with_tenant(self, user_mgt_app):
418447
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
419448
claims = auth.verify_id_token(TEST_ID_TOKEN_WITH_TENANT, app=user_mgt_app)
@@ -458,8 +487,12 @@ def test_invalid_arg(self, user_mgt_app, id_token):
458487
auth.verify_id_token(id_token, app=user_mgt_app)
459488
assert 'Illegal ID token provided' in str(excinfo.value)
460489

461-
@pytest.mark.parametrize('id_token', invalid_tokens.values(), ids=list(invalid_tokens))
462-
def test_invalid_token(self, user_mgt_app, id_token):
490+
@pytest.mark.parametrize('id_token_key', list(invalid_tokens))
491+
def test_invalid_token(self, user_mgt_app, id_token_key):
492+
id_token = self.invalid_tokens[id_token_key]
493+
if _is_emulated() and id_token_key in self.tokens_accepted_in_emulator:
494+
self._assert_valid_token(id_token, user_mgt_app)
495+
return
463496
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
464497
with pytest.raises(auth.InvalidIdTokenError) as excinfo:
465498
auth.verify_id_token(id_token, app=user_mgt_app)
@@ -469,6 +502,9 @@ def test_invalid_token(self, user_mgt_app, id_token):
469502
def test_expired_token(self, user_mgt_app):
470503
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
471504
id_token = self.invalid_tokens['ExpiredToken']
505+
if _is_emulated():
506+
self._assert_valid_token(id_token, user_mgt_app)
507+
return
472508
with pytest.raises(auth.ExpiredIdTokenError) as excinfo:
473509
auth.verify_id_token(id_token, app=user_mgt_app)
474510
assert isinstance(excinfo.value, auth.InvalidIdTokenError)
@@ -506,6 +542,10 @@ def test_custom_token(self, auth_app):
506542

507543
def test_certificate_request_failure(self, user_mgt_app):
508544
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
545+
if _is_emulated():
546+
# Shouldn't fetch certificates in emulator mode.
547+
self._assert_valid_token(TEST_ID_TOKEN, app=user_mgt_app)
548+
return
509549
with pytest.raises(auth.CertificateFetchError) as excinfo:
510550
auth.verify_id_token(TEST_ID_TOKEN, app=user_mgt_app)
511551
assert 'Could not fetch certificates' in str(excinfo.value)
@@ -540,20 +580,28 @@ class TestVerifySessionCookie:
540580
'IDToken': TEST_ID_TOKEN,
541581
}
542582

583+
cookies_accepted_in_emulator = [
584+
'NoKid',
585+
'WrongKid',
586+
'FutureCookie',
587+
'ExpiredCookie'
588+
]
589+
590+
def _assert_valid_cookie(self, cookie, app, check_revoked=False):
591+
claims = auth.verify_session_cookie(cookie, app=app, check_revoked=check_revoked)
592+
assert claims['admin'] is True
593+
assert claims['uid'] == claims['sub']
594+
543595
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
544596
def test_valid_cookie(self, user_mgt_app, cookie):
545597
_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']
598+
self._assert_valid_cookie(cookie, user_mgt_app)
549599

550600
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
551601
def test_valid_cookie_check_revoked(self, user_mgt_app, cookie):
552602
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
553603
_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']
604+
self._assert_valid_cookie(cookie, app=user_mgt_app, check_revoked=True)
557605

558606
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
559607
def test_revoked_cookie_check_revoked(self, user_mgt_app, revoked_tokens, cookie):
@@ -567,9 +615,7 @@ def test_revoked_cookie_check_revoked(self, user_mgt_app, revoked_tokens, cookie
567615
def test_revoked_cookie_does_not_check_revoked(self, user_mgt_app, revoked_tokens, cookie):
568616
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
569617
_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']
618+
self._assert_valid_cookie(cookie, app=user_mgt_app, check_revoked=False)
573619

574620
@pytest.mark.parametrize('cookie', INVALID_JWT_ARGS.values(), ids=list(INVALID_JWT_ARGS))
575621
def test_invalid_args(self, user_mgt_app, cookie):
@@ -578,8 +624,12 @@ def test_invalid_args(self, user_mgt_app, cookie):
578624
auth.verify_session_cookie(cookie, app=user_mgt_app)
579625
assert 'Illegal session cookie provided' in str(excinfo.value)
580626

581-
@pytest.mark.parametrize('cookie', invalid_cookies.values(), ids=list(invalid_cookies))
582-
def test_invalid_cookie(self, user_mgt_app, cookie):
627+
@pytest.mark.parametrize('cookie_key', list(invalid_cookies))
628+
def test_invalid_cookie(self, user_mgt_app, cookie_key):
629+
cookie = self.invalid_cookies[cookie_key]
630+
if _is_emulated() and cookie_key in self.cookies_accepted_in_emulator:
631+
self._assert_valid_cookie(cookie, user_mgt_app)
632+
return
583633
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
584634
with pytest.raises(auth.InvalidSessionCookieError) as excinfo:
585635
auth.verify_session_cookie(cookie, app=user_mgt_app)
@@ -589,6 +639,9 @@ def test_invalid_cookie(self, user_mgt_app, cookie):
589639
def test_expired_cookie(self, user_mgt_app):
590640
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
591641
cookie = self.invalid_cookies['ExpiredCookie']
642+
if _is_emulated():
643+
self._assert_valid_cookie(cookie, user_mgt_app)
644+
return
592645
with pytest.raises(auth.ExpiredSessionCookieError) as excinfo:
593646
auth.verify_session_cookie(cookie, app=user_mgt_app)
594647
assert isinstance(excinfo.value, auth.InvalidSessionCookieError)
@@ -621,6 +674,10 @@ def test_custom_token(self, auth_app):
621674

622675
def test_certificate_request_failure(self, user_mgt_app):
623676
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
677+
if _is_emulated():
678+
# Shouldn't fetch certificates in emulator mode.
679+
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
680+
return
624681
with pytest.raises(auth.CertificateFetchError) as excinfo:
625682
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
626683
assert 'Could not fetch certificates' in str(excinfo.value)
@@ -637,9 +694,11 @@ def test_certificate_caching(self, user_mgt_app, httpserver):
637694
verifier.cookie_verifier.cert_url = httpserver.url
638695
verifier.id_token_verifier.cert_url = httpserver.url
639696
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
640-
assert len(httpserver.requests) == 1
697+
# No requests should be made in emulated mode
698+
request_count = 0 if _is_emulated() else 1
699+
assert len(httpserver.requests) == request_count
641700
# Subsequent requests should not fetch certs from the server
642701
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
643-
assert len(httpserver.requests) == 1
702+
assert len(httpserver.requests) == request_count
644703
verifier.verify_id_token(TEST_ID_TOKEN)
645-
assert len(httpserver.requests) == 1
704+
assert len(httpserver.requests) == request_count

tests/test_user_mgt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
'PREFIX': ID_TOOLKIT_URL + URL_PROJECT_SUFFIX,
6363
}
6464

65-
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
65+
@pytest.fixture(params=[{'emulated': False}, {'emulated': True}])
6666
def user_mgt_app(request):
6767
monkeypatch = testutils.new_monkeypatch()
6868
if request.param['emulated']:
@@ -75,7 +75,7 @@ def user_mgt_app(request):
7575
firebase_admin.delete_app(app)
7676
monkeypatch.undo()
7777

78-
@pytest.fixture(scope='module')
78+
@pytest.fixture
7979
def user_mgt_app_with_timeout():
8080
app = firebase_admin.initialize_app(
8181
testutils.MockCredential(),

0 commit comments

Comments
 (0)