Skip to content

Commit c002232

Browse files
committed
1. Added a project_config_mgt file
2. Unit tests for project_config_mgt 3. Formatting changes
1 parent b625e13 commit c002232

File tree

4 files changed

+344
-30
lines changed

4 files changed

+344
-30
lines changed

firebase_admin/project_config_mgt.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Copyright 2023 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Firebase project configuration management module.
15+
16+
This module contains functions for managing various project operations like update and create
17+
"""
18+
19+
import requests
20+
21+
import firebase_admin
22+
from firebase_admin import _auth_utils
23+
from firebase_admin import _http_client
24+
from firebase_admin import _utils
25+
from firebase_admin.multi_factor_config_mgt import MultiFactorConfig
26+
from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig
27+
28+
_PROJECT_CONFIG_MGT_ATTRIBUTE = '_project_config_mgt'
29+
30+
__all__ = [
31+
'ProjectConfig',
32+
33+
'get_project_config',
34+
'update_project_config',
35+
]
36+
37+
38+
def get_project_config(app=None):
39+
"""Gets the project config corresponding to the given project_id.
40+
41+
Args:
42+
app: An App instance (optional).
43+
44+
Returns:
45+
Project: A project object.
46+
47+
Raises:
48+
ValueError: If the project ID is None, empty or not a string.
49+
ProjectNotFoundError: If no project exists by the given ID.
50+
FirebaseError: If an error occurs while retrieving the project.
51+
"""
52+
project_config_mgt_service = _get_project_config_mgt_service(app)
53+
return project_config_mgt_service.get_project_config()
54+
55+
def update_project_config(multi_factor_config: MultiFactorConfig = None, app=None):
56+
"""Update the Project Config with the given options.
57+
Args:
58+
multi_factor_config: Updated Multi Factor Authentication configuration
59+
(optional)
60+
app: An App instance (optional).
61+
Returns:
62+
Project: An updated ProjectConfig object.
63+
Raises:
64+
ValueError: If any of the given arguments are invalid.
65+
FirebaseError: If an error occurs while updating the project.
66+
"""
67+
project_config_mgt_service = _get_project_config_mgt_service(app)
68+
return project_config_mgt_service.update_project_config(multi_factor_config=multi_factor_config)
69+
70+
71+
def _get_project_config_mgt_service(app):
72+
return _utils.get_app_service(app, _PROJECT_CONFIG_MGT_ATTRIBUTE,
73+
_ProjectConfigManagementService)
74+
75+
class ProjectConfig:
76+
"""Represents a project config in an application.
77+
"""
78+
79+
def __init__(self, data):
80+
if not isinstance(data, dict):
81+
raise ValueError(
82+
'Invalid data argument in Project constructor: {0}'.format(data))
83+
self._data = data
84+
85+
@property
86+
def multi_factor_config(self):
87+
data = self._data.get('mfa')
88+
if data:
89+
return MultiFactorServerConfig(data)
90+
return None
91+
92+
class _ProjectConfigManagementService:
93+
"""Firebase project management service."""
94+
95+
PROJECT_CONFIG_MGT_URL = 'https://identitytoolkit.googleapis.com/v2/projects'
96+
97+
def __init__(self, app):
98+
credential = app.credential.get_credential()
99+
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
100+
base_url = '{0}/{1}/config'.format(
101+
self.PROJECT_CONFIG_MGT_URL, app.project_id)
102+
self.app = app
103+
self.client = _http_client.JsonHttpClient(
104+
credential=credential, base_url=base_url, headers={'X-Client-Version': version_header})
105+
106+
def get_project_config(self) -> ProjectConfig:
107+
"""Gets the project config"""
108+
try:
109+
body = self.client.body('get', url='')
110+
except requests.exceptions.RequestException as error:
111+
raise _auth_utils.handle_auth_backend_error(error)
112+
else:
113+
return ProjectConfig(body)
114+
115+
def update_project_config(self, multi_factor_config: MultiFactorConfig = None) -> ProjectConfig:
116+
"""Updates the specified project with the given parameters."""
117+
118+
payload = {}
119+
if multi_factor_config is not None:
120+
if not isinstance(multi_factor_config, MultiFactorConfig):
121+
raise ValueError('multi_factor_config must be of type MultiFactorConfig.')
122+
payload['mfa'] = multi_factor_config.build_server_request()
123+
if not payload:
124+
raise ValueError(
125+
'At least one parameter must be specified for update.')
126+
127+
update_mask = ','.join(_auth_utils.build_update_mask(payload))
128+
params = 'updateMask={0}'.format(update_mask)
129+
try:
130+
body = self.client.body(
131+
'patch', url='', json=payload, params=params)
132+
except requests.exceptions.RequestException as error:
133+
raise _auth_utils.handle_auth_backend_error(error)
134+
else:
135+
return ProjectConfig(body)

firebase_admin/tenant_mgt.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525

2626
import firebase_admin
2727
from firebase_admin import auth
28+
from firebase_admin import multi_factor_config_mgt
2829
from firebase_admin import _auth_utils
2930
from firebase_admin import _http_client
3031
from firebase_admin import _utils
31-
from firebase_admin.multi_factor_config_mgt import MultiFactorConfig, MultiFactorServerConfig
3232

3333

3434
_TENANT_MGT_ATTRIBUTE = '_tenant_mgt'
@@ -93,7 +93,7 @@ def get_tenant(tenant_id, app=None):
9393

9494
def create_tenant(
9595
display_name, allow_password_sign_up=None, enable_email_link_sign_in=None,
96-
multi_factor_config: MultiFactorConfig = None, app=None):
96+
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None, app=None):
9797
"""Creates a new tenant from the given options.
9898
9999
Args:
@@ -122,7 +122,7 @@ def create_tenant(
122122

123123
def update_tenant(
124124
tenant_id, display_name=None, allow_password_sign_up=None, enable_email_link_sign_in=None,
125-
multi_factor_config: MultiFactorConfig = None, app=None):
125+
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None, app=None):
126126
"""Updates an existing tenant with the given options.
127127
128128
Args:
@@ -238,7 +238,7 @@ def enable_email_link_sign_in(self):
238238
def multi_factor_config(self):
239239
data = self._data.get('mfaConfig', None)
240240
if data is not None:
241-
return MultiFactorServerConfig(data)
241+
return multi_factor_config_mgt.MultiFactorServerConfig(data)
242242
return None
243243

244244

@@ -286,7 +286,7 @@ def get_tenant(self, tenant_id):
286286

287287
def create_tenant(
288288
self, display_name, allow_password_sign_up=None, enable_email_link_sign_in=None,
289-
multi_factor_config: MultiFactorConfig = None):
289+
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None):
290290
"""Creates a new tenant from the given parameters."""
291291

292292
payload = {'displayName': _validate_display_name(display_name)}
@@ -297,7 +297,7 @@ def create_tenant(
297297
payload['enableEmailLinkSignin'] = _auth_utils.validate_boolean(
298298
enable_email_link_sign_in, 'enableEmailLinkSignin')
299299
if multi_factor_config is not None:
300-
if not isinstance(multi_factor_config, MultiFactorConfig):
300+
if not isinstance(multi_factor_config, multi_factor_config_mgt.MultiFactorConfig):
301301
raise ValueError(
302302
'multi_factor_config must be of type MultiFactorConfig.')
303303
payload['mfaConfig'] = multi_factor_config.build_server_request()
@@ -311,7 +311,7 @@ def create_tenant(
311311
def update_tenant(
312312
self, tenant_id, display_name=None, allow_password_sign_up=None,
313313
enable_email_link_sign_in=None,
314-
multi_factor_config: MultiFactorConfig = None):
314+
multi_factor_config: multi_factor_config_mgt.MultiFactorConfig = None):
315315
"""Updates the specified tenant with the given parameters."""
316316
if not isinstance(tenant_id, str) or not tenant_id:
317317
raise ValueError('Tenant ID must be a non-empty string.')
@@ -326,9 +326,8 @@ def update_tenant(
326326
payload['enableEmailLinkSignin'] = _auth_utils.validate_boolean(
327327
enable_email_link_sign_in, 'enableEmailLinkSignin')
328328
if multi_factor_config is not None:
329-
if not isinstance(multi_factor_config, MultiFactorConfig):
330-
raise ValueError(
331-
'multi_factor_config must be of type MultiFactorConfig.')
329+
if not isinstance(multi_factor_config, multi_factor_config_mgt.MultiFactorConfig):
330+
raise ValueError('multi_factor_config must be of type MultiFactorConfig.')
332331
payload['mfaConfig'] = multi_factor_config.build_server_request()
333332

334333
if not payload:

tests/test_multi_factor_config.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from copy import copy
15+
1516
import pytest
16-
from firebase_admin.multi_factor_config_mgt import MultiFactorConfig
17-
from firebase_admin.multi_factor_config_mgt import ProviderConfig
18-
from firebase_admin.multi_factor_config_mgt import TOTPProviderConfig
19-
from firebase_admin.multi_factor_config_mgt import MultiFactorServerConfig
20-
21-
sample_mfa_config = MultiFactorConfig(
22-
provider_configs=[ProviderConfig(
23-
state=ProviderConfig.State.ENABLED,
24-
totp_provider_config=TOTPProviderConfig(
17+
18+
from firebase_admin import multi_factor_config_mgt
19+
20+
sample_mfa_config = multi_factor_config_mgt.MultiFactorConfig(
21+
provider_configs=[multi_factor_config_mgt.ProviderConfig(
22+
state=multi_factor_config_mgt.ProviderConfig.State.ENABLED,
23+
totp_provider_config=multi_factor_config_mgt.TOTPProviderConfig(
2524
adjacent_intervals=5
2625
)
2726
)]
@@ -79,7 +78,7 @@ def test_undefined_provider_config_state(self):
7978
@pytest.mark.parametrize('state',
8079
['', 1, True, False, [], (), {}, "foo", 'ENABLED'])
8180
def test_invalid_provider_config_state(self, state):
82-
test_config = ProviderConfig(
81+
test_config = multi_factor_config_mgt.ProviderConfig(
8382
state=state
8483
)
8584
with pytest.raises(ValueError) as excinfo:
@@ -88,9 +87,10 @@ def test_invalid_provider_config_state(self, state):
8887
' ProviderConfig.State.')
8988

9089
@pytest.mark.parametrize('state',
91-
[ProviderConfig.State.ENABLED, ProviderConfig.State.DISABLED])
90+
[multi_factor_config_mgt.ProviderConfig.State.ENABLED,
91+
multi_factor_config_mgt.ProviderConfig.State.DISABLED])
9292
def test_undefined_totp_provider_config(self, state):
93-
test_config = ProviderConfig(state=state)
93+
test_config = multi_factor_config_mgt.ProviderConfig(state=state)
9494
with pytest.raises(ValueError) as excinfo:
9595
test_config.build_server_request()
9696
assert str(excinfo.value).startswith('provider_config.totp_provider_config must be'
@@ -134,22 +134,22 @@ class TestMultiFactorServerConfig:
134134
def test_invalid_multi_factor_config_response(self):
135135
test_config = 'invalid'
136136
with pytest.raises(ValueError) as excinfo:
137-
MultiFactorServerConfig(test_config)
137+
multi_factor_config_mgt.MultiFactorServerConfig(test_config)
138138
assert str(excinfo.value).startswith('Invalid data argument in MultiFactorConfig'
139139
' constructor: {0}'.format(test_config))
140140

141141
def test_invalid_provider_config_response(self):
142142
test_config = 'invalid'
143143
with pytest.raises(ValueError) as excinfo:
144-
MultiFactorServerConfig.ProviderConfigServerConfig(test_config)
144+
multi_factor_config_mgt.MultiFactorServerConfig.ProviderConfigServerConfig(test_config)
145145
assert str(excinfo.value).startswith('Invalid data argument in ProviderConfig'
146146
' constructor: {0}'.format(test_config))
147147

148148
def test_invalid_totp_provider_config_response(self):
149149
test_config = 'invalid'
150150
with pytest.raises(ValueError) as excinfo:
151-
MultiFactorServerConfig.ProviderConfigServerConfig.TOTPProviderServerConfig(
152-
test_config)
151+
multi_factor_config_mgt.MultiFactorServerConfig.ProviderConfigServerConfig.\
152+
TOTPProviderServerConfig(test_config)
153153
assert str(excinfo.value).startswith('Invalid data argument in TOTPProviderConfig'
154154
' constructor: {0}'.format(test_config))
155155

@@ -162,20 +162,20 @@ def test_valid_server_response(self):
162162
}
163163
}]
164164
}
165-
mfa_config = MultiFactorServerConfig(response)
165+
mfa_config = multi_factor_config_mgt.MultiFactorServerConfig(response)
166166
_assert_multi_factor_config(mfa_config)
167167

168168

169169
def _assert_multi_factor_config(mfa_config):
170-
assert isinstance(mfa_config, MultiFactorServerConfig)
170+
assert isinstance(mfa_config, multi_factor_config_mgt.MultiFactorServerConfig)
171171
assert len(mfa_config.provider_configs) == 1
172172
assert isinstance(mfa_config.provider_configs, list)
173173
for provider_config in mfa_config.provider_configs:
174174
assert isinstance(
175175
provider_config,
176-
MultiFactorServerConfig.ProviderConfigServerConfig)
176+
multi_factor_config_mgt.MultiFactorServerConfig.ProviderConfigServerConfig)
177177
assert provider_config.state == 'ENABLED'
178178
assert isinstance(provider_config.totp_provider_config,
179-
MultiFactorServerConfig.ProviderConfigServerConfig
179+
multi_factor_config_mgt.MultiFactorServerConfig.ProviderConfigServerConfig
180180
.TOTPProviderServerConfig)
181181
assert provider_config.totp_provider_config.adjacent_intervals == 5

0 commit comments

Comments
 (0)