From 16c46d85391fd9b4e0f1644cc344e6ea35d4f55a Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 9 Apr 2021 14:13:03 -0700 Subject: [PATCH 1/3] fix(auth): Using alg=none header for custom tokens in emulator mode --- firebase_admin/_token_gen.py | 15 ++++++++++++--- tests/test_token_gen.py | 9 ++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/firebase_admin/_token_gen.py b/firebase_admin/_token_gen.py index 135573c01..32c109d5d 100644 --- a/firebase_admin/_token_gen.py +++ b/firebase_admin/_token_gen.py @@ -53,6 +53,8 @@ ]) METADATA_SERVICE_URL = ('http://metadata.google.internal/computeMetadata/v1/instance/' 'service-accounts/default/email') +ALGORITHM_RS256 = 'RS256' +ALGORITHM_NONE = 'none' # Emulator fake account AUTH_EMULATOR_EMAIL = 'firebase-auth-emulator@example.com' @@ -71,9 +73,10 @@ def sign(self, message): class _SigningProvider: """Stores a reference to a google.auth.crypto.Signer.""" - def __init__(self, signer, signer_email): + def __init__(self, signer, signer_email, alg=ALGORITHM_RS256): self._signer = signer self._signer_email = signer_email + self._alg = alg @property def signer(self): @@ -83,6 +86,10 @@ def signer(self): def signer_email(self): return self._signer_email + @property + def alg(self): + return self._alg + @classmethod def from_credential(cls, google_cred): return _SigningProvider(google_cred.signer, google_cred.signer_email) @@ -94,7 +101,7 @@ def from_iam(cls, request, google_cred, service_account): @classmethod def for_emulator(cls): - return _SigningProvider(_EmulatedSigner(), AUTH_EMULATOR_EMAIL) + return _SigningProvider(_EmulatedSigner(), AUTH_EMULATOR_EMAIL, ALGORITHM_NONE) class TokenGenerator: @@ -190,8 +197,10 @@ def create_custom_token(self, uid, developer_claims=None, tenant_id=None): if developer_claims is not None: payload['claims'] = developer_claims + + header = {'alg': signing_provider.alg} try: - return jwt.encode(signing_provider.signer, payload) + return jwt.encode(signing_provider.signer, payload, header=header) except google.auth.exceptions.TransportError as error: msg = 'Failed to sign custom token. {0}'.format(error) raise TokenSignError(msg, error) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index b0a744f1d..0c1e39dc5 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -78,14 +78,20 @@ def _merge_jwt_claims(defaults, overrides): def verify_custom_token(custom_token, expected_claims, tenant_id=None): assert isinstance(custom_token, bytes) expected_email = MOCK_SERVICE_ACCOUNT_EMAIL + header = jwt.decode_header(custom_token) + assert header.get('typ') == 'JWT' if _is_emulated(): + assert header.get('alg') == 'none' + assert custom_token.split(b'.')[2] == b'' expected_email = _token_gen.AUTH_EMULATOR_EMAIL token = jwt.decode(custom_token, verify=False) else: + assert header.get('alg') == 'RS256' token = google.oauth2.id_token.verify_token( custom_token, testutils.MockRequest(200, MOCK_PUBLIC_CERTS), _token_gen.FIREBASE_AUDIENCE) + assert token['uid'] == MOCK_UID assert token['iss'] == expected_email assert token['sub'] == expected_email @@ -94,9 +100,6 @@ def verify_custom_token(custom_token, expected_claims, tenant_id=None): else: assert token['tenant_id'] == tenant_id - header = jwt.decode_header(custom_token) - assert header.get('typ') == 'JWT' - assert header.get('alg') == 'RS256' if expected_claims: for key, value in expected_claims.items(): assert value == token['claims'][key] From bf4969046dcbf619a4360be2814d97fb16ab2f3d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 12 Apr 2021 12:02:48 -0700 Subject: [PATCH 2/3] fix: Dropping google-auth explicit dependency; Temp test skip for Py 3.5 --- tests/test_token_gen.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 0c1e39dc5..ebc3a9037 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -18,6 +18,7 @@ import datetime import json import os +import sys import time from google.auth import crypt @@ -75,13 +76,22 @@ def _merge_jwt_claims(defaults, overrides): del defaults[key] return defaults + +def _is_py_35(): + return sys.version_info.minor == 5 + + def verify_custom_token(custom_token, expected_claims, tenant_id=None): assert isinstance(custom_token, bytes) expected_email = MOCK_SERVICE_ACCOUNT_EMAIL header = jwt.decode_header(custom_token) assert header.get('typ') == 'JWT' if _is_emulated(): - assert header.get('alg') == 'none' + # Setting alg requires https://github.com/googleapis/google-auth-library-python/pull/729 + # which is only available in latest google-auth versions that's supported on Python 3.6 + # and up. Skip the test on Python 3.5 for now. + if not _is_py_35(): + assert header.get('alg') == 'none' assert custom_token.split(b'.')[2] == b'' expected_email = _token_gen.AUTH_EMULATOR_EMAIL token = jwt.decode(custom_token, verify=False) From 3a8a3e010de6868f2d400d4ad1f65d200483b473 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 21 Apr 2021 12:42:17 -0700 Subject: [PATCH 3/3] chore: Removed py35 hack --- tests/test_token_gen.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index ebc3a9037..0a09862ab 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -18,7 +18,6 @@ import datetime import json import os -import sys import time from google.auth import crypt @@ -77,21 +76,13 @@ def _merge_jwt_claims(defaults, overrides): return defaults -def _is_py_35(): - return sys.version_info.minor == 5 - - def verify_custom_token(custom_token, expected_claims, tenant_id=None): assert isinstance(custom_token, bytes) expected_email = MOCK_SERVICE_ACCOUNT_EMAIL header = jwt.decode_header(custom_token) assert header.get('typ') == 'JWT' if _is_emulated(): - # Setting alg requires https://github.com/googleapis/google-auth-library-python/pull/729 - # which is only available in latest google-auth versions that's supported on Python 3.6 - # and up. Skip the test on Python 3.5 for now. - if not _is_py_35(): - assert header.get('alg') == 'none' + assert header.get('alg') == 'none' assert custom_token.split(b'.')[2] == b'' expected_email = _token_gen.AUTH_EMULATOR_EMAIL token = jwt.decode(custom_token, verify=False)