Skip to content

feat(auth): Added delete and list APIs for SAMLProviderConfig #441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions firebase_admin/_auth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,44 @@ def update_saml_provider_config(
x509_certificates=x509_certificates, rp_entity_id=rp_entity_id,
callback_url=callback_url, display_name=display_name, enabled=enabled)

def delete_saml_provider_config(self, provider_id):
"""Deletes the SAMLProviderConfig with the given ID.

Args:
provider_id: Provider ID string.

Raises:
ValueError: If the provider ID is invalid, empty or does not have ``saml.`` prefix.
ConfigurationNotFoundError: If no SAML provider is available with the given identifier.
FirebaseError: If an error occurs while deleting the SAML provider.
"""
self._provider_manager.delete_saml_provider_config(provider_id)

def list_saml_provider_configs(
self, page_token=None, max_results=_auth_providers.MAX_LIST_CONFIGS_RESULTS):
"""Retrieves a page of SAML provider configs from a Firebase project.

The ``page_token`` argument governs the starting point of the page. The ``max_results``
argument governs the maximum number of configs that may be included in the returned
page. This function never returns None. If there are no SAML configs in the Firebase
project, this returns an empty page.

Args:
page_token: A non-empty page token string, which indicates the starting point of the
page (optional). Defaults to ``None``, which will retrieve the first page of users.
max_results: A positive integer indicating the maximum number of users to include in
the returned page (optional). Defaults to 100, which is also the maximum number
allowed.

Returns:
ListProviderConfigsPage: A ListProviderConfigsPage instance.

Raises:
ValueError: If max_results or page_token are invalid.
FirebaseError: If an error occurs while retrieving the SAML provider configs.
"""
return self._provider_manager.list_saml_provider_configs(page_token, max_results)

def _check_jwt_revoked(self, verified_claims, exc_type, label):
user = self.get_user(verified_claims.get('uid'))
if verified_claims.get('iat') * 1000 < user.tokens_valid_after_timestamp:
Expand Down
94 changes: 94 additions & 0 deletions firebase_admin/_auth_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from firebase_admin import _user_mgt


MAX_LIST_CONFIGS_RESULTS = 100


class ProviderConfig:
"""Parent type for all authentication provider config types."""

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


class ListProviderConfigsPage:
"""Represents a page of AuthProviderConfig instances retrieved from a Firebase project.

Provides methods for traversing the provider configs included in this page, as well as
retrieving subsequent pages. The iterator returned by ``iterate_all()`` can be used to iterate
through all provider configs in the Firebase project starting from this page.
"""

def __init__(self, download, page_token, max_results):
self._download = download
self._max_results = max_results
self._current = download(page_token, max_results)

@property
def provider_configs(self):
"""A list of ``AuthProviderConfig`` instances available in this page."""
raise NotImplementedError

@property
def next_page_token(self):
"""Page token string for the next page (empty string indicates no more pages)."""
return self._current.get('nextPageToken', '')

@property
def has_next_page(self):
"""A boolean indicating whether more pages are available."""
return bool(self.next_page_token)

def get_next_page(self):
"""Retrieves the next page of provider configs, if available.

Returns:
ListProviderConfigsPage: Next page of provider configs, or None if this is the last
page.
"""
if self.has_next_page:
return self.__class__(self._download, self.next_page_token, self._max_results)
return None

def iterate_all(self):
"""Retrieves an iterator for provider configs.

Returned iterator will iterate through all the provider configs in the Firebase project
starting from this page. The iterator will never buffer more than one page of configs
in memory at a time.

Returns:
iterator: An iterator of AuthProviderConfig instances.
"""
return _ProviderConfigIterator(self)


class _ListSAMLProviderConfigsPage(ListProviderConfigsPage):

@property
def provider_configs(self):
return [SAMLProviderConfig(data) for data in self._current.get('inboundSamlConfigs', [])]


class _ProviderConfigIterator(_auth_utils.PageIterator):

