Skip to content
Next Next commit
feat(auth): Add Email Privacy support in Project and Tenant config
  • Loading branch information
pragatimodi committed Mar 17, 2023
commit 1b25d4be002b1347b56d2c7c38e8fbedc54b6e6f
86 changes: 86 additions & 0 deletions firebase_admin/email_privacy_config_mgt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 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.
"""
from enum import Enum
from typing import List

__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()
25 changes: 22 additions & 3 deletions firebase_admin/project_config_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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 import email_privacy_config_mgt

_PROJECT_CONFIG_MGT_ATTRIBUTE = '_project_config_mgt'

Expand Down Expand Up @@ -52,7 +53,9 @@ 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):
def update_project_config(multi_factor_config: MultiFactorConfig = None,
email_privacy_config: email_privacy_config_mgt.EmailPrivacyConfig = None,
app=None):
"""Update the Project Config with the given options.
Args:
multi_factor_config: Updated Multi Factor Authentication configuration
Expand All @@ -65,7 +68,8 @@ 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):
Expand All @@ -89,6 +93,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 email_privacy_config_mgt.EmailPrivacyServerConfig(data)
return None

class _ProjectConfigManagementService:
"""Firebase project management service."""

Expand All @@ -112,14 +123,22 @@ 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:
email_privacy_config_mgt.EmailPrivacyConfig = None) -> ProjectConfig:
"""Updates the specified project with the given parameters."""

payload = {}
if multi_factor_config is not 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, email_privacy_config_mgt.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.')
Expand Down
35 changes: 29 additions & 6 deletions firebase_admin/tenant_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from firebase_admin import _auth_utils
from firebase_admin import _http_client
from firebase_admin import _utils
from firebase_admin import email_privacy_config_mgt


_TENANT_MGT_ATTRIBUTE = '_tenant_mgt'
Expand Down Expand Up @@ -93,7 +94,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: multi_factor_config_mgt.MultiFactorConfig = None, app=None):
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None,
email_privacy_config: email_privacy_config_mgt.EmailPrivacyConfig = None, app=None):
"""Creates a new tenant from the given options.

Args:
Expand All @@ -104,6 +106,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:
Expand All @@ -117,12 +120,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: multi_factor_config_mgt.MultiFactorConfig = None, app=None):
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None,
email_privacy_config: email_privacy_config_mgt.EmailPrivacyConfig = None, app=None):
"""Updates an existing tenant with the given options.

Args:
Expand All @@ -133,6 +138,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:
Expand All @@ -147,7 +153,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):
Expand Down Expand Up @@ -240,6 +246,13 @@ def multi_factor_config(self):
if data is not None:
return multi_factor_config_mgt.MultiFactorServerConfig(data)
return None

@property
def email_privacy_config(self):
data = self._data.get('emailPrivacyConfig')
if data:
return email_privacy_config_mgt.EmailPrivacyServerConfig(data)
return None


class _TenantManagementService:
Expand Down Expand Up @@ -286,7 +299,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: multi_factor_config_mgt.MultiFactorConfig = None):
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None,
email_privacy_config: email_privacy_config_mgt.EmailPrivacyConfig = None):
"""Creates a new tenant from the given parameters."""

payload = {'displayName': _validate_display_name(display_name)}
Expand All @@ -301,6 +315,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, email_privacy_config_mgt.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:
Expand All @@ -311,7 +329,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: multi_factor_config_mgt.MultiFactorConfig = None):
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None,
email_privacy_config: email_privacy_config_mgt.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.')
Expand All @@ -329,6 +348,10 @@ def update_tenant(
if not isinstance(multi_factor_config, multi_factor_config_mgt.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, email_privacy_config_mgt.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.')
Expand Down