Skip to content

Commit e9174e2

Browse files
authored
feat(auth): Added OIDCProviderConfig type and get/delete APIs (#442)
* feat(auth): Added OIDCProviderConfig type and get/delete APIs * Added newline to eof
1 parent 46b5be9 commit e9174e2

File tree

6 files changed

+202
-2
lines changed

6 files changed

+202
-2
lines changed

firebase_admin/_auth_client.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,35 @@ def generate_sign_in_with_email_link(self, email, action_code_settings):
390390
return self._user_manager.generate_email_action_link(
391391
'EMAIL_SIGNIN', email, action_code_settings=action_code_settings)
392392

393+
def get_oidc_provider_config(self, provider_id):
394+
"""Returns the OIDCProviderConfig with the given ID.
395+
396+
Args:
397+
provider_id: Provider ID string.
398+
399+
Returns:
400+
SAMLProviderConfig: An OIDCProviderConfig instance.
401+
402+
Raises:
403+
ValueError: If the provider ID is invalid, empty or does not have ``oidc.`` prefix.
404+
ConfigurationNotFoundError: If no OIDC provider is available with the given identifier.
405+
FirebaseError: If an error occurs while retrieving the OIDC provider.
406+
"""
407+
return self._provider_manager.get_oidc_provider_config(provider_id)
408+
409+
def delete_oidc_provider_config(self, provider_id):
410+
"""Deletes the OIDCProviderConfig with the given ID.
411+
412+
Args:
413+
provider_id: Provider ID string.
414+
415+
Raises:
416+
ValueError: If the provider ID is invalid, empty or does not have ``oidc.`` prefix.
417+
ConfigurationNotFoundError: If no OIDC provider is available with the given identifier.
418+
FirebaseError: If an error occurs while deleting the OIDC provider.
419+
"""
420+
self._provider_manager.delete_oidc_provider_config(provider_id)
421+
393422
def get_saml_provider_config(self, provider_id):
394423
"""Returns the SAMLProviderConfig with the given ID.
395424

firebase_admin/_auth_providers.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,26 @@ def enabled(self):
4545
return self._data['enabled']
4646

4747

48+
class OIDCProviderConfig(ProviderConfig):
49+
"""Represents the OIDC auth provider configuration.
50+
51+
See https://openid.net/specs/openid-connect-core-1_0-final.html.
52+
"""
53+
54+
@property
55+
def issuer(self):
56+
return self._data['issuer']
57+
58+
@property
59+
def client_id(self):
60+
return self._data['clientId']
61+
62+
4863
class SAMLProviderConfig(ProviderConfig):
4964
"""Represents he SAML auth provider configuration.
5065
51-
See http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html."""
66+
See http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html.
67+
"""
5268

5369
@property
5470
def idp_entity_id(self):
@@ -149,6 +165,15 @@ def __init__(self, http_client, project_id, tenant_id=None):
149165
if tenant_id:
150166
self.base_url += '/tenants/{0}'.format(tenant_id)
151167

168+
def get_oidc_provider_config(self, provider_id):
169+
_validate_oidc_provider_id(provider_id)
170+
body = self._make_request('get', '/oauthIdpConfigs/{0}'.format(provider_id))
171+
return OIDCProviderConfig(body)
172+
173+
def delete_oidc_provider_config(self, provider_id):
174+
_validate_oidc_provider_id(provider_id)
175+
self._make_request('delete', '/oauthIdpConfigs/{0}'.format(provider_id))
176+
152177
def get_saml_provider_config(self, provider_id):
153178
_validate_saml_provider_id(provider_id)
154179
body = self._make_request('get', '/inboundSamlConfigs/{0}'.format(provider_id))
@@ -253,6 +278,16 @@ def _make_request(self, method, path, **kwargs):
253278
raise _auth_utils.handle_auth_backend_error(error)
254279

255280

281+
def _validate_oidc_provider_id(provider_id):
282+
if not isinstance(provider_id, str):
283+
raise ValueError(
284+
'Invalid OIDC provider ID: {0}. Provider ID must be a non-empty string.'.format(
285+
provider_id))
286+
if not provider_id.startswith('oidc.'):
287+
raise ValueError('Invalid OIDC provider ID: {0}.'.format(provider_id))
288+
return provider_id
289+
290+
256291
def _validate_saml_provider_id(provider_id):
257292
if not isinstance(provider_id, str):
258293
raise ValueError(

firebase_admin/auth.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
InvalidSessionCookieError = _token_gen.InvalidSessionCookieError
106106
ListProviderConfigsPage = _auth_providers.ListProviderConfigsPage
107107
ListUsersPage = _user_mgt.ListUsersPage
108+
OIDCProviderConfig = _auth_providers.OIDCProviderConfig
108109
PhoneNumberAlreadyExistsError = _auth_utils.PhoneNumberAlreadyExistsError
109110
ProviderConfig = _auth_providers.ProviderConfigClient
110111
RevokedIdTokenError = _token_gen.RevokedIdTokenError
@@ -545,6 +546,41 @@ def generate_sign_in_with_email_link(email, action_code_settings, app=None):
545546
email, action_code_settings=action_code_settings)
546547

547548

549+
def get_oidc_provider_config(provider_id, app=None):
550+
"""Returns the OIDCProviderConfig with the given ID.
551+
552+
Args:
553+
provider_id: Provider ID string.
554+
app: An App instance (optional).
555+
556+
Returns:
557+
OIDCProviderConfig: An OIDCProviderConfig instance.
558+
559+
Raises:
560+
ValueError: If the provider ID is invalid, empty or does not have ``oidc.`` prefix.
561+
ConfigurationNotFoundError: If no OIDC provider is available with the given identifier.
562+
FirebaseError: If an error occurs while retrieving the OIDC provider.
563+
"""
564+
client = _get_client(app)
565+
return client.get_oidc_provider_config(provider_id)
566+
567+
568+
def delete_oidc_provider_config(provider_id, app=None):
569+
"""Deletes the OIDCProviderConfig with the given ID.
570+
571+
Args:
572+
provider_id: Provider ID string.
573+
app: An App instance (optional).
574+
575+
Raises:
576+
ValueError: If the provider ID is invalid, empty or does not have ``oidc.`` prefix.
577+
ConfigurationNotFoundError: If no OIDC provider is available with the given identifier.
578+
FirebaseError: If an error occurs while deleting the OIDC provider.
579+
"""
580+
client = _get_client(app)
581+
client.delete_oidc_provider_config(provider_id)
582+
583+
548584
def get_saml_provider_config(provider_id, app=None):
549585
"""Returns the SAMLProviderConfig with the given ID.
550586

tests/data/oidc_provider_config.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name":"projects/mock-project-id/oauthIdpConfigs/oidc.provider",
3+
"clientId": "CLIENT_ID",
4+
"issuer": "https://oidc.com/issuer",
5+
"displayName": "oidcProviderName",
6+
"enabled": true
7+
}

tests/test_auth_providers.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from tests import testutils
2626

2727
USER_MGT_URL_PREFIX = 'https://identitytoolkit.googleapis.com/v2beta1/projects/mock-project-id'
28+
OIDC_PROVIDER_CONFIG_RESPONSE = testutils.resource('oidc_provider_config.json')
2829
SAML_PROVIDER_CONFIG_RESPONSE = testutils.resource('saml_provider_config.json')
2930
LIST_SAML_PROVIDER_CONFIGS_RESPONSE = testutils.resource('list_saml_provider_configs.json')
3031

@@ -55,6 +56,64 @@ def _instrument_provider_mgt(app, status, payload):
5556
return recorder
5657

5758

59+
class TestOIDCProviderConfig:
60+
61+
@pytest.mark.parametrize('provider_id', INVALID_PROVIDER_IDS + ['saml.provider'])
62+
def test_get_invalid_provider_id(self, user_mgt_app, provider_id):
63+
with pytest.raises(ValueError) as excinfo:
64+
auth.get_oidc_provider_config(provider_id, app=user_mgt_app)
65+
66+
assert str(excinfo.value).startswith('Invalid OIDC provider ID')
67+
68+
def test_get(self, user_mgt_app):
69+
recorder = _instrument_provider_mgt(user_mgt_app, 200, OIDC_PROVIDER_CONFIG_RESPONSE)
70+
71+
provider_config = auth.get_oidc_provider_config('oidc.provider', app=user_mgt_app)
72+
73+
self._assert_provider_config(provider_config)
74+
assert len(recorder) == 1
75+
req = recorder[0]
76+
assert req.method == 'GET'
77+
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/oauthIdpConfigs/oidc.provider')
78+
79+
@pytest.mark.parametrize('provider_id', INVALID_PROVIDER_IDS + ['saml.provider'])
80+
def test_delete_invalid_provider_id(self, user_mgt_app, provider_id):
81+
with pytest.raises(ValueError) as excinfo:
82+
auth.delete_oidc_provider_config(provider_id, app=user_mgt_app)
83+
84+
assert str(excinfo.value).startswith('Invalid OIDC provider ID')
85+
86+
def test_delete(self, user_mgt_app):
87+
recorder = _instrument_provider_mgt(user_mgt_app, 200, '{}')
88+
89+
auth.delete_oidc_provider_config('oidc.provider', app=user_mgt_app)
90+
91+
assert len(recorder) == 1
92+
req = recorder[0]
93+
assert req.method == 'DELETE'
94+
assert req.url == '{0}{1}'.format(USER_MGT_URL_PREFIX, '/oauthIdpConfigs/oidc.provider')
95+
96+
def test_config_not_found(self, user_mgt_app):
97+
_instrument_provider_mgt(user_mgt_app, 500, CONFIG_NOT_FOUND_RESPONSE)
98+
99+
with pytest.raises(auth.ConfigurationNotFoundError) as excinfo:
100+
auth.get_oidc_provider_config('oidc.provider', app=user_mgt_app)
101+
102+
error_msg = 'No auth provider found for the given identifier (CONFIGURATION_NOT_FOUND).'
103+
assert excinfo.value.code == exceptions.NOT_FOUND
104+
assert str(excinfo.value) == error_msg
105+
assert excinfo.value.http_response is not None
106+
assert excinfo.value.cause is not None
107+
108+
def _assert_provider_config(self, provider_config, want_id='oidc.provider'):
109+
assert isinstance(provider_config, auth.OIDCProviderConfig)
110+
assert provider_config.provider_id == want_id
111+
assert provider_config.display_name == 'oidcProviderName'
112+
assert provider_config.enabled is True
113+
assert provider_config.issuer == 'https://oidc.com/issuer'
114+
assert provider_config.client_id == 'CLIENT_ID'
115+
116+
58117
class TestSAMLProviderConfig:
59118

60119
VALID_CREATE_OPTIONS = {

tests/test_tenant_mgt.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
MOCK_GET_USER_RESPONSE = testutils.resource('get_user.json')
7979
MOCK_LIST_USERS_RESPONSE = testutils.resource('list_users.json')
8080

81+
OIDC_PROVIDER_CONFIG_RESPONSE = testutils.resource('oidc_provider_config.json')
8182
SAML_PROVIDER_CONFIG_RESPONSE = testutils.resource('saml_provider_config.json')
8283
SAML_PROVIDER_CONFIG_REQUEST = body = {
8384
'displayName': 'samlProviderName',
@@ -715,6 +716,31 @@ def test_generate_sign_in_with_email_link(self, tenant_mgt_app):
715716
'continueUrl': 'http://localhost',
716717
})
717718

719+
def test_get_oidc_provider_config(self, tenant_mgt_app):
720+
client = tenant_mgt.auth_for_tenant('tenant-id', app=tenant_mgt_app)
721+
recorder = _instrument_provider_mgt(client, 200, OIDC_PROVIDER_CONFIG_RESPONSE)
722+
723+
provider_config = client.get_oidc_provider_config('oidc.provider')
724+
725+
self._assert_oidc_provider_config(provider_config)
726+
assert len(recorder) == 1
727+
req = recorder[0]
728+
assert req.method == 'GET'
729+
assert req.url == '{0}/tenants/tenant-id/oauthIdpConfigs/oidc.provider'.format(
730+
PROVIDER_MGT_URL_PREFIX)
731+
732+
def test_delete_oidc_provider_config(self, tenant_mgt_app):
733+
client = tenant_mgt.auth_for_tenant('tenant-id', app=tenant_mgt_app)
734+
recorder = _instrument_provider_mgt(client, 200, '{}')
735+
736+
client.delete_oidc_provider_config('oidc.provider')
737+
738+
assert len(recorder) == 1
739+
req = recorder[0]
740+
assert req.method == 'DELETE'
741+
assert req.url == '{0}/tenants/tenant-id/oauthIdpConfigs/oidc.provider'.format(
742+
PROVIDER_MGT_URL_PREFIX)
743+
718744
def test_get_saml_provider_config(self, tenant_mgt_app):
719745
client = tenant_mgt.auth_for_tenant('tenant-id', app=tenant_mgt_app)
720746
recorder = _instrument_provider_mgt(client, 200, SAML_PROVIDER_CONFIG_RESPONSE)
@@ -765,7 +791,7 @@ def test_update_saml_provider_config(self, tenant_mgt_app):
765791

766792
def test_delete_saml_provider_config(self, tenant_mgt_app):
767793
client = tenant_mgt.auth_for_tenant('tenant-id', app=tenant_mgt_app)
768-
recorder = _instrument_provider_mgt(client, 200, SAML_PROVIDER_CONFIG_RESPONSE)
794+
recorder = _instrument_provider_mgt(client, 200, '{}')
769795

770796
client.delete_saml_provider_config('saml.provider')
771797

@@ -822,6 +848,14 @@ def _assert_request(
822848
body = json.loads(req.body.decode())
823849
assert body == want_body
824850

851+
def _assert_oidc_provider_config(self, provider_config, want_id='oidc.provider'):
852+
assert isinstance(provider_config, auth.OIDCProviderConfig)
853+
assert provider_config.provider_id == want_id
854+
assert provider_config.display_name == 'oidcProviderName'
855+
assert provider_config.enabled is True
856+
assert provider_config.client_id == 'CLIENT_ID'
857+
assert provider_config.issuer == 'https://oidc.com/issuer'
858+
825859
def _assert_saml_provider_config(self, provider_config, want_id='saml.provider'):
826860
assert isinstance(provider_config, auth.SAMLProviderConfig)
827861
assert provider_config.provider_id == want_id

0 commit comments

Comments
 (0)