-
Notifications
You must be signed in to change notification settings - Fork 337
Support for creating custom tokens without service account credentials #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
fb86610
bc57be0
a14119c
838c913
2afeabe
8eebafc
5870af4
d7888a6
76fdf5b
4a7af32
785e259
453deb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,10 +109,13 @@ def create_custom_token(uid, developer_claims=None, app=None): | |
|
||
Raises: | ||
ValueError: If input parameters are invalid. | ||
AuthError: If an error occurs while creating the token using the remote IAM service. | ||
""" | ||
token_generator = _get_auth_service(app).token_generator | ||
return token_generator.create_custom_token(uid, developer_claims) | ||
|
||
try: | ||
return token_generator.create_custom_token(uid, developer_claims) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like we are wrapping some calls with try catch (I assume because we now no longer assume that IAM calls work). How confident are you that you caught all the cases. This feels like it would be easy to miss one of these cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IAM dependency is introduced with this PR. |
||
except _token_gen.ApiCallError as error: | ||
raise AuthError(error.code, str(error), error.detail) | ||
|
||
def verify_id_token(id_token, app=None, check_revoked=False): | ||
"""Verifies the signature and data for the provided JWT. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
|
||
"""Test cases for the firebase_admin._token_gen module.""" | ||
|
||
import base64 | ||
import datetime | ||
import json | ||
import os | ||
|
@@ -109,8 +110,11 @@ def _instrument_user_manager(app, status, payload): | |
|
||
def _overwrite_cert_request(app, request): | ||
auth_service = auth._get_auth_service(app) | ||
token_verifier = auth_service.token_verifier | ||
token_verifier.request = request | ||
auth_service.token_verifier.request = request | ||
|
||
def _overwrite_iam_request(app, request): | ||
auth_service = auth._get_auth_service(app) | ||
auth_service.token_generator.request = request | ||
|
||
@pytest.fixture(scope='module') | ||
def auth_app(): | ||
|
@@ -194,6 +198,74 @@ def test_noncert_credential(self, user_mgt_app): | |
with pytest.raises(ValueError): | ||
auth.create_custom_token(MOCK_UID, app=user_mgt_app) | ||
|
||
def test_sign_with_iam(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of these test exercise the new error cases you added, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
options = {'service_account_id': 'test-service-account'} | ||
app = firebase_admin.initialize_app( | ||
testutils.MockCredential(), name='iam-signer-app', options=options) | ||
try: | ||
signature = base64.b64encode(b'test').decode() | ||
iam_resp = '{{"signature": "{0}"}}'.format(signature) | ||
_overwrite_iam_request(app, testutils.MockRequest(200, iam_resp)) | ||
custom_token = auth.create_custom_token(MOCK_UID, app=app).decode() | ||
assert custom_token.endswith('.' + signature) | ||
self._verify_signer(custom_token, 'test-service-account') | ||
finally: | ||
firebase_admin.delete_app(app) | ||
|
||
def test_sign_with_iam_error(self): | ||
options = {'service_account_id': 'test-service-account'} | ||
app = firebase_admin.initialize_app( | ||
testutils.MockCredential(), name='iam-signer-app', options=options) | ||
try: | ||
iam_resp = '{"error": {"code": 403, "message": "test error"}}' | ||
_overwrite_iam_request(app, testutils.MockRequest(403, iam_resp)) | ||
with pytest.raises(auth.AuthError) as excinfo: | ||
auth.create_custom_token(MOCK_UID, app=app) | ||
assert excinfo.value.code == _token_gen.TOKEN_SIGN_ERROR | ||
assert iam_resp in str(excinfo.value) | ||
finally: | ||
firebase_admin.delete_app(app) | ||
|
||
def test_sign_with_discovered_service_account(self): | ||
request = testutils.MockRequest(200, 'discovered-service-account') | ||
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app') | ||
try: | ||
_overwrite_iam_request(app, request) | ||
# Force initialization of the signing provider. This will invoke the Metadata service. | ||
auth_service = auth._get_auth_service(app) | ||
assert auth_service.token_generator.signing_provider is not None | ||
# Now invoke the IAM signer. | ||
signature = base64.b64encode(b'test').decode() | ||
request.response = testutils.MockResponse( | ||
200, '{{"signature": "{0}"}}'.format(signature)) | ||
custom_token = auth.create_custom_token(MOCK_UID, app=app).decode() | ||
assert custom_token.endswith('.' + signature) | ||
self._verify_signer(custom_token, 'discovered-service-account') | ||
assert len(request.log) == 2 | ||
assert request.log[0][1]['headers'] == {'Metadata-Flavor': 'Google'} | ||
finally: | ||
firebase_admin.delete_app(app) | ||
|
||
def test_sign_with_discovery_failure(self): | ||
request = testutils.MockFailedRequest(Exception('test error')) | ||
app = firebase_admin.initialize_app(testutils.MockCredential(), name='iam-signer-app') | ||
try: | ||
_overwrite_iam_request(app, request) | ||
with pytest.raises(ValueError) as excinfo: | ||
auth.create_custom_token(MOCK_UID, app=app) | ||
assert str(excinfo.value).startswith('Failed to determine service account: test error') | ||
assert len(request.log) == 1 | ||
assert request.log[0][1]['headers'] == {'Metadata-Flavor': 'Google'} | ||
finally: | ||
firebase_admin.delete_app(app) | ||
|
||
def _verify_signer(self, token, signer): | ||
segments = token.split('.') | ||
assert len(segments) == 3 | ||
body = json.loads(base64.b64decode(segments[1]).decode()) | ||
assert body['iss'] == signer | ||
assert body['sub'] == signer | ||
|
||
|
||
class TestCreateSessionCookie(object): | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we don't want them to be able to override the default GCE credentials?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this option is not specified, we fall back to discovering the serviceAccountId from the environment. So this is the case when a developer is overriding the default serviceAccountId with an explicitly specified one.