From 6e9058f58123f627a7cc49142e05f1dd7d87108c Mon Sep 17 00:00:00 2001 From: Huw Martin Date: Fri, 30 May 2025 13:23:08 +0100 Subject: [PATCH 1/5] Add link_domain to ActionCodeSettings; update encode_action_code_settings to handle link_domain --- firebase_admin/_user_mgt.py | 11 ++++++++++- tests/test_user_mgt.py | 8 ++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index aa0dfb0a4..6b41472ec 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -490,7 +490,8 @@ class ActionCodeSettings: """ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_bundle_id=None, - android_package_name=None, android_install_app=None, android_minimum_version=None): + android_package_name=None, android_install_app=None, android_minimum_version=None, + link_domain=None): self.url = url self.handle_code_in_app = handle_code_in_app self.dynamic_link_domain = dynamic_link_domain @@ -498,6 +499,7 @@ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_b self.android_package_name = android_package_name self.android_install_app = android_install_app self.android_minimum_version = android_minimum_version + self.link_domain = link_domain def encode_action_code_settings(settings): @@ -535,6 +537,13 @@ def encode_action_code_settings(settings): .format(settings.dynamic_link_domain)) parameters['dynamicLinkDomain'] = settings.dynamic_link_domain + # link_domain + if settings.link_domain is not None: + if not isinstance(settings.link_domain, str): + raise ValueError('Invalid value provided for link_domain: {0}' + .format(settings.link_domain)) + parameters['linkDomain'] = settings.link_domain + # ios_bundle_id if settings.ios_bundle_id is not None: if not isinstance(settings.ios_bundle_id, str): diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index 34b698be4..835dd0b9f 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -43,7 +43,8 @@ MOCK_ACTION_CODE_DATA = { 'url': 'http://localhost', 'handle_code_in_app': True, - 'dynamic_link_domain': 'http://testly', + 'dynamic_link_domain': 'http://dynamic-link-domain', + 'link_domain': 'http://link-domain', 'ios_bundle_id': 'test.bundle', 'android_package_name': 'test.bundle', 'android_minimum_version': '7', @@ -1364,7 +1365,8 @@ def test_valid_data(self): data = { 'url': 'http://localhost', 'handle_code_in_app': True, - 'dynamic_link_domain': 'http://testly', + 'dynamic_link_domain': 'http://dynamic-link-domain', + 'link_domain': 'http://link-domain', 'ios_bundle_id': 'test.bundle', 'android_package_name': 'test.bundle', 'android_minimum_version': '7', @@ -1375,6 +1377,7 @@ def test_valid_data(self): assert parameters['continueUrl'] == data['url'] assert parameters['canHandleCodeInApp'] == data['handle_code_in_app'] assert parameters['dynamicLinkDomain'] == data['dynamic_link_domain'] + assert parameters['linkDomain'] == data['link_domain'] assert parameters['iOSBundleId'] == data['ios_bundle_id'] assert parameters['androidPackageName'] == data['android_package_name'] assert parameters['androidMinimumVersion'] == data['android_minimum_version'] @@ -1535,6 +1538,7 @@ def _validate_request(self, request, settings=None): assert request['continueUrl'] == settings.url assert request['canHandleCodeInApp'] == settings.handle_code_in_app assert request['dynamicLinkDomain'] == settings.dynamic_link_domain + assert request['linkDomain'] == settings.link_domain assert request['iOSBundleId'] == settings.ios_bundle_id assert request['androidPackageName'] == settings.android_package_name assert request['androidMinimumVersion'] == settings.android_minimum_version From bb1a986ed904c72e0166a852ce60133e6f16bc09 Mon Sep 17 00:00:00 2001 From: Huw Martin Date: Mon, 2 Jun 2025 10:32:03 +0100 Subject: [PATCH 2/5] Add handling for InvalidHostingLinkDomainError --- firebase_admin/_auth_utils.py | 10 ++++++++++ firebase_admin/auth.py | 2 ++ tests/test_user_mgt.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index ac7b322ff..127a508a4 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -328,6 +328,15 @@ def __init__(self, message, cause, http_response): exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) +class InvalidHostingLinkDomainError(exceptions.InvalidArgumentError): + """Hosting link domain in ActionCodeSettings is not authorized.""" + + default_message = 'Hosting link domain specified in ActionCodeSettings is not authorized' + + def __init__(self, message, cause, http_response): + exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) + + class InvalidIdTokenError(exceptions.InvalidArgumentError): """The provided ID token is not a valid Firebase ID token.""" @@ -427,6 +436,7 @@ def __init__(self, message, cause=None, http_response=None): 'EMAIL_NOT_FOUND': EmailNotFoundError, 'INSUFFICIENT_PERMISSION': InsufficientPermissionError, 'INVALID_DYNAMIC_LINK_DOMAIN': InvalidDynamicLinkDomainError, + 'INVALID_HOSTING_LINK_DOMAIN': InvalidHostingLinkDomainError, 'INVALID_ID_TOKEN': InvalidIdTokenError, 'PHONE_NUMBER_EXISTS': PhoneNumberAlreadyExistsError, 'TENANT_NOT_FOUND': TenantNotFoundError, diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index ced143112..cb63ab7f0 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -49,6 +49,7 @@ 'ImportUserRecord', 'InsufficientPermissionError', 'InvalidDynamicLinkDomainError', + 'InvalidHostingLinkDomainError', 'InvalidIdTokenError', 'InvalidSessionCookieError', 'ListProviderConfigsPage', @@ -125,6 +126,7 @@ ImportUserRecord = _user_import.ImportUserRecord InsufficientPermissionError = _auth_utils.InsufficientPermissionError InvalidDynamicLinkDomainError = _auth_utils.InvalidDynamicLinkDomainError +InvalidHostingLinkDomainError = _auth_utils.InvalidHostingLinkDomainError InvalidIdTokenError = _auth_utils.InvalidIdTokenError InvalidSessionCookieError = _token_gen.InvalidSessionCookieError ListProviderConfigsPage = _auth_providers.ListProviderConfigsPage diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index 835dd0b9f..e471e5b11 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -1500,6 +1500,23 @@ def test_invalid_dynamic_link(self, user_mgt_app, func): assert excinfo.value.http_response is not None assert excinfo.value.cause is not None + @pytest.mark.parametrize('func', [ + auth.generate_sign_in_with_email_link, + auth.generate_email_verification_link, + auth.generate_password_reset_link, + ]) + def test_invalid_hosting_link(self, user_mgt_app, func): + resp = '{"error":{"message": "INVALID_HOSTING_LINK_DOMAIN: Because of this reason."}}' + _instrument_user_manager(user_mgt_app, 500, resp) + with pytest.raises(auth.InvalidHostingLinkDomainError) as excinfo: + func('test@test.com', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app) + assert isinstance(excinfo.value, exceptions.InvalidArgumentError) + assert str(excinfo.value) == ('Hosting link domain specified in ActionCodeSettings is ' + 'not authorized (INVALID_HOSTING_LINK_DOMAIN). Because ' + 'of this reason.') + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None + @pytest.mark.parametrize('func', [ auth.generate_sign_in_with_email_link, auth.generate_email_verification_link, From 3b1a8db822e25034a03410eddeada408ae9f0c4f Mon Sep 17 00:00:00 2001 From: Huw Martin Date: Tue, 3 Jun 2025 11:06:07 +0100 Subject: [PATCH 3/5] Add deprecation warning for dynamic_link_domain --- firebase_admin/_user_mgt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index 6b41472ec..a03262a34 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -18,6 +18,7 @@ from collections import defaultdict import json from urllib import parse +import warnings import requests @@ -492,6 +493,8 @@ class ActionCodeSettings: def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_bundle_id=None, android_package_name=None, android_install_app=None, android_minimum_version=None, link_domain=None): + if dynamic_link_domain is not None: + warnings.warn('dynamic_link_domain is deprecated, use link_domain instead', DeprecationWarning) self.url = url self.handle_code_in_app = handle_code_in_app self.dynamic_link_domain = dynamic_link_domain From bc7fcfcf7f052b9c569fc0cf17c1bf596db73804 Mon Sep 17 00:00:00 2001 From: Huw Martin Date: Tue, 1 Jul 2025 19:58:11 +0100 Subject: [PATCH 4/5] Update error message for InvalidHostingLinkDomainError --- firebase_admin/_auth_utils.py | 10 ++++++---- tests/test_user_mgt.py | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index 127a508a4..ae3171249 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -329,12 +329,14 @@ def __init__(self, message, cause, http_response): class InvalidHostingLinkDomainError(exceptions.InvalidArgumentError): - """Hosting link domain in ActionCodeSettings is not authorized.""" + """The provided hosting link domain is not configured in Firebase Hosting + or is not owned by the current project.""" - default_message = 'Hosting link domain specified in ActionCodeSettings is not authorized' + default_message = ('The provided hosting link domain is not configured in Firebase ' + 'Hosting or is not owned by the current project.') - def __init__(self, message, cause, http_response): - exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) + def __init__(self, message, cause, http_response): + exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) class InvalidIdTokenError(exceptions.InvalidArgumentError): diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index e471e5b11..a73151dfc 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -1511,9 +1511,9 @@ def test_invalid_hosting_link(self, user_mgt_app, func): with pytest.raises(auth.InvalidHostingLinkDomainError) as excinfo: func('test@test.com', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app) assert isinstance(excinfo.value, exceptions.InvalidArgumentError) - assert str(excinfo.value) == ('Hosting link domain specified in ActionCodeSettings is ' - 'not authorized (INVALID_HOSTING_LINK_DOMAIN). Because ' - 'of this reason.') + assert str(excinfo.value) == ('The provided hosting link domain is not configured in ' + 'Firebase Hosting or is not owned by the current project. ' + '(INVALID_HOSTING_LINK_DOMAIN). Because of this reason.') assert excinfo.value.http_response is not None assert excinfo.value.cause is not None From dfce7c1d31e16c1a9c05653bdfa676c93da58c80 Mon Sep 17 00:00:00 2001 From: Huw Martin Date: Mon, 7 Jul 2025 18:29:42 +0100 Subject: [PATCH 5/5] Fix lint --- firebase_admin/_auth_utils.py | 6 +++--- firebase_admin/_user_mgt.py | 5 ++++- tests/test_user_mgt.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index ae3171249..3bb636796 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -331,10 +331,10 @@ def __init__(self, message, cause, http_response): class InvalidHostingLinkDomainError(exceptions.InvalidArgumentError): """The provided hosting link domain is not configured in Firebase Hosting or is not owned by the current project.""" - + default_message = ('The provided hosting link domain is not configured in Firebase ' - 'Hosting or is not owned by the current project.') - + 'Hosting or is not owned by the current project') + def __init__(self, message, cause, http_response): exceptions.InvalidArgumentError.__init__(self, message, cause, http_response) diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index a03262a34..1b9f51455 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -494,7 +494,10 @@ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_b android_package_name=None, android_install_app=None, android_minimum_version=None, link_domain=None): if dynamic_link_domain is not None: - warnings.warn('dynamic_link_domain is deprecated, use link_domain instead', DeprecationWarning) + warnings.warn( + 'dynamic_link_domain is deprecated, use link_domain instead', + DeprecationWarning + ) self.url = url self.handle_code_in_app = handle_code_in_app self.dynamic_link_domain = dynamic_link_domain diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index a73151dfc..b12405e8b 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -1512,7 +1512,7 @@ def test_invalid_hosting_link(self, user_mgt_app, func): func('test@test.com', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app) assert isinstance(excinfo.value, exceptions.InvalidArgumentError) assert str(excinfo.value) == ('The provided hosting link domain is not configured in ' - 'Firebase Hosting or is not owned by the current project. ' + 'Firebase Hosting or is not owned by the current project ' '(INVALID_HOSTING_LINK_DOMAIN). Because of this reason.') assert excinfo.value.http_response is not None assert excinfo.value.cause is not None