@property
def items(self):
return self._current_page.provider_configs


class ProviderConfigClient:
"""Client for managing Auth provider configurations."""

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

def delete_saml_provider_config(self, provider_id):
_validate_saml_provider_id(provider_id)
self._make_request('delete', '/inboundSamlConfigs/{0}'.format(provider_id))

def list_saml_provider_configs(self, page_token=None, max_results=MAX_LIST_CONFIGS_RESULTS):
return _ListSAMLProviderConfigsPage(
self._fetch_saml_provider_configs, page_token, max_results)

def _fetch_saml_provider_configs(self, page_token=None, max_results=MAX_LIST_CONFIGS_RESULTS):
"""Fetches a page of SAML provider configs"""
if page_token is not None:
if not isinstance(page_token, str) or not page_token:
raise ValueError('Page token must be a non-empty string.')
if not isinstance(max_results, int):
raise ValueError('Max results must be an integer.')
if max_results < 1 or max_results > MAX_LIST_CONFIGS_RESULTS:
raise ValueError(
'Max results must be a positive integer less than or equal to '
'{0}.'.format(MAX_LIST_CONFIGS_RESULTS))

params = 'pageSize={0}'.format(max_results)
if page_token:
params += '&pageToken={0}'.format(page_token)
return self._make_request('get', '/inboundSamlConfigs', params=params)

def _make_request(self, method, path, **kwargs):
url = '{0}{1}'.format(self.base_url, path)
try:
Expand Down
36 changes: 36 additions & 0 deletions firebase_admin/_auth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,42 @@
VALID_EMAIL_ACTION_TYPES = set(['VERIFY_EMAIL', 'EMAIL_SIGNIN', 'PASSWORD_RESET'])


class PageIterator:
"""An iterator that allows iterating over a sequence of items, one at a time.

This implementation loads a page of items into memory, and iterates on them. When the whole
page has been traversed, it loads another page. This class never keeps more than one page
of entries in memory.
"""

def __init__(self, current_page):
if not current_page:
raise ValueError('Current page must not be None.')
self._current_page = current_page
self._index = 0

def next(self):
if self._index == len(self.items):
if self._current_page.has_next_page:
self._current_page = self._current_page.get_next_page()
self._index = 0
if self._index < len(self.items):
result = self.items[self._index]
self._index += 1
return result
raise StopIteration

@property
def items(self):
raise NotImplementedError

def __next__(self):
return self.next()

def __iter__(self):
return self


def validate_uid(uid, required=False):
if uid is None and not required:
return None
Expand Down
33 changes: 4 additions & 29 deletions firebase_admin/_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,33 +639,8 @@ def _make_request(self, method, path, **kwargs):
raise _auth_utils.handle_auth_backend_error(error)


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

