diff --git a/firebase_admin/email_privacy_config_mgt.py b/firebase_admin/email_privacy_config_mgt.py new file mode 100644 index 000000000..553c93550 --- /dev/null +++ b/firebase_admin/email_privacy_config_mgt.py @@ -0,0 +1,83 @@ +# Copyright 2023 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Firebase multifactor configuration management module. + +This module contains functions for managing various multifactor configurations at +the project and tenant level. +""" + +__all__ = [ + 'validate_keys', + 'EmailPrivacyServerConfig', + 'EmailPrivacyConfig', +] + + +def validate_keys(keys, valid_keys, config_name): + for key in keys: + if key not in valid_keys: + raise ValueError( + '"{0}" is not a valid "{1}" parameter.'.format( + key, config_name)) + + +class EmailPrivacyServerConfig: + """Represents email privacy configuration response received from the server and + converts it to user format. + """ + + def __init__(self, data): + if not isinstance(data, dict): + raise ValueError( + 'Invalid data argument in EmailPrivacyConfig constructor: {0}'.format(data)) + self._data = data + + @property + def enable_improved_email_privacy(self): + return self._data.get('enableImprovedEmailPrivacy', False) + +class EmailPrivacyConfig: + """Represents a email privacy configuration for tenant or project + """ + + def __init__(self, + enable_improved_email_privacy: bool = False): + self.enable_improved_email_privacy: bool = enable_improved_email_privacy + + def to_dict(self) -> dict: + data = {} + if self.enable_improved_email_privacy: + data['enableImprovedEmailPrivacy'] = self.enable_improved_email_privacy + return data + + def validate(self): + """Validates a given email_privacy_config object. + + Raises: + ValueError: In case of an unsuccessful validation. + """ + validate_keys( + keys=vars(self).keys(), + valid_keys={'enable_improved_email_privacy'}, + config_name='EmailPrivacyConfig') + if self.enable_improved_email_privacy is None: + raise ValueError( + 'email_privacy_config.enable_improved_email_privacy must be specified') + if not isinstance(self.enable_improved_email_privacy, bool): + raise ValueError( + 'enable_improved_email_privacy must be a valid bool.') + + def build_server_request(self): + self.validate() + return self.to_dict() diff --git a/firebase_admin/project_config_mgt.py b/firebase_admin/project_config_mgt.py index 329ded69f..67554050c 100644 --- a/firebase_admin/project_config_mgt.py +++ b/firebase_admin/project_config_mgt.py @@ -24,6 +24,8 @@ from firebase_admin import _utils from firebase_admin.multi_factor_config_mgt import MultiFactorConfig from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig _PROJECT_CONFIG_MGT_ATTRIBUTE = '_project_config_mgt' @@ -51,12 +53,14 @@ def get_project_config(app=None): project_config_mgt_service = _get_project_config_mgt_service(app) return project_config_mgt_service.get_project_config() -def update_project_config(multi_factor_config: MultiFactorConfig = None, app=None): - """Update the project config with the given options. - +def update_project_config(multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None, + app=None): + """Update the Project Config with the given options. Args: multi_factor_config: Updated multi-factor authentication configuration (optional) + email_privacy_config: Updated Email Privacy configuration (optional). app: An App instance (optional). Returns: Project: An updated ProjectConfig object. @@ -65,7 +69,9 @@ def update_project_config(multi_factor_config: MultiFactorConfig = None, app=Non FirebaseError: If an error occurs while updating the project. """ project_config_mgt_service = _get_project_config_mgt_service(app) - return project_config_mgt_service.update_project_config(multi_factor_config=multi_factor_config) + return project_config_mgt_service.update_project_config(multi_factor_config=multi_factor_config, + email_privacy_config= + email_privacy_config) def _get_project_config_mgt_service(app): @@ -89,6 +95,13 @@ def multi_factor_config(self): return MultiFactorServerConfig(data) return None + @property + def email_privacy_config(self): + data = self._data.get('emailPrivacyConfig') + if data: + return EmailPrivacyServerConfig(data) + return None + class _ProjectConfigManagementService: """Firebase project management service.""" @@ -112,7 +125,8 @@ def get_project_config(self) -> ProjectConfig: else: return ProjectConfig(body) - def update_project_config(self, multi_factor_config: MultiFactorConfig = None) -> ProjectConfig: + def update_project_config(self, multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None) -> ProjectConfig: """Updates the specified project with the given parameters.""" payload = {} @@ -120,6 +134,12 @@ def update_project_config(self, multi_factor_config: MultiFactorConfig = None) - if not isinstance(multi_factor_config, MultiFactorConfig): raise ValueError('multi_factor_config must be of type MultiFactorConfig.') payload['mfa'] = multi_factor_config.build_server_request() + + if email_privacy_config is not None: + if not isinstance(email_privacy_config, EmailPrivacyConfig): + raise ValueError('email_privacy_config must be of type EmailPrivacyConfig.') + payload['emailPrivacyConfig'] = email_privacy_config.build_server_request() + if not payload: raise ValueError( 'At least one parameter must be specified for update.') diff --git a/firebase_admin/tenant_mgt.py b/firebase_admin/tenant_mgt.py index 4f943b3e3..ea225b455 100644 --- a/firebase_admin/tenant_mgt.py +++ b/firebase_admin/tenant_mgt.py @@ -30,6 +30,8 @@ from firebase_admin import _utils from firebase_admin.multi_factor_config_mgt import MultiFactorConfig from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig _TENANT_MGT_ATTRIBUTE = '_tenant_mgt' @@ -94,7 +96,8 @@ def get_tenant(tenant_id, app=None): def create_tenant( display_name, allow_password_sign_up=None, enable_email_link_sign_in=None, - multi_factor_config: MultiFactorConfig = None, app=None): + multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None, app=None): """Creates a new tenant from the given options. Args: @@ -105,6 +108,7 @@ def create_tenant( enable_email_link_sign_in: A boolean indicating whether to enable or disable email link sign-in (optional). Disabling this makes the password required for email sign-in. multi_factor_config : A multi factor configuration to add to the tenant (optional). + email_privacy_config: An email privacy configuration to add to the tenant (optional). app: An App instance (optional). Returns: @@ -118,12 +122,14 @@ def create_tenant( return tenant_mgt_service.create_tenant( display_name=display_name, allow_password_sign_up=allow_password_sign_up, enable_email_link_sign_in=enable_email_link_sign_in, - multi_factor_config=multi_factor_config,) + multi_factor_config=multi_factor_config, + email_privacy_config=email_privacy_config) def update_tenant( tenant_id, display_name=None, allow_password_sign_up=None, enable_email_link_sign_in=None, - multi_factor_config: MultiFactorConfig = None, app=None): + multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None, app=None): """Updates an existing tenant with the given options. Args: @@ -134,6 +140,7 @@ def update_tenant( enable_email_link_sign_in: A boolean indicating whether to enable or disable email link sign-in. Disabling this makes the password required for email sign-in. multi_factor_config : A multi factor configuration to update for the tenant (optional). + email_privacy_config: An email privacy configuration to update for the tenant (optional). app: An App instance (optional). Returns: @@ -148,7 +155,7 @@ def update_tenant( return tenant_mgt_service.update_tenant( tenant_id, display_name=display_name, allow_password_sign_up=allow_password_sign_up, enable_email_link_sign_in=enable_email_link_sign_in, - multi_factor_config=multi_factor_config) + multi_factor_config=multi_factor_config, email_privacy_config=email_privacy_config) def delete_tenant(tenant_id, app=None): @@ -244,6 +251,13 @@ def multi_factor_config(self): return MultiFactorServerConfig(data) return None + @property + def email_privacy_config(self): + data = self._data.get('emailPrivacyConfig') + if data: + return EmailPrivacyServerConfig(data) + return None + class _TenantManagementService: """Firebase tenant management service.""" @@ -290,7 +304,8 @@ def get_tenant(self, tenant_id): def create_tenant( self, display_name, allow_password_sign_up=None, enable_email_link_sign_in=None, - multi_factor_config: MultiFactorConfig = None): + multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None): """Creates a new tenant from the given parameters.""" payload = {'displayName': _validate_display_name(display_name)} @@ -305,6 +320,10 @@ def create_tenant( raise ValueError( 'multi_factor_config must be of type MultiFactorConfig.') payload['mfaConfig'] = multi_factor_config.build_server_request() + if email_privacy_config is not None: + if not isinstance(email_privacy_config, EmailPrivacyConfig): + raise ValueError('email_privacy_config must be of type EmailPrivacyConfig.') + payload['emailPrivacyConfig'] = email_privacy_config.build_server_request() try: body = self.client.body('post', '/tenants', json=payload) except requests.exceptions.RequestException as error: @@ -315,7 +334,8 @@ def create_tenant( def update_tenant( self, tenant_id, display_name=None, allow_password_sign_up=None, enable_email_link_sign_in=None, - multi_factor_config: MultiFactorConfig = None): + multi_factor_config: MultiFactorConfig = None, + email_privacy_config: EmailPrivacyConfig = None): """Updates the specified tenant with the given parameters.""" if not isinstance(tenant_id, str) or not tenant_id: raise ValueError('Tenant ID must be a non-empty string.') @@ -331,9 +351,12 @@ def update_tenant( enable_email_link_sign_in, 'enableEmailLinkSignin') if multi_factor_config is not None: if not isinstance(multi_factor_config, MultiFactorConfig): - raise ValueError( - 'multi_factor_config must be of type MultiFactorConfig.') + raise ValueError('multi_factor_config must be of type MultiFactorConfig.') payload['mfaConfig'] = multi_factor_config.build_server_request() + if email_privacy_config is not None: + if not isinstance(email_privacy_config, EmailPrivacyConfig): + raise ValueError('email_privacy_config must be of type EmailPrivacyConfig.') + payload['emailPrivacyConfig'] = email_privacy_config.build_server_request() if not payload: raise ValueError( diff --git a/integration/test_project_config_mgt.py b/integration/test_project_config_mgt.py index 52b86841e..5b4a2f9d9 100644 --- a/integration/test_project_config_mgt.py +++ b/integration/test_project_config_mgt.py @@ -14,8 +14,6 @@ """Integration tests for firebase_admin.project_config_mgt module.""" -import pytest - from firebase_admin.project_config_mgt import ProjectConfig from firebase_admin.project_config_mgt import get_project_config from firebase_admin.project_config_mgt import update_project_config @@ -23,23 +21,8 @@ from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig from firebase_admin.multi_factor_config_mgt import ProviderConfig from firebase_admin.multi_factor_config_mgt import TOTPProviderConfig - -ADJACENT_INTERVALS = 5 - -@pytest.fixture(scope='module') -def sample_mfa_config(): - mfa_config = { - 'providerConfigs': [ - { - 'state': 'ENABLED', - 'totpProviderConfig': { - 'adjacentIntervals': ADJACENT_INTERVALS - } - } - ] - } - return mfa_config - +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig def test_update_project_config(): mfa_object = MultiFactorConfig( @@ -52,14 +35,20 @@ def test_update_project_config(): ) ] ) - project_config = update_project_config(multi_factor_config=mfa_object) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) + project_config = update_project_config( + multi_factor_config=mfa_object, email_privacy_config=email_privacy_object) _assert_multi_factor_config(project_config.multi_factor_config) + _assert_email_privacy_config(project_config.email_privacy_config) def test_get_project(): project_config = get_project_config() assert isinstance(project_config, ProjectConfig) _assert_multi_factor_config(project_config.multi_factor_config) + _assert_email_privacy_config(project_config.email_privacy_config) def _assert_multi_factor_config(multi_factor_config): assert isinstance(multi_factor_config, MultiFactorServerConfig) @@ -72,4 +61,8 @@ def _assert_multi_factor_config(multi_factor_config): assert isinstance(provider_config.totp_provider_config, MultiFactorServerConfig.ProviderServerConfig .TOTPProviderServerConfig) - assert provider_config.totp_provider_config.adjacent_intervals == ADJACENT_INTERVALS + assert provider_config.totp_provider_config.adjacent_intervals == 5 + +def _assert_email_privacy_config(email_privacy_config): + assert isinstance(email_privacy_config, EmailPrivacyServerConfig) + assert email_privacy_config.enable_improved_email_privacy is True diff --git a/integration/test_tenant_mgt.py b/integration/test_tenant_mgt.py index 1766b2b1a..4dea01dd6 100644 --- a/integration/test_tenant_mgt.py +++ b/integration/test_tenant_mgt.py @@ -25,7 +25,12 @@ from firebase_admin import auth from firebase_admin import tenant_mgt -from firebase_admin import multi_factor_config_mgt +from firebase_admin.multi_factor_config_mgt import MultiFactorConfig +from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig +from firebase_admin.multi_factor_config_mgt import ProviderConfig +from firebase_admin.multi_factor_config_mgt import TOTPProviderConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig from integration import test_auth @@ -36,35 +41,44 @@ @pytest.fixture(scope='module') def sample_tenant(): - mfa_object = multi_factor_config_mgt.MultiFactorConfig( - provider_configs=[multi_factor_config_mgt.ProviderConfig( - state=multi_factor_config_mgt.ProviderConfig.State.ENABLED, - totp_provider_config=multi_factor_config_mgt.TOTPProviderConfig( + mfa_object = MultiFactorConfig( + provider_configs=[ProviderConfig( + state=ProviderConfig.State.ENABLED, + totp_provider_config=TOTPProviderConfig( adjacent_intervals=5 ) )] ) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) tenant = tenant_mgt.create_tenant( display_name='admin-python-tenant', allow_password_sign_up=True, enable_email_link_sign_in=True, - multi_factor_config=mfa_object) + multi_factor_config=mfa_object, + email_privacy_config=email_privacy_object) yield tenant tenant_mgt.delete_tenant(tenant.tenant_id) def _assert_multi_factor_config(mfa_config): - assert isinstance(mfa_config, multi_factor_config_mgt.MultiFactorServerConfig) + assert isinstance(mfa_config, MultiFactorServerConfig) assert len(mfa_config.provider_configs) == 1 assert isinstance(mfa_config.provider_configs, list) for provider_config in mfa_config.provider_configs: - assert isinstance(provider_config, multi_factor_config_mgt.MultiFactorServerConfig.\ + assert isinstance(provider_config, MultiFactorServerConfig.\ ProviderServerConfig) assert provider_config.state == 'ENABLED' assert isinstance(provider_config.totp_provider_config, - multi_factor_config_mgt.MultiFactorServerConfig.ProviderServerConfig + MultiFactorServerConfig.ProviderServerConfig .TOTPProviderServerConfig) assert provider_config.totp_provider_config.adjacent_intervals == 5 + +def _assert_email_privacy_config(email_privacy_config): + assert isinstance(email_privacy_config, EmailPrivacyServerConfig) + assert email_privacy_config.enable_improved_email_privacy is True + @pytest.fixture(scope='module') def tenant_user(sample_tenant): client = tenant_mgt.auth_for_tenant(sample_tenant.tenant_id) @@ -82,6 +96,7 @@ def test_get_tenant(sample_tenant): assert tenant.allow_password_sign_up is True assert tenant.enable_email_link_sign_in is True _assert_multi_factor_config(tenant.multi_factor_config) + _assert_email_privacy_config(tenant.email_privacy_config) def test_list_tenants(sample_tenant): @@ -99,17 +114,20 @@ def test_list_tenants(sample_tenant): def test_update_tenant(): - mfa_object = multi_factor_config_mgt.MultiFactorConfig( - provider_configs=[multi_factor_config_mgt.ProviderConfig( - state=multi_factor_config_mgt.ProviderConfig.State.ENABLED, - totp_provider_config=multi_factor_config_mgt.TOTPProviderConfig( + mfa_object = MultiFactorConfig( + provider_configs=[ProviderConfig( + state=ProviderConfig.State.ENABLED, + totp_provider_config=TOTPProviderConfig( adjacent_intervals=5 ) )] ) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) tenant = tenant_mgt.create_tenant( display_name='py-update-test', allow_password_sign_up=True, enable_email_link_sign_in=True, - multi_factor_config=mfa_object) + multi_factor_config=mfa_object, email_privacy_config=email_privacy_object) try: tenant = tenant_mgt.update_tenant( tenant.tenant_id, display_name='updated-py-tenant', allow_password_sign_up=False, @@ -120,6 +138,7 @@ def test_update_tenant(): assert tenant.allow_password_sign_up is False assert tenant.enable_email_link_sign_in is False _assert_multi_factor_config(tenant.multi_factor_config) + _assert_email_privacy_config(tenant.email_privacy_config) finally: tenant_mgt.delete_tenant(tenant.tenant_id) diff --git a/tests/test_email_privacy_config.py b/tests/test_email_privacy_config.py new file mode 100644 index 000000000..ea2efa2d7 --- /dev/null +++ b/tests/test_email_privacy_config.py @@ -0,0 +1,63 @@ +# Copyright 2023 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from copy import copy + +import pytest + +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig + +sample_email_privacy_config = EmailPrivacyConfig( + enable_improved_email_privacy=True, +) + + +class TestEmailPrivacyConfig: + def test_invalid_email_privacy_config_params(self): + test_config = copy(sample_email_privacy_config) + test_config.invalid_parameter = 'invalid' + with pytest.raises(ValueError) as excinfo: + test_config.build_server_request() + assert str(excinfo.value).startswith('"invalid_parameter" is not a valid' + ' "EmailPrivacyConfig" parameter.') + + @pytest.mark.parametrize('enable_improved_email_privacy', + [{}, 1, 0, list(), tuple(), dict()]) + def test_invalid_enable_improved_email_privacy_type(self, enable_improved_email_privacy): + test_config = copy(sample_email_privacy_config) + test_config.enable_improved_email_privacy = enable_improved_email_privacy + with pytest.raises(ValueError) as excinfo: + test_config.build_server_request() + assert str(excinfo.value).startswith('enable_improved_email_privacy must be a valid bool.') + + +class TestEmailPrivacyServerConfig: + def test_invalid_email_privacy_config_response(self): + test_config = 'invalid' + with pytest.raises(ValueError) as excinfo: + EmailPrivacyServerConfig(test_config) + assert str(excinfo.value).startswith('Invalid data argument in EmailPrivacyConfig' + ' constructor: {0}'.format(test_config)) + + def test_valid_server_response(self): + response = { + 'enableImprovedEmailPrivacy': True, + } + email_privacy_config = EmailPrivacyServerConfig(response) + _assert_email_privacy_config(email_privacy_config) + + +def _assert_email_privacy_config(email_privacy_config): + assert isinstance(email_privacy_config, EmailPrivacyServerConfig) + assert email_privacy_config.enable_improved_email_privacy is True diff --git a/tests/test_project_config_mgt.py b/tests/test_project_config_mgt.py index 8d4775315..10882c4ab 100644 --- a/tests/test_project_config_mgt.py +++ b/tests/test_project_config_mgt.py @@ -22,7 +22,12 @@ import firebase_admin from firebase_admin import project_config_mgt -from firebase_admin import multi_factor_config_mgt +from firebase_admin.multi_factor_config_mgt import MultiFactorConfig +from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig +from firebase_admin.multi_factor_config_mgt import ProviderConfig +from firebase_admin.multi_factor_config_mgt import TOTPProviderConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig ADJACENT_INTERVALS = 5 @@ -81,6 +86,9 @@ def test_project_config(self): } } ] + }, + 'emailPrivacyConfig': { + 'enableImprovedEmailPrivacy': True } } project_config = project_config_mgt.ProjectConfig(data) @@ -92,6 +100,7 @@ def test_project_optional_params(self): } project = project_config_mgt.ProjectConfig(data) assert project.multi_factor_config is None + assert project.email_privacy_config is None class TestGetProjectConfig: @@ -123,23 +132,37 @@ def test_invalid_multi_factor_config_type(self, multi_factor_config, project_con assert str(excinfo.value).startswith( 'multi_factor_config must be of type MultiFactorConfig.') + @pytest.mark.parametrize('email_privacy_config', ['a', 1, True, {}, dict(), list(), tuple()]) + def test_invalid_email_privacy_configs(self, email_privacy_config, project_config_mgt_app): + with pytest.raises(ValueError) as excinfo: + project_config_mgt.update_project_config(email_privacy_config=email_privacy_config, + app=project_config_mgt_app) + assert str(excinfo.value).startswith('email_privacy_config must be of type' + ' EmailPrivacyConfig.') + + def test_update_project_config(self, project_config_mgt_app): _, recorder = _instrument_project_config_mgt( project_config_mgt_app, 200, GET_PROJECT_RESPONSE) - mfa_object = multi_factor_config_mgt.MultiFactorConfig( + mfa_object = MultiFactorConfig( provider_configs=[ - multi_factor_config_mgt.ProviderConfig( - state=multi_factor_config_mgt.ProviderConfig.State.ENABLED, - totp_provider_config=multi_factor_config_mgt.TOTPProviderConfig( + ProviderConfig( + state=ProviderConfig.State.ENABLED, + totp_provider_config=TOTPProviderConfig( adjacent_intervals=ADJACENT_INTERVALS ) ) ] ) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) project_config = project_config_mgt.update_project_config( - multi_factor_config=mfa_object, app=project_config_mgt_app) + multi_factor_config=mfa_object, + email_privacy_config=email_privacy_object, + app=project_config_mgt_app) - mask = ['mfa.providerConfigs'] + mask = ['emailPrivacyConfig.enableImprovedEmailPrivacy', 'mfa.providerConfigs'] _assert_project_config(project_config) self._assert_request(recorder, { @@ -152,6 +175,9 @@ def test_update_project_config(self, project_config_mgt_app): } } ] + }, + 'emailPrivacyConfig': { + 'enableImprovedEmailPrivacy': True } }, mask) @@ -165,18 +191,24 @@ def _assert_request(self, recorder, body, mask): assert got == body def _assert_multi_factor_config(multi_factor_config): - assert isinstance(multi_factor_config, multi_factor_config_mgt.MultiFactorServerConfig) + assert isinstance(multi_factor_config, MultiFactorServerConfig) assert len(multi_factor_config.provider_configs) == 1 assert isinstance(multi_factor_config.provider_configs, list) for provider_config in multi_factor_config.provider_configs: - assert isinstance(provider_config, multi_factor_config_mgt.MultiFactorServerConfig + assert isinstance(provider_config, MultiFactorServerConfig .ProviderServerConfig) assert provider_config.state == 'ENABLED' assert isinstance(provider_config.totp_provider_config, - multi_factor_config_mgt.MultiFactorServerConfig.ProviderServerConfig + MultiFactorServerConfig.ProviderServerConfig .TOTPProviderServerConfig) assert provider_config.totp_provider_config.adjacent_intervals == ADJACENT_INTERVALS +def _assert_email_privacy_config(email_privacy_config): + assert isinstance(email_privacy_config, EmailPrivacyServerConfig) + assert email_privacy_config.enable_improved_email_privacy is True + def _assert_project_config(project_config): if project_config.multi_factor_config is not None: _assert_multi_factor_config(project_config.multi_factor_config) + if project_config.email_privacy_config is not None: + _assert_email_privacy_config(project_config.email_privacy_config) diff --git a/tests/test_tenant_mgt.py b/tests/test_tenant_mgt.py index 5e9963fec..4da48bd52 100644 --- a/tests/test_tenant_mgt.py +++ b/tests/test_tenant_mgt.py @@ -30,6 +30,8 @@ from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig from firebase_admin.multi_factor_config_mgt import ProviderConfig from firebase_admin.multi_factor_config_mgt import TOTPProviderConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyConfig +from firebase_admin.email_privacy_config_mgt import EmailPrivacyServerConfig from tests import testutils from tests import test_token_gen @@ -48,6 +50,9 @@ "adjacentIntervals": 5 } }] + }, + "emailPrivacyConfig": { + "enableImprovedEmailPrivacy": true } }""" @@ -190,6 +195,8 @@ def test_tenant_optional_params(self): assert tenant.display_name is None assert tenant.allow_password_sign_up is False assert tenant.enable_email_link_sign_in is False + assert tenant.multi_factor_config is None + assert tenant.email_privacy_config is None class TestGetTenant: @@ -254,10 +261,22 @@ def test_invalid_enable_email_link_sign_in(self, enable, tenant_mgt_app): def test_invalid_multi_factor_configs(self, multi_factor_config, tenant_mgt_app): with pytest.raises(ValueError) as excinfo: tenant_mgt.create_tenant( - display_name='test', multi_factor_config=multi_factor_config, app=tenant_mgt_app) + display_name='test', + multi_factor_config=multi_factor_config, + app=tenant_mgt_app) assert str(excinfo.value).startswith('multi_factor_config must be of type' ' MultiFactorConfig.') + @pytest.mark.parametrize('email_privacy_config', ['a', 1, True, {}, dict(), list(), tuple()]) + def test_invalid_email_privacy_configs(self, email_privacy_config, tenant_mgt_app): + with pytest.raises(ValueError) as excinfo: + tenant_mgt.create_tenant( + display_name='test', + email_privacy_config=email_privacy_config, + app=tenant_mgt_app) + assert str(excinfo.value).startswith('email_privacy_config must be of type' + ' EmailPrivacyConfig.') + def test_create_tenant(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) mfa_object = MultiFactorConfig( @@ -270,9 +289,13 @@ def test_create_tenant(self, tenant_mgt_app): ) ] ) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) tenant = tenant_mgt.create_tenant( display_name='My-Tenant', allow_password_sign_up=True, enable_email_link_sign_in=True, - multi_factor_config=mfa_object, app=tenant_mgt_app) + multi_factor_config=mfa_object, email_privacy_config=email_privacy_object, + app=tenant_mgt_app) _assert_tenant(tenant) self._assert_request(recorder, { @@ -289,6 +312,9 @@ def test_create_tenant(self, tenant_mgt_app): } ] }, + 'emailPrivacyConfig': { + 'enableImprovedEmailPrivacy': True + } }) def test_create_tenant_false_values(self, tenant_mgt_app): @@ -372,6 +398,14 @@ def test_invalid_multi_factor_configs(self, multi_factor_config, tenant_mgt_app) assert str(excinfo.value).startswith('multi_factor_config must be of type' ' MultiFactorConfig.') + @pytest.mark.parametrize('email_privacy_config', ['a', 1, True, {}, dict(), list(), tuple()]) + def test_invalid_email_privacy_configs(self, email_privacy_config, tenant_mgt_app): + with pytest.raises(ValueError) as excinfo: + tenant_mgt.update_tenant( + 'tenant-id', email_privacy_config=email_privacy_config, app=tenant_mgt_app) + assert str(excinfo.value).startswith('email_privacy_config must be of type' + ' EmailPrivacyConfig.') + def test_update_tenant_no_args(self, tenant_mgt_app): with pytest.raises(ValueError) as excinfo: tenant_mgt.update_tenant('tenant-id', app=tenant_mgt_app) @@ -389,10 +423,18 @@ def test_update_tenant(self, tenant_mgt_app): ) ] ) + email_privacy_object = EmailPrivacyConfig( + enable_improved_email_privacy=True + ) tenant = tenant_mgt.update_tenant( - 'tenant-id', display_name='My-Tenant', allow_password_sign_up=True, + tenant_id='tenant-id', + display_name='My-Tenant', + allow_password_sign_up=True, enable_email_link_sign_in=True, - multi_factor_config=mfa_object, app=tenant_mgt_app) + multi_factor_config=mfa_object, + email_privacy_config=email_privacy_object, + app=tenant_mgt_app + ) _assert_tenant(tenant) body = { @@ -408,17 +450,25 @@ def test_update_tenant(self, tenant_mgt_app): } } ] + }, + 'emailPrivacyConfig': { + 'enableImprovedEmailPrivacy': True, } } - mask = ['allowPasswordSignup', 'displayName', 'enableEmailLinkSignin', + mask = ['allowPasswordSignup', 'displayName', + 'emailPrivacyConfig.enableImprovedEmailPrivacy', + 'enableEmailLinkSignin', 'mfaConfig.providerConfigs'] self._assert_request(recorder, body, mask) def test_update_tenant_false_values(self, tenant_mgt_app): _, recorder = _instrument_tenant_mgt(tenant_mgt_app, 200, GET_TENANT_RESPONSE) tenant = tenant_mgt.update_tenant( - 'tenant-id', allow_password_sign_up=False, - enable_email_link_sign_in=False, app=tenant_mgt_app) + tenant_id='tenant-id', + allow_password_sign_up=False, + enable_email_link_sign_in=False, + app=tenant_mgt_app + ) _assert_tenant(tenant) body = { @@ -1080,6 +1130,10 @@ def _assert_multi_factor_config(mfa_config): .TOTPProviderServerConfig) assert provider_config.totp_provider_config.adjacent_intervals == ADJACENT_INTERVALS +def _assert_email_privacy_config(email_privacy_config): + assert isinstance(email_privacy_config, EmailPrivacyServerConfig) + assert email_privacy_config.enable_improved_email_privacy is True + def _assert_tenant(tenant, tenant_id='tenant-id'): assert isinstance(tenant, tenant_mgt.Tenant) assert tenant.tenant_id == tenant_id @@ -1088,3 +1142,5 @@ def _assert_tenant(tenant, tenant_id='tenant-id'): assert tenant.enable_email_link_sign_in is True if tenant.multi_factor_config is not None: _assert_multi_factor_config(mfa_config=tenant.multi_factor_config) + if tenant.email_privacy_config is not None: + _assert_email_privacy_config(email_privacy_config=tenant.email_privacy_config)