Skip to content

Commit 0720435

Browse files
committed
WIP hack integration tests for auth emulator
1 parent 3b930f0 commit 0720435

File tree

6 files changed

+90
-19
lines changed

6 files changed

+90
-19
lines changed

.github/workflows/ci.yml

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
run: |
3131
npm install -g firebase-tools
3232
firebase emulators:exec --only database --project fake-project-id 'pytest integration/test_db.py'
33+
echo mock-api-key > apikey.txt
34+
firebase emulators:exec --only auth --project mock-project-id 'pytest integration/test_auth.py --cert tests/data/service_account.json --apikey apikey.txt'
3335
3436
lint:
3537
runs-on: ubuntu-latest

firebase_admin/_auth_client.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
"""Firebase auth client sub module."""
1616

17-
importos
1817
importtime
1918

2019
importfirebase_admin
@@ -27,9 +26,6 @@
2726
fromfirebase_adminimport_user_mgt
2827
fromfirebase_adminimport_utils
2928

30-
_EMULATOR_HOST_ENV_VAR='FIREBASE_AUTH_EMULATOR_HOST'
31-
_DEFAULT_AUTH_URL='https://identitytoolkit.googleapis.com'
32-
3329
classClient:
3430
"""Firebase Authentication client scoped to a specific tenant."""
3531

@@ -44,19 +40,17 @@ def __init__(self, app, tenant_id=None):
4440
version_header='Python/Admin/{0}'.format(firebase_admin.__version__)
4541
# Non-default endpoint URLs for emulator support are set in this dict later.
4642
endpoint_urls= {}
43+
self.emulated=False
4744

4845
# If an emulator is present, check that the given value matches the expected format and set
4946
# endpoint URLs to use the emulator. Additionally, use a fake credential.
50-
emulator_host=os.environ.get(_EMULATOR_HOST_ENV_VAR)
47+
emulator_host=_auth_utils.get_emulator_host()
5148
ifemulator_host:
52-
if'//'inemulator_host:
53-
raiseValueError(
54-
'Invalid {0}: "{1}". It must follow format "host:port".'.format(
55-
_EMULATOR_HOST_ENV_VAR, emulator_host))
5649
base_url='http://{0}/identitytoolkit.googleapis.com'.format(emulator_host)
5750
endpoint_urls['v1'] =base_url+'/v1'
5851
endpoint_urls['v2beta1'] =base_url+'/v2beta1'
5952
credential=_utils.EmulatorAdminCredentials()
53+
self.emulated=True
6054
else:
6155
# Use credentials if provided
6256
credential=app.credential.get_credential()
@@ -132,7 +126,7 @@ def verify_id_token(self, id_token, check_revoked=False):
132126
raise_auth_utils.TenantIdMismatchError(
133127
'Invalid tenant ID: {0}'.format(token_tenant_id))
134128

135-
ifcheck_revoked:
129+
ifnotself.emulatedandcheck_revoked:
136130
self._check_jwt_revoked(verified_claims, _token_gen.RevokedIdTokenError, 'ID token')
137131
returnverified_claims
138132

firebase_admin/_auth_utils.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
"""Firebase auth utils."""
1616

1717
importjson
18+
importos
1819
importre
1920
fromurllibimportparse
2021

2122
fromfirebase_adminimportexceptions
2223
fromfirebase_adminimport_utils
2324

