Skip to content

Commit 46b5be9

Browse files
authored
feat(auth): Added delete and list APIs for SAMLProviderConfig (#441)
* feat(auth): Added delete_saml_provider_config() API * Preliminary list provider config impl * Refactored the common paging logic into base classes * Added more tests for list API
1 parent 5427c1d commit 46b5be9

File tree

8 files changed

+455
-37
lines changed

8 files changed

+455
-37
lines changed

firebase_admin/_auth_client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,44 @@ def update_saml_provider_config(
476476
x509_certificates=x509_certificates, rp_entity_id=rp_entity_id,
477477
callback_url=callback_url, display_name=display_name, enabled=enabled)
478478

479+
def delete_saml_provider_config(self, provider_id):
480+
"""Deletes the SAMLProviderConfig with the given ID.
481+
482+
Args:
483+
provider_id: Provider ID string.
484+
485+
Raises:
486+
ValueError: If the provider ID is invalid, empty or does not have ``saml.`` prefix.
487+
ConfigurationNotFoundError: If no SAML provider is available with the given identifier.
488+
FirebaseError: If an error occurs while deleting the SAML provider.
489+
"""
490+
self._provider_manager.delete_saml_provider_config(provider_id)
491+
492+
def list_saml_provider_configs(
493+
self, page_token=None, max_results=_auth_providers.MAX_LIST_CONFIGS_RESULTS):
494+
"""Retrieves a page of SAML provider configs from a Firebase project.
495+
496+
The ``page_token`` argument governs the starting point of the page. The ``max_results``
497+
argument governs the maximum number of configs that may be included in the returned
498+
page. This function never returns None. If there are no SAML configs in the Firebase
499+
project, this returns an empty page.
500+
501+
Args:
502+
page_token: A non-empty page token string, which indicates the starting point of the
503+
page (optional). Defaults to ``None``, which will retrieve the first page of users.
504+
max_results: A positive integer indicating the maximum number of users to include in
505+
the returned page (optional). Defaults to 100, which is also the maximum number
506+
allowed.
507+
508+
Returns:
509+
ListProviderConfigsPage: A ListProviderConfigsPage instance.
510+
511+
Raises:
512+
ValueError: If max_results or page_token are invalid.
513+
FirebaseError: If an error occurs while retrieving the SAML provider configs.
514+
"""
515+
return self._provider_manager.list_saml_provider_configs(page_token, max_results)
516+
479517
def _check_jwt_revoked(self, verified_claims, exc_type, label):
480518
user = self.get_user(verified_claims.get('uid'))
481519
if verified_claims.get('iat') * 1000 < user.tokens_valid_after_timestamp:

firebase_admin/_auth_providers.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from firebase_admin import _user_mgt
2323

2424

25+
MAX_LIST_CONFIGS_RESULTS = 100
26+
27+
2528
class ProviderConfig:
2629
"""Parent type for all authentication provider config types."""
2730

@@ -69,6 +72,72 @@ def rp_entity_id(self):
6972
return self._data.get('spConfig', {})['spEntityId']
7073

7174

75+
class ListProviderConfigsPage:
76+
"""Represents a page of AuthProviderConfig instances retrieved from a Firebase project.
77+
78+
Provides methods for traversing the provider configs included in this page, as well as
79+
retrieving subsequent pages. The iterator returned by ``iterate_all()`` can be used to iterate
80+
through all provider configs in the Firebase project starting from this page.
81+
"""
82+
83+
def __init__(self, download, page_token, max_results):
84+
self._download = download
85+
self._max_results = max_results
86+
self._current = download(page_token, max_results)
87+
88+
@property
89+
def provider_configs(self):
90+
"""A list of ``AuthProviderConfig`` instances available in this page."""
91+
raise NotImplementedError
92+
93+
@property
94+
def next_page_token(self):
95+
"""Page token string for the next page (empty string indicates no more pages)."""
96+
return self._current.get('nextPageToken', '')
97+
98+
@property
99+
def has_next_page(self):
100+
"""A boolean indicating whether more pages are available."""
101+
return bool(self.next_page_token)
102+
103+
def get_next_page(self):
104+
"""Retrieves the next page of provider configs, if available.
105+
106+
Returns:
107+
ListProviderConfigsPage: Next page of provider configs, or None if this is the last
108+
page.
109+
"""
110+
if self.has_next_page:
111+
return self.__class__(self._download, self.next_page_token, self._max_results)
112+
return None
113+
114+
def iterate_all(self):
115+
"""Retrieves an iterator for provider configs.
116+
117+
Returned iterator will iterate through all the provider configs in the Firebase project
118+
starting from this page. The iterator will never buffer more than one page of configs
119+
in memory at a time.
120+
121+
Returns:
122+
iterator: An iterator of AuthProviderConfig instances.
123+
"""
124+
return _ProviderConfigIterator(self)
125+
126+
127+
class _ListSAMLProviderConfigsPage(ListProviderConfigsPage):
128+
129+
@property
130+
def provider_configs(self):
131+
return [SAMLProviderConfig(data) for data in self._current.get('inboundSamlConfigs', [])]
132+
133+
134+
class _ProviderConfigIterator(_auth_utils.PageIterator):
135+
136+
@property
137+
def items(self):
138+
return self._current_page.provider_configs
139+
140+
72141
class ProviderConfigClient:
73142
"""Client for managing Auth provider configurations."""
74143

@@ -151,6 +220,31 @@ def update_saml_provider_config(
151220
body = self._make_request('patch', url, json=req, params=params)
152221
return SAMLProviderConfig(body)
153222

223+
def delete_saml_provider_config(self, provider_id):
224+
_validate_saml_provider_id(provider_id)
225+
self._make_request('delete', '/inboundSamlConfigs/{0}'.format(provider_id))
226+
227+
def list_saml_provider_configs(self, page_token=None, max_results=MAX_LIST_CONFIGS_RESULTS):
228+
return _ListSAMLProviderConfigsPage(
229+
self._fetch_saml_provider_configs, page_token, max_results)
230+
231+
def _fetch_saml_provider_configs(self, page_token=None, max_results=MAX_LIST_CONFIGS_RESULTS):
232+
"""Fetches a page of SAML provider configs"""
233+
if page_token is not None:
234+
if not isinstance(page_token, str) or not page_token:
235+
raise ValueError('Page token must be a non-empty string.')
236+
if not isinstance(max_results, int):
237+
raise ValueError('Max results must be an integer.')
238+
if max_results < 1 or max_results > MAX_LIST_CONFIGS_RESULTS:
239+
raise ValueError(
240+
'Max results must be a positive integer less than or equal to '
241+
'{0}.'.format(MAX_LIST_CONFIGS_RESULTS))
242+
243+
params = 'pageSize={0}'.format(max_results)
244+
if page_token:
245+
params += '&pageToken={0}'.format(page_token)
246+
return self._make_request('get', '/inboundSamlConfigs', params=params)
247+
154248
def _make_request(self, method, path, **kwargs):
155249
url = '{0}{1}'.format(self.base_url, path)
156250
try:

firebase_admin/_auth_utils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,42 @@
3030
VALID_EMAIL_ACTION_TYPES = set(['VERIFY_EMAIL', 'EMAIL_SIGNIN', 'PASSWORD_RESET'])
3131

3232

33+
class PageIterator:
34+
"""An iterator that allows iterating over a sequence of items, one at a time.
35+
36+
This implementation loads a page of items into memory, and iterates on them. When the whole
37+
page has been traversed, it loads another page. This class never keeps more than one page
38+
of entries in memory.
39+
"""
40+
41+
def __init__(self, current_page):
42+
if not current_page:
43+
raise ValueError('Current page must not be None.')
44+
self._current_page = current_page
45+
self._index = 0
46+
47+
def next(self):
48+
if self._index == len(self.items):
49+
if self._current_page.has_next_page:
50+
self._current_page = self._current_page.get_next_page()
51+
self._index = 0
52+
if self._index < len(self.items):
53+
result = self.items[self._index]
54+
self._index += 1
55+
return result
56+
raise StopIteration
57+
58+
@property
59+
def items(self):
60+
raise NotImplementedError
61+
62+
def __next__(self):
63+
return self.next()
64+
65+
def __iter__(self):
66+
return self
67+
68+
3369
def validate_uid(uid, required=False):
3470
if uid is None and not required:
3571
return None

firebase_admin/_user_mgt.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -639,33 +639,8 @@ def _make_request(self, method, path, **kwargs):
639639
raise _auth_utils.handle_auth_backend_error(error)
640640

641641

642-
class _UserIterator:
643-
"""An iterator that allows iterating over user accounts, one at a time.
642+
class _UserIterator(_auth_utils.PageIterator):
644643

645-
This implementation loads a page of users into memory, and iterates on them. When the whole
646-
page has been traversed, it loads another page. This class never keeps more than one page
647-
of entries in memory.
648-
"""
649-
650-
def __init__(self, current_page):
651-
if not current_page:
652-
raise ValueError('Current page must not be None.')
653-
self._current_page = current_page
654-
self._index = 0
655-
656-
def next(self):
657-
if self._index == len(self._current_page.users):
658-
if self._current_page.has_next_page:
659-
self._current_page = self._current_page.get_next_page()
660-
self._index = 0
661-
if self._index < len(self._current_page.users):
662-
result = self._current_page.users[self._index]
663-
self._index += 1
664-
return result
665-
raise StopIteration
666-
667-
def __next__(self):
668-
return self.next()
669-
670-
def __iter__(self):
671-
return self
644+
@property
645+
def items(self):
646+
return self._current_page.users

firebase_admin/auth.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'InvalidDynamicLinkDomainError',
4747
'InvalidIdTokenError',
4848
'InvalidSessionCookieError',
49+
'ListProviderConfigsPage',
4950
'ListUsersPage',
5051
'PhoneNumberAlreadyExistsError',
5152
'ProviderConfig',
@@ -67,6 +68,7 @@
6768
'create_saml_provider_config',
6869
'create_session_cookie',
6970
'create_user',
71+
'delete_saml_provider_config',
7072
'delete_user',
7173
'generate_email_verification_link',
7274
'generate_password_reset_link',
@@ -76,6 +78,7 @@
7678
'get_user_by_email',
7779
'get_user_by_phone_number',
7880
'import_users',
81+
'list_saml_provider_configs',
7982
'list_users',
8083
'revoke_refresh_tokens',
8184
'set_custom_user_claims',
@@ -100,6 +103,7 @@
100103
InvalidDynamicLinkDomainError = _auth_utils.InvalidDynamicLinkDomainError
101104
InvalidIdTokenError = _auth_utils.InvalidIdTokenError
102105
InvalidSessionCookieError = _token_gen.InvalidSessionCookieError
106+
ListProviderConfigsPage = _auth_providers.ListProviderConfigsPage
103107
ListUsersPage = _user_mgt.ListUsersPage
104108
PhoneNumberAlreadyExistsError = _auth_utils.PhoneNumberAlreadyExistsError
105109
ProviderConfig = _auth_providers.ProviderConfigClient
@@ -633,3 +637,47 @@ def update_saml_provider_config(
633637
provider_id, idp_entity_id=idp_entity_id, sso_url=sso_url,
634638
x509_certificates=x509_certificates, rp_entity_id=rp_entity_id,
635639
callback_url=callback_url, display_name=display_name, enabled=enabled)
640+
641+
642+
def delete_saml_provider_config(provider_id, app=None):
643+
"""Deletes the SAMLProviderConfig with the given ID.
644+
645+
Args:
646+
provider_id: Provider ID string.
647+
app: An App instance (optional).
648+
649+
Raises:
650+
ValueError: If the provider ID is invalid, empty or does not have ``saml.`` prefix.
651+
ConfigurationNotFoundError: If no SAML provider is available with the given identifier.
652+
FirebaseError: If an error occurs while deleting the SAML provider.
653+
"""
654+
client = _get_client(app)
655+
client.delete_saml_provider_config(provider_id)
656+
657+
658+
def list_saml_provider_configs(
659+
page_token=None, max_results=_auth_providers.MAX_LIST_CONFIGS_RESULTS, app=None):
660+
"""Retrieves a page of SAML provider configs from a Firebase project.
661+
662+
The ``page_token`` argument governs the starting point of the page. The ``max_results``
663+
argument governs the maximum number of configs that may be included in the returned
664+
page. This function never returns None. If there are no SAML configs in the Firebase
665+
project, this returns an empty page.
666+
667+
Args:
668+
page_token: A non-empty page token string, which indicates the starting point of the
669+
page (optional). Defaults to ``None``, which will retrieve the first page of users.
670+
max_results: A positive integer indicating the maximum number of users to include in
671+
the returned page (optional). Defaults to 100, which is also the maximum number
672+
allowed.
673+
app: An App instance (optional).
674+
675+
Returns:
676+
ListProviderConfigsPage: A ListProviderConfigsPage instance.
677+
678+
Raises:
679+
ValueError: If max_results or page_token are invalid.
680+
FirebaseError: If an error occurs while retrieving the SAML provider configs.
681+
"""
682+
client = _get_client(app)
683+
return client.list_saml_provider_configs(page_token, max_results)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"inboundSamlConfigs": [
3+
{
4+
"name": "projects/mock-project-id/inboundSamlConfigs/saml.provider0",
5+
"idpConfig": {
6+
"idpEntityId": "IDP_ENTITY_ID",
7+
"ssoUrl": "https://example.com/login",
8+
"signRequest": true,
9+
"idpCertificates": [
10+
{"x509Certificate": "CERT1"},
11+
{"x509Certificate": "CERT2"}
12+
]
13+
},
14+
"spConfig": {
15+
"spEntityId": "RP_ENTITY_ID",
16+
"callbackUri": "https://projectId.firebaseapp.com/__/auth/handler"
17+
},
18+
"displayName": "samlProviderName",
19+
"enabled": true
20+
},
21+
{
22+
"name": "projects/mock-project-id/inboundSamlConfigs/saml.provider1",
23+
"idpConfig": {
24+
"idpEntityId": "IDP_ENTITY_ID",
25+
"ssoUrl": "https://example.com/login",
26+
"signRequest": true,
27+
"idpCertificates": [
28+
{"x509Certificate": "CERT1"},
29+
{"x509Certificate": "CERT2"}
30+
]
31+
},
32+
"spConfig": {
33+
"spEntityId": "RP_ENTITY_ID",
34+
"callbackUri": "https://projectId.firebaseapp.com/__/auth/handler"
35+
},
36+
"displayName": "samlProviderName",
37+
"enabled": true
38+
}
39+
]
40+
}

0 commit comments

Comments
 (0)