This implementation loads a page of users into memory, and iterates on them. When the whole
page has been traversed, it loads another page. This class never keeps more than one page
of entries in memory.
"""

def __init__(self, current_page):
if not current_page:
raise ValueError('Current page must not be None.')
self._current_page = current_page
self._index = 0

def next(self):
if self._index == len(self._current_page.users):
if self._current_page.has_next_page:
self._current_page = self._current_page.get_next_page()
self._index = 0
if self._index < len(self._current_page.users):
result = self._current_page.users[self._index]
self._index += 1
return result
raise StopIteration

def __next__(self):
return self.next()

def __iter__(self):
return self
@property
def items(self):
return self._current_page.users
48 changes: 48 additions & 0 deletions firebase_admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
'InvalidDynamicLinkDomainError',
'InvalidIdTokenError',
'InvalidSessionCookieError',
'ListProviderConfigsPage',
'ListUsersPage',
'PhoneNumberAlreadyExistsError',
'ProviderConfig',
Expand All @@ -67,6 +68,7 @@
'create_saml_provider_config',
'create_session_cookie',
'create_user',
'delete_saml_provider_config',
'delete_user',
'generate_email_verification_link',
'generate_password_reset_link',
Expand All @@ -76,6 +78,7 @@
'get_user_by_email',
'get_user_by_phone_number',
'import_users',
'list_saml_provider_configs',
'list_users',
'revoke_refresh_tokens',
'set_custom_user_claims',
Expand All @@ -100,6 +103,7 @@
InvalidDynamicLinkDomainError = _auth_utils.InvalidDynamicLinkDomainError
InvalidIdTokenError = _auth_utils.InvalidIdTokenError
InvalidSessionCookieError = _token_gen.InvalidSessionCookieError
ListProviderConfigsPage = _auth_providers.ListProviderConfigsPage
ListUsersPage = _user_mgt.ListUsersPage
PhoneNumberAlreadyExistsError = _auth_utils.PhoneNumberAlreadyExistsError
ProviderConfig = _auth_providers.ProviderConfigClient
Expand Down Expand Up @@ -633,3 +637,47 @@ def update_saml_provider_config(
provider_id, idp_entity_id=idp_entity_id, sso_url=sso_url,
x509_certificates=x509_certificates, rp_entity_id=rp_entity_id,
callback_url=callback_url, display_name=display_name, enabled=enabled)


def delete_saml_provider_config(provider_id, app=None):
"""Deletes the SAMLProviderConfig with the given ID.

Args:
provider_id: Provider ID string.
app: An App instance (optional).

Raises:
ValueError: If the provider ID is invalid, empty or does not have ``saml.`` prefix.
ConfigurationNotFoundError: If no SAML provider is available with the given identifier.
FirebaseError: If an error occurs while deleting the SAML provider.
"""
client = _get_client(app)
client.delete_saml_provider_config(provider_id)


def list_saml_provider_configs(
page_token=None, max_results=_auth_providers.MAX_LIST_CONFIGS_RESULTS, app=None):
"""Retrieves a page of SAML provider configs from a Firebase project.

The ``page_token`` argument governs the starting point of the page. The ``max_results``
argument governs the maximum number of configs that may be included in the returned
page. This function never returns None. If there are no SAML configs in the Firebase
project, this returns an empty page.

Args:
page_token: A non-empty page token string, which indicates the starting point of the
page (optional). Defaults to ``None``, which will retrieve the first page of users.
max_results: A positive integer indicating the maximum number of users to include in
the returned page (optional). Defaults to 100, which is also the maximum number
allowed.
app: An App instance (optional).

Returns:
ListProviderConfigsPage: A ListProviderConfigsPage instance.

Raises:
ValueError: If max_results or page_token are invalid.
FirebaseError: If an error occurs while retrieving the SAML provider configs.
"""
client = _get_client(app)
return client.list_saml_provider_configs(page_token, max_results)
40 changes: 40 additions & 0 deletions tests/data/list_saml_provider_configs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"inboundSamlConfigs": [
{
"name": "projects/mock-project-id/inboundSamlConfigs/saml.provider0",
"idpConfig": {
"idpEntityId": "IDP_ENTITY_ID",
"ssoUrl": "https://example.com/login",
"signRequest": true,
"idpCertificates": [
{"x509Certificate": "CERT1"},
{"x509Certificate": "CERT2"}
]
},
"spConfig": {
"spEntityId": "RP_ENTITY_ID",
"callbackUri": "https://projectId.firebaseapp.com/__/auth/handler"
},
"displayName": "samlProviderName",
"enabled": true
},
{
"name": "projects/mock-project-id/inboundSamlConfigs/saml.provider1",
"idpConfig": {
"idpEntityId": "IDP_ENTITY_ID",
"ssoUrl": "https://example.com/login",
"signRequest": true,
"idpCertificates": [
{"x509Certificate": "CERT1"},
{"x509Certificate": "CERT2"}
]
},
"spConfig": {
"spEntityId": "RP_ENTITY_ID",
"callbackUri": "https://projectId.firebaseapp.com/__/auth/handler"
},
"displayName": "samlProviderName",
"enabled": true
}
]
}
Loading