Skip to content

Commit b7601f6

Browse files
committed
Fix or skip tests that fail with auth emulator
1 parent 6d09431 commit b7601f6

File tree

2 files changed

+112
-24
lines changed

2 files changed

+112
-24
lines changed

firebase_admin/_auth_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def verify_id_token(self, id_token, check_revoked=False):
126126
raise_auth_utils.TenantIdMismatchError(
127127
'Invalid tenant ID: {0}'.format(token_tenant_id))
128128

129-
ifnotself.emulatedandcheck_revoked:
129+
ifcheck_revoked:
130130
self._check_jwt_revoked(verified_claims, _token_gen.RevokedIdTokenError, 'ID token')
131131
returnverified_claims
132132

tests/test_token_gen.py

+111-23
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,19 @@ def _merge_jwt_claims(defaults, overrides):
7676

7777
defverify_custom_token(custom_token, expected_claims, tenant_id=None):
7878
assertisinstance(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+
if_is_emulated():
80+
token=jwt.decode(custom_token, verify=False)
81+
else:
82+
token=google.oauth2.id_token.verify_token(
83+
custom_token,
84+
testutils.MockRequest(200, MOCK_PUBLIC_CERTS),
85+
_token_gen.FIREBASE_AUDIENCE)
8386
asserttoken['uid'] ==MOCK_UID
84-
asserttoken['iss'] ==MOCK_SERVICE_ACCOUNT_EMAIL
85-
asserttoken['sub'] ==MOCK_SERVICE_ACCOUNT_EMAIL
87+
expected_email=MOCK_SERVICE_ACCOUNT_EMAIL
88+
if_is_emulated():
89+
expected_email=_token_gen.AUTH_EMULATOR_EMAIL
90+
asserttoken['iss'] ==expected_email
91+
asserttoken['sub'] ==expected_email
8692
iftenant_idisNone:
8793
assert'tenant_id'notintoken
8894
else:
@@ -141,7 +147,13 @@ def _overwrite_iam_request(app, request):
141147
client=auth._get_client(app)
142148
client._token_generator.request=request
143149

144-
@pytest.fixture(scope='module', params=[{'emulated': False}, {'emulated': True}])
150+
151+
def_is_emulated():
152+
emulator_host=os.getenv(EMULATOR_HOST_ENV_VAR, '')
153+
returnemulator_hostand'//'notinemulator_host
154+
155+
156+
@pytest.fixture(scope='function', params=[{'emulated': False}, {'emulated': True}])
145157
defauth_app(request):
146158
"""Returns an App initialized with a mock service account credential.
147159
@@ -217,6 +229,12 @@ class TestCreateCustomToken:
217229
'MultipleReservedClaims': (MOCK_UID, {'sub':'1234', 'aud':'foo'}, ValueError),
218230
}
219231

232+
def_check_emulated_token(self, uid, app):
233+
# Should work fine with the emulator, so do a condensed version of
234+
# test_sign_with_iam above.
235+
custom_token=auth.create_custom_token(uid, app=app).decode()
236+
self._verify_signer(custom_token, _token_gen.AUTH_EMULATOR_EMAIL)
237+
220238
@pytest.mark.parametrize('values', valid_args.values(), ids=list(valid_args))
221239
deftest_valid_params(self, auth_app, values):
222240
user, claims=values
@@ -230,20 +248,27 @@ def test_invalid_params(self, auth_app, values):
230248
auth.create_custom_token(user, claims, app=auth_app)
231249

232250
deftest_noncert_credential(self, user_mgt_app):
251+
if_is_emulated():
252+
self._check_emulated_token(MOCK_UID, user_mgt_app)
253+
return
233254
withpytest.raises(ValueError):
234255
auth.create_custom_token(MOCK_UID, app=user_mgt_app)
235256

236257
deftest_sign_with_iam(self):
237-
options= {'serviceAccountId': 'test-service-account', 'projectId': 'mock-project-id'}
258+
signer=_token_gen.AUTH_EMULATOR_EMAILif_is_emulated() else'test-service-account'
259+
options= {'serviceAccountId': signer, 'projectId': 'mock-project-id'}
238260
app=firebase_admin.initialize_app(
239261
testutils.MockCredential(), name='iam-signer-app', options=options)
240262
try:
241263
signature=base64.b64encode(b'test').decode()
242264
iam_resp='{{"signature": "{0}"}}'.format(signature)
243265
_overwrite_iam_request(app, testutils.MockRequest(200, iam_resp))
244266
custom_token=auth.create_custom_token(MOCK_UID, app=app).decode()
245-
assertcustom_token.endswith('.'+signature.rstrip('='))
246-
self._verify_signer(custom_token, 'test-service-account')
267+
if_is_emulated():
268+
assertcustom_token.endswith('.')
269+
else:
270+
assertcustom_token.endswith('.'+signature.rstrip('='))
271+
self._verify_signer(custom_token, signer)
247272
finally:
248273
firebase_admin.delete_app(app)
249274

@@ -254,7 +279,10 @@ def test_sign_with_iam_error(self):
254279
try:
255280
iam_resp='{"error": {"code": 403, "message": "test error"}}'
256281
_overwrite_iam_request(app, testutils.MockRequest(403, iam_resp))
257-
withpytest.raises(auth.TokenSignError) asexcinfo:
282+
if_is_emulated():
283+
self._check_emulated_token(MOCK_UID, app)
284+
return
285+
withpytest.raises((ValueError, auth.TokenSignError)) asexcinfo:
258286
auth.create_custom_token(MOCK_UID, app=app)
259287
error=excinfo.value
260288
asserterror.code==exceptions.UNKNOWN
@@ -264,7 +292,8 @@ def test_sign_with_iam_error(self):
264292
firebase_admin.delete_app(app)
265293

266294
deftest_sign_with_discovered_service_account(self):
267-
request=testutils.MockRequest(200, 'discovered-service-account')
295+
signer=_token_gen.AUTH_EMULATOR_EMAILif_is_emulated() else'discovered-service-account'
296+
request=testutils.MockRequest(200, signer)
268297
options= {'projectId': 'mock-project-id'}
269298
app=firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app',
270299
options=options)
@@ -279,10 +308,17 @@ def test_sign_with_discovered_service_account(self):
279308
request.response=testutils.MockResponse(
280309
200, '{{"signature": "{0}"}}'.format(signature))
281310
custom_token=auth.create_custom_token(MOCK_UID, app=app).decode()
282-
assertcustom_token.endswith('.'+signature.rstrip('='))
283-
self._verify_signer(custom_token, 'discovered-service-account')
284-
assertlen(request.log) ==2
285-
assertrequest.log[0][1]['headers'] == {'Metadata-Flavor': 'Google'}
311+
if_is_emulated():
312+
# No signature from the emulator
313+
assertcustom_token.endswith('.')
314+
else:
315+
assertcustom_token.endswith('.'+signature.rstrip('='))
316+
if_is_emulated():
317+
# No requests will be made with the emulator
318+
assertlen(request.log) ==0
319+
else:
320+
assertlen(request.log) ==2
321+
assertrequest.log[0][1]['headers'] == {'Metadata-Flavor': 'Google'}
286322
finally:
287323
firebase_admin.delete_app(app)
288324

@@ -293,6 +329,9 @@ def test_sign_with_discovery_failure(self):
293329
options=options)
294330
try:
295331
_overwrite_iam_request(app, request)
332+
if_is_emulated():
333+
self._check_emulated_token(MOCK_UID, app)
334+
return
296335
withpytest.raises(ValueError) asexcinfo:
297336
auth.create_custom_token(MOCK_UID, app=app)
298337
assertstr(excinfo.value).startswith('Failed to determine service account: test error')
@@ -304,6 +343,14 @@ def test_sign_with_discovery_failure(self):
304343
def_verify_signer(self, token, signer):
305344
segments=token.split('.')
306345
assertlen(segments) ==3
346+
# See https://github.com/googleapis/google-auth-library-python/pull/324
347+
# and also RFC 7515 which is the basis of that PR:
348+
# JWT segments don't have padding and base64 decoding might fail.
349+
#
350+
# Workaround from https://stackoverflow.com/a/9807138/2072269
351+
missing_padding=len(segments[1]) %4
352+
ifmissing_padding:
353+
segments[1] +='='* (4-missing_padding)
307354
body=json.loads(base64.b64decode(segments[1]).decode())
308355
assertbody['iss'] ==signer
309356
assertbody['sub'] ==signer
@@ -406,6 +453,12 @@ class TestVerifyIdToken:
406453
'BadFormatToken': 'foobar'
407454
}
408455

456+
tokens_not_invalid_in_emulator= [
457+
'WrongKid',
458+
'FutureToken',
459+
'ExpiredToken'
460+
]
461+
409462
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
410463
deftest_valid_token(self, user_mgt_app, id_token):
411464
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
@@ -458,15 +511,25 @@ def test_invalid_arg(self, user_mgt_app, id_token):
458511
auth.verify_id_token(id_token, app=user_mgt_app)
459512
assert'Illegal ID token provided'instr(excinfo.value)
460513

461-
@pytest.mark.parametrize('id_token', invalid_tokens.values(), ids=list(invalid_tokens))
462-
deftest_invalid_token(self, user_mgt_app, id_token):
514+
@pytest.mark.parametrize('id_token_key', list(invalid_tokens))
515+
deftest_invalid_token(self, user_mgt_app, id_token_key):
516+
id_token=self.invalid_tokens[id_token_key]
517+
if_is_emulated() andid_token_keyinself.tokens_not_invalid_in_emulator:
518+
# These tokens won't be invalid when using the emulator
519+
claims=auth.verify_id_token(id_token, app=user_mgt_app)
520+
assertclaims['admin'] isTrue
521+
assertclaims['uid'] ==claims['sub']
522+
return
463523
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
464524
withpytest.raises(auth.InvalidIdTokenError) asexcinfo:
465525
auth.verify_id_token(id_token, app=user_mgt_app)
466526
assertisinstance(excinfo.value, exceptions.InvalidArgumentError)
467527
assertexcinfo.value.http_responseisNone
468528

469529
deftest_expired_token(self, user_mgt_app):
530+
if_is_emulated():
531+
pytest.skip('Not supported in emulator mode: Token expiration')
532+
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
470533
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
471534
id_token=self.invalid_tokens['ExpiredToken']
472535
withpytest.raises(auth.ExpiredIdTokenError) asexcinfo:
@@ -506,6 +569,10 @@ def test_custom_token(self, auth_app):
506569

507570
deftest_certificate_request_failure(self, user_mgt_app):
508571
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
572+
if_is_emulated():
573+
# Shouldn't fetch certificates in emulator mode.
574+
auth.verify_id_token(TEST_ID_TOKEN, app=user_mgt_app)
575+
return
509576
withpytest.raises(auth.CertificateFetchError) asexcinfo:
510577
auth.verify_id_token(TEST_ID_TOKEN, app=user_mgt_app)
511578
assert'Could not fetch certificates'instr(excinfo.value)
@@ -540,6 +607,12 @@ class TestVerifySessionCookie:
540607
'IDToken': TEST_ID_TOKEN,
541608
}
542609

610+
cookies_not_invalid_in_emulator= [
611+
'WrongKid',
612+
'FutureCookie',
613+
'ExpiredCookie'
614+
]
615+
543616
@pytest.mark.parametrize('cookie', valid_cookies.values(), ids=list(valid_cookies))
544617
deftest_valid_cookie(self, user_mgt_app, cookie):
545618
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
@@ -578,15 +651,24 @@ def test_invalid_args(self, user_mgt_app, cookie):
578651
auth.verify_session_cookie(cookie, app=user_mgt_app)
579652
assert'Illegal session cookie provided'instr(excinfo.value)
580653

581-
@pytest.mark.parametrize('cookie', invalid_cookies.values(), ids=list(invalid_cookies))
582-
deftest_invalid_cookie(self, user_mgt_app, cookie):
654+
@pytest.mark.parametrize('cookie_key', list(invalid_cookies))
655+
deftest_invalid_cookie(self, user_mgt_app, cookie_key):
656+
cookie=self.invalid_cookies[cookie_key]
657+
if_is_emulated() andcookie_keyinself.cookies_not_invalid_in_emulator:
658+
# These cookies won't be invalid when using the emulator
659+
claims=auth.verify_session_cookie(cookie, app=user_mgt_app)
660+
assertclaims['admin'] isTrue
661+
assertclaims['uid'] ==claims['sub']
662+
return
583663
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
584664
withpytest.raises(auth.InvalidSessionCookieError) asexcinfo:
585665
auth.verify_session_cookie(cookie, app=user_mgt_app)
586666
assertisinstance(excinfo.value, exceptions.InvalidArgumentError)
587667
assertexcinfo.value.http_responseisNone
588668

589669
deftest_expired_cookie(self, user_mgt_app):
670+
if_is_emulated():
671+
pytest.skip('Not supported in emulator mode: Cookie expiration')
590672
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
591673
cookie=self.invalid_cookies['ExpiredCookie']
592674
withpytest.raises(auth.ExpiredSessionCookieError) asexcinfo:
@@ -621,6 +703,10 @@ def test_custom_token(self, auth_app):
621703

622704
deftest_certificate_request_failure(self, user_mgt_app):
623705
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
706+
if_is_emulated():
707+
# Shouldn't fetch certificates in emulator mode.
708+
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
709+
return
624710
withpytest.raises(auth.CertificateFetchError) asexcinfo:
625711
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
626712
assert'Could not fetch certificates'instr(excinfo.value)
@@ -637,9 +723,11 @@ def test_certificate_caching(self, user_mgt_app, httpserver):
637723
verifier.cookie_verifier.cert_url=httpserver.url
638724
verifier.id_token_verifier.cert_url=httpserver.url
639725
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
640-
assertlen(httpserver.requests) ==1
726+
# No requests should be made in emulated mode
727+
request_count=0if_is_emulated() else1
728+
assertlen(httpserver.requests) ==request_count
641729
# Subsequent requests should not fetch certs from the server
642730
verifier.verify_session_cookie(TEST_SESSION_COOKIE)
643-
assertlen(httpserver.requests) ==1
731+
assertlen(httpserver.requests) ==request_count
644732
verifier.verify_id_token(TEST_ID_TOKEN)
645-
assertlen(httpserver.requests) ==1
733+
assertlen(httpserver.requests) ==request_count

0 commit comments

Comments
 (0)
close