24-
25+
EMULATOR_HOST_ENV_VAR='FIREBASE_AUTH_EMULATOR_HOST'
2526
MAX_CLAIMS_PAYLOAD_SIZE=1000
2627
RESERVED_CLAIMS=set([
2728
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
@@ -66,6 +67,19 @@ def __iter__(self):
6667
returnself
6768

6869

70+
defget_emulator_host():
71+
emulator_host=os.getenv(EMULATOR_HOST_ENV_VAR)
72+
ifemulator_hostand'//'inemulator_host:
73+
raiseValueError(
74+
'Invalid {0}: "{1}". It must follow format "host:port".'.format(
75+
EMULATOR_HOST_ENV_VAR, emulator_host))
76+
returnemulator_host
77+
78+
79+
defis_emulated():
80+
returnget_emulator_host() notin [None, '']
81+
82+
6983
defvalidate_uid(uid, required=False):
7084
ifuidisNoneandnotrequired:
7185
returnNone

firebase_admin/_token_gen.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
fromfirebase_adminimport_auth_utils
3232

3333

34+
#_auth_utils.is_emulated() = _auth_utils.get_emulator_host() != ''
3435
# ID token constants
3536
ID_TOKEN_ISSUER_PREFIX='https://securetoken.google.com/'
3637
ID_TOKEN_CERT_URI= ('https://www.googleapis.com/robot/v1/metadata/x509/'
@@ -54,6 +55,16 @@
5455
'service-accounts/default/email')
5556

5657

58+
class_EmulatedSigner(google.auth.crypt.Signer):
59+
key_id=None
60+
61+
def__init__(self):
62+
pass
63+
64+
defsign(self, message):
65+
returnb''
66+
67+
5768
class_SigningProvider:
5869
"""Stores a reference to a google.auth.crypto.Signer."""
5970

@@ -78,6 +89,10 @@ def from_iam(cls, request, google_cred, service_account):
7889
signer=iam.Signer(request, google_cred, service_account)
7990
return_SigningProvider(signer, service_account)
8091

92+
@classmethod
93+
deffor_emulator(cls):
94+
return_SigningProvider(_EmulatedSigner(), 'firebase-auth-emulator@example.com')
95+
8196

8297
classTokenGenerator:
8398
"""Generates custom tokens and session cookies."""
@@ -94,6 +109,8 @@ def __init__(self, app, http_client, url_override=None):
94109

95110
def_init_signing_provider(self):
96111
"""Initializes a signing provider by following the go/firebase-admin-sign protocol."""
112+
if_auth_utils.is_emulated():
113+
return_SigningProvider.for_emulator()
97114
# If the SDK was initialized with a service account, use it to sign bytes.
98115
google_cred=self.app.credential.get_credential()
99116
ifisinstance(google_cred, google.oauth2.service_account.Credentials):
@@ -291,15 +308,15 @@ def verify(self, token, request):
291308
error_message= (
292309
'{0} expects {1}, but was given a custom '
293310
'token.'.format(self.operation, self.articled_short_name))
294-
elifnotheader.get('kid'):
311+
elifnot_auth_utils.is_emulated() andnotheader.get('kid'):
295312
ifheader.get('alg') =='HS256'andpayload.get(
296313
'v') ==0and'uid'inpayload.get('d', {}):
297314
error_message= (
298315
'{0} expects {1}, but was given a legacy custom '
299316
'token.'.format(self.operation, self.articled_short_name))
300317
else:
301318
error_message='Firebase {0} has no "kid" claim.'.format(self.short_name)
302-
elifheader.get('alg') !='RS256':
319+
elifnot_auth_utils.is_emulated() andheader.get('alg') !='RS256':
303320
error_message= (
304321
'Firebase {0} has incorrect algorithm. Expected "RS256" but got '
305322
'"{1}". {2}'.format(self.short_name, header.get('alg'), verify_id_token_msg))
@@ -329,6 +346,10 @@ def verify(self, token, request):
329346
iferror_message:
330347
raiseself._invalid_token_error(error_message)
331348

349+
if_auth_utils.is_emulated():
350+
claims=jwt.decode(token, verify=False)
351+
claims['uid'] =claims['sub']
352+
returnclaims
332353
try:
333354
verified_claims=google.oauth2.id_token.verify_token(
334355
token,
@@ -342,7 +363,7 @@ def verify(self, token, request):
342363
exceptValueErroraserror:
343364
if'Token expired'instr(error):
344365
raiseself._expired_token_error(str(error), cause=error)
345-
raiseself._invalid_token_error(str(error), cause=error)
366+
raiseself._invalid_token_error(str(error)+"FOO", cause=error)
346367

347368
def_decode_unverified(self, token):
348369
try:

integration/test_auth.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Integration tests for firebase_admin.auth module."""
1616
importbase64
1717
importdatetime
18+
importos
1819
importrandom
1920
importstring
2021
importtime
@@ -32,11 +33,18 @@
3233
fromfirebase_adminimportcredentials
3334

3435

35-
_verify_token_url='https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken'
36-
_verify_password_url='https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword'
37-
_password_reset_url='https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword'
38-
_verify_email_url='https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo'
39-
_email_sign_in_url='https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSignin'
36+
_EMULATOR_HOST_ENV_VAR='FIREBASE_AUTH_EMULATOR_HOST'
37+
URL_PREFIX='https://www.googleapis.com/identitytoolkit'
38+
39+
emulator_host=os.getenv(_EMULATOR_HOST_ENV_VAR)
40+
ifemulator_host:
41+
URL_PREFIX='http://{0}/www.googleapis.com/identitytoolkit'.format(emulator_host)
42+
43+
_verify_token_url='{0}/v3/relyingparty/verifyCustomToken'.format(URL_PREFIX)
44+
_verify_password_url='{0}/v3/relyingparty/verifyPassword'.format(URL_PREFIX)
45+
_password_reset_url='{0}/v3/relyingparty/resetPassword'.format(URL_PREFIX)
46+
_verify_email_url='{0}/v3/relyingparty/setAccountInfo'.format(URL_PREFIX)
47+
_email_sign_in_url='{0}/v3/relyingparty/emailLinkSignin'.format(URL_PREFIX)
4048

4149
ACTION_LINK_CONTINUE_URL='http://localhost?a=1&b=5#f=1'
4250

@@ -560,6 +568,9 @@ def test_verify_id_token_revoked(new_user, api_key):
560568
# verify_id_token succeeded because it didn't check revoked.
561569
assertclaims['iat'] *1000<user.tokens_valid_after_timestamp
562570

571+
ifemulator_host:
572+
pytest.skip("Not supported with auth emulator")
573+
563574
withpytest.raises(auth.RevokedIdTokenError) asexcinfo:
564575
claims=auth.verify_id_token(id_token, check_revoked=True)
565576
assertstr(excinfo.value) =='The Firebase ID token has been revoked.'

tests/test_token_gen.py

+29
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def _merge_jwt_claims(defaults, overrides):
7575
returndefaults
7676

7777
defverify_custom_token(custom_token, expected_claims, tenant_id=None):
78+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
79+
pytest.skip("Not supported with auth emulator")
7880
assertisinstance(custom_token, bytes)
7981
token=google.oauth2.id_token.verify_token(
8082
custom_token,
@@ -230,10 +232,14 @@ def test_invalid_params(self, auth_app, values):
230232
auth.create_custom_token(user, claims, app=auth_app)
231233

232234
deftest_noncert_credential(self, user_mgt_app):
235+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
236+
pytest.skip("Not supported with auth emulator")
233237
withpytest.raises(ValueError):
234238
auth.create_custom_token(MOCK_UID, app=user_mgt_app)
235239

236240
deftest_sign_with_iam(self):
241+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
242+
pytest.skip("Not supported with auth emulator")
237243
options= {'serviceAccountId': 'test-service-account', 'projectId': 'mock-project-id'}
238244
app=firebase_admin.initialize_app(
239245
testutils.MockCredential(), name='iam-signer-app', options=options)
@@ -248,6 +254,8 @@ def test_sign_with_iam(self):
248254
firebase_admin.delete_app(app)
249255

250256
deftest_sign_with_iam_error(self):
257+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
258+
pytest.skip("Not supported with auth emulator")
251259
options= {'serviceAccountId': 'test-service-account', 'projectId': 'mock-project-id'}
252260
app=firebase_admin.initialize_app(
253261
testutils.MockCredential(), name='iam-signer-app', options=options)
@@ -264,6 +272,8 @@ def test_sign_with_iam_error(self):
264272
firebase_admin.delete_app(app)
265273

266274
deftest_sign_with_discovered_service_account(self):
275+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
276+
pytest.skip("Not supported with auth emulator")
267277
request=testutils.MockRequest(200, 'discovered-service-account')
268278
options= {'projectId': 'mock-project-id'}
269279
app=firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app',
@@ -287,6 +297,8 @@ def test_sign_with_discovered_service_account(self):
287297
firebase_admin.delete_app(app)
288298

289299
deftest_sign_with_discovery_failure(self):
300+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
301+
pytest.skip("Not supported with auth emulator")
290302
request=testutils.MockFailedRequest(Exception('test error'))
291303
options= {'projectId': 'mock-project-id'}
292304
app=firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app',
@@ -431,6 +443,8 @@ def test_valid_token_check_revoked(self, user_mgt_app, id_token):
431443

432444
@pytest.mark.parametrize('id_token', valid_tokens.values(), ids=list(valid_tokens))
433445
deftest_revoked_token_check_revoked(self, user_mgt_app, revoked_tokens, id_token):
446+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
447+
pytest.skip("Not supported with auth emulator")
434448
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
435449
_instrument_user_manager(user_mgt_app, 200, revoked_tokens)
436450
withpytest.raises(auth.RevokedIdTokenError) asexcinfo:
@@ -460,13 +474,18 @@ def test_invalid_arg(self, user_mgt_app, id_token):
460474

461475
@pytest.mark.parametrize('id_token', invalid_tokens.values(), ids=list(invalid_tokens))
462476
deftest_invalid_token(self, user_mgt_app, id_token):
477+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
478+
pytest.skip("Not supported with auth emulator")
463479
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
464480
withpytest.raises(auth.InvalidIdTokenError) asexcinfo:
465481
auth.verify_id_token(id_token, app=user_mgt_app)
466482
assertisinstance(excinfo.value, exceptions.InvalidArgumentError)
467483
assertexcinfo.value.http_responseisNone
468484

469485
deftest_expired_token(self, user_mgt_app):
486+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
487+
pytest.skip("Not supported with auth emulator")
488+
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
470489
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
471490
id_token=self.invalid_tokens['ExpiredToken']
472491
withpytest.raises(auth.ExpiredIdTokenError) asexcinfo:
@@ -505,6 +524,8 @@ def test_custom_token(self, auth_app):
505524
assertstr(excinfo.value) ==message
506525

507526
deftest_certificate_request_failure(self, user_mgt_app):
527+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
528+
pytest.skip("Not supported with auth emulator")
508529
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
509530
withpytest.raises(auth.CertificateFetchError) asexcinfo:
510531
auth.verify_id_token(TEST_ID_TOKEN, app=user_mgt_app)
@@ -580,13 +601,17 @@ def test_invalid_args(self, user_mgt_app, cookie):
580601

581602
@pytest.mark.parametrize('cookie', invalid_cookies.values(), ids=list(invalid_cookies))
582603
deftest_invalid_cookie(self, user_mgt_app, cookie):
604+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
605+
pytest.skip("Not supported with auth emulator")
583606
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
584607
withpytest.raises(auth.InvalidSessionCookieError) asexcinfo:
585608
auth.verify_session_cookie(cookie, app=user_mgt_app)
586609
assertisinstance(excinfo.value, exceptions.InvalidArgumentError)
587610
assertexcinfo.value.http_responseisNone
588611

589612
deftest_expired_cookie(self, user_mgt_app):
613+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
614+
pytest.skip("Not supported with auth emulator")
590615
_overwrite_cert_request(user_mgt_app, MOCK_REQUEST)
591616
cookie=self.invalid_cookies['ExpiredCookie']
592617
withpytest.raises(auth.ExpiredSessionCookieError) asexcinfo:
@@ -620,6 +645,8 @@ def test_custom_token(self, auth_app):
620645
auth.verify_session_cookie(custom_token, app=auth_app)
621646

622647
deftest_certificate_request_failure(self, user_mgt_app):
648+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
649+
pytest.skip("Not supported with auth emulator")
623650
_overwrite_cert_request(user_mgt_app, testutils.MockRequest(404, 'not found'))
624651
withpytest.raises(auth.CertificateFetchError) asexcinfo:
625652
auth.verify_session_cookie(TEST_SESSION_COOKIE, app=user_mgt_app)
@@ -632,6 +659,8 @@ def test_certificate_request_failure(self, user_mgt_app):
632659
classTestCertificateCaching:
633660

634661
deftest_certificate_caching(self, user_mgt_app, httpserver):
662+
ifos.getenv(EMULATOR_HOST_ENV_VAR):
663+
pytest.skip("Not supported with auth emulator")
635664
httpserver.serve_content(MOCK_PUBLIC_CERTS, 200, headers={'Cache-Control': 'max-age=3600'})
636665
verifier=_token_gen.TokenVerifier(user_mgt_app)
637666
verifier.cookie_verifier.cert_url=httpserver.url

0 commit comments

Comments
 (0)
close