8000 feat(auth): enables OIDC auth code flow (#549) · Spirans/firebase-admin-python@008b1d8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 008b1d8

Browse files
feat(auth): enables OIDC auth code flow (firebase#549)
Provides an option for developers to specify the OAuth response type for their OIDC provider (either one of these can be set:): - id_token - code (if set, must also set the client secret) RELEASE NOTES: Added support for configuring the authorization code flow for OIDC providers.
1 parent 02596dc commit 008b1d8

File tree

5 files changed

+150
-16
lines changed

5 files changed

+150
-16
lines changed

firebase_admin/_auth_client.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,8 @@ def get_oidc_provider_config(self, provider_id):
514514
return self._provider_manager.get_oidc_provider_config(provider_id)
515515

516516
def create_oidc_provider_config(
517-
self, provider_id, client_id, issuer, display_name=None, enabled=None):
517+
self, provider_id, client_id, issuer, display_name=None, enabled=None,
< 8000 code>518+
client_secret=None, id_token_response_type=None, code_response_type=None):
518519
"""Creates a new OIDC provider config from the given parameters.
519520
520521
OIDC provider support requires Google Cloud's Identity Platform (GCIP). To learn more about
@@ -528,6 +529,16 @@ def create_oidc_provider_config(
528529
This name is also used as the provider label in the Cloud Console.
529530
enabled: A boolean indicating whether the provider configuration is enabled or disabled
530531
(optional). A user cannot sign in using a disabled provider.
532+
client_secret: A string which sets the client secret for the new provider.
533+
This is required for the code flow.
534+
code_response_type: A boolean which sets whether to enable the code response flow for
535+
the new provider. By default, this is not enabled if no response type is
536+
specified. A client secret must be set for this response type.
537+
Having both the code and ID token response flows is currently not supported.
538+
id_token_response_type: A boolean which sets whether to enable the ID token response
539+
flow for the new provider. By default, this is enabled if no response type is
540+
specified.
541+
Having both the code and ID token response flows is currently not supported.
531542
532543
Returns:
533544
OIDCProviderConfig: The newly created OIDC provider config instance.
@@ -538,10 +549,12 @@ def create_oidc_provider_config(
538549
"""
539550
return self._provider_manager.create_oidc_provider_config(
540551
provider_id, client_id=client_id, issuer=issuer, display_name=display_name,
541-
enabled=enabled)
552+
enabled=enabled, client_secret=client_secret,
553+
id_token_response_type=id_token_response_type, code_response_type=code_response_type)
542554

543555
def update_oidc_provider_config(
544-
self, provider_id, client_id=None, issuer=None, display_name=None, enabled=None):
556+
self, provider_id, client_id=None, issuer=None, display_name=None, enabled=None,
557+
client_secret=None, id_token_response_type=None, code_response_type=None):
545558
"""Updates an existing OIDC provider config with the given parameters.
546559
547560
Args:
@@ -552,6 +565,16 @@ def update_oidc_provider_config(
552565
Pass ``auth.DELETE_ATTRIBUTE`` to delete the current display name.
553566
enabled: A boolean indicating whether the provider configuration is enabled or disabled
554567
(optional).
568+
client_secret: A string which sets the client secret for the new provider.
569+
This is required for the code flow.
570+
code_response_type: A boolean which sets whether to enable the code response flow for
571+
the new provider. By default, this is not enabled if no response type is specified.
572+
A client secret must be set for this response type.
573+
Having both the code and ID token response flows is currently not supported.
574+
id_token_response_type: A boolean which sets whether to enable the ID token response
575+
flow for the new provider. By default, this is enabled if no response type is
576+
specified.
577+
Having both the code and ID token response flows is currently not supported.
555578
556579
Returns:
557580
OIDCProviderConfig: The updated OIDC provider config instance.
@@ -562,7 +585,8 @@ def update_oidc_provider_config(
562585
"""
563586
return self._provider_manager.update_oidc_provider_config(
564587
provider_id, client_id=client_id, issuer=issuer, display_name=display_name,
565-
enabled=enabled)
588+
enabled=enabled, client_secret=client_secret,
589+
id_token_response_type=id_token_response_type, code_response_type=code_response_type)
566590

567591
def delete_oidc_provider_config(self, provider_id):
568592
"""Deletes the ``OIDCProviderConfig`` with the given ID.

firebase_admin/_auth_providers.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ def issuer(self):
5959
def client_id(self):
6060
return self._data['clientId']
6161

62+
@property
63+
def client_secret(self):
64+
return self._data.get('clientSecret')
65+
66+
@property
67+
def id_token_response_type(self):
68+
return self._data.get('responseType', {}).get('idToken', False)
69+
70+
@property
71+
def code_response_type(self):
72+
return self._data.get('responseType', {}).get('code', False)
73+
6274

6375
class SAMLProviderConfig(ProviderConfig):
6476
"""Represents he SAML auth provider configuration.
@@ -179,7 +191,8 @@ def get_oidc_provider_config(self, provider_id):
179191
return OIDCProviderConfig(body)
180192

181193
def create_oidc_provider_config(
182-
self, provider_id, client_id, issuer, display_name=None, enabled=None):
194+
self, provider_id, client_id, issuer, display_name=None, enabled=None,
195+
client_secret=None, id_token_response_type=None, code_response_type=None):
183196
"""Creates a new OIDC provider config from the given parameters."""
184197
_validate_oidc_provider_id(provider_id)
185198
req = {
@@ -191,12 +204,28 @@ def create_oidc_provider_config(
191204
if enabled is not None:
192205
req['enabled'] = _auth_utils.validate_boolean(enabled, 'enabled')
193206

207+
response_type = {}
208+
if id_token_response_type is False and code_response_type is False:
209+
raise ValueError('At least one response type must be returned.')
210+
if id_token_response_type is not None:
211+
response_type['idToken'] = _auth_utils.validate_boolean(
212+
id_token_response_type, 'id_token_response_type')
213+
if code_response_type is not None:
214+
response_type['code'] = _auth_utils.validate_boolean(
215+
code_response_type, 'code_response_type')
216+
if code_response_type:
217+
req['clientSecret'] = _validate_non_empty_string(client_secret, 'client_secret')
218+
if response_type:
219+
req['responseType'] = response_type
220+
194221
params = 'oauthIdpConfigId={0}'.format(provider_id)
195222
body = self._make_request('post', '/oauthIdpConfigs', json=req, params=params)
196223
return OIDCProviderConfig(body)
197224

198225
def update_oidc_provider_config(
199-
self, provider_id, client_id=None, issuer=None, display_name=None, enabled=None):
226+
self, provider_id, client_id=None, issuer=None, display_name=None,
227+
enabled=None, client_secret=None, id_token_response_type=None,
228+
code_response_type=None):
200229
"""Updates an existing OIDC provider config with the given parameters."""
201230
_validate_oidc_provider_id(provider_id)
202231
req = {}
@@ -212,6 +241,20 @@ def update_oidc_provider_config(
212241
if issuer:
213242
req['issuer'] = _validate_url(issuer, 'issuer')
214243

244+
response_type = {}
245+
if id_token_response_type is False and code_response_type is False:
246+
raise ValueError('At least one response type must be returned.')
247+
if id_token_response_type is not None:
248+
response_type['idToken'] = _auth_utils.validate_boolean(
249+
id_token_response_type, 'id_token_response_type')
250+
if code_response_type is not None:
251+
response_type['code'] = _auth_utils.validate_boolean(
252+
code_response_type, 'code_response_type')
253+
if code_response_type:
254+
req['clientSecret'] = _validate_non_empty_string(client_secret, 'client_secret')
255+
if response_type:
256+
req['responseType'] = response_type
257+
215258
if not req:
216259
raise ValueError('At least one parameter must be specified for update.')
217260

firebase_admin/auth.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@ def get_oidc_provider_config(provider_id, app=None):
656656
return client.get_oidc_provider_config(provider_id)
657657

658658
def create_oidc_provider_config(
659-
provider_id, client_id, issuer, display_name=None, enabled=None, app=None):
659+
provider_id, client_id, issuer, display_name=None, enabled=None, client_secret=None,
660+
id_token_response_type=None, code_response_type=None, app=None):
660661
"""Creates a new OIDC provider config from the given parameters.
661662
662663
OIDC provider support requires Google Cloud's Identity Platform (GCIP). To learn more about
@@ -671,6 +672,15 @@ def create_oidc_provider_config(
671672
enabled: A boolean indicating whether the provider configuration is enabled or disabled
672673
(optional). A user cannot sign in using a disabled provider.
673674
app: An App instance (optional).
675+
client_secret: A string which sets the client secret for the new provider.
676+
This is required for the code flow.
677+
code_response_type: A boolean which sets whether to enable the code response flow for the
678+
new provider. By default, this is not enabled if no response type is specified.
679+
A client secret must be set for this response type.
680+
Having both the code and ID token response flows is currently not supported.
681+
id_token_response_type: A boolean which sets whether to enable the ID token response flow
682+
for the new provider. By default, this is enabled if no response type is specified.
683+
Having both the code and ID token response flows is currently not supported.
674684
675685
Returns:
676686
OIDCProviderConfig: The newly created OIDC provider config instance.
@@ -682,11 +692,13 @@ def create_oidc_provider_config(
682692
client = _get_client(app)
683693
return client.create_oidc_provider_config(
684694
provider_id, client_id=client_id, issuer=issuer, display_name=display_name,
685-
enabled=enabled)
695+
enabled=enabled, client_secret=client_secret, id_token_response_type=id_token_response_type,
696+
code_response_type=code_response_type)
686697

687698

688699
def update_oidc_provider_config(
689-
provider_id, client_id=None, issuer=None, display_name=None, enabled=None, app=None):
700+
provider_id, client_id=None, issuer=None, display_name=None, enabled=None,
701+
client_secret=None, id_token_response_type=None, code_response_type=None, app=None):
690702
"""Updates an existing OIDC provider config with the given parameters.
691703
692704
Args:
@@ -698,6 +710,15 @@ def update_oidc_provider_config(
698710
enabled: A boolean indicating whether the provider configuration is enabled or disabled
699711
(optional).
700712
app: An App instance (optional).
713+
client_secret: A string which sets the client secret for the new provider.
714+
This is required for the code flow.
715+
code_response_type: A boolean which sets whether to enable the code response flow for the
716+
new provider. By default, this is not enabled if no response type is specified.
717+
A client secret must be set for this response type.
718+
Having both the code and ID token response flows is currently not supported.
719+
id_token_response_type: A boolean which sets whether to enable the ID token response flow
720+
for the new provider. By default, this is enabled if no response type is specified.
721+
Having both the code and ID token response flows is currently not supported.
701722
702723
Returns:
703724
OIDCProviderConfig: The updated OIDC provider config instance.
@@ -709,7 +730,8 @@ def update_oidc_provider_config(
709730
client = _get_client(app)
710731
return client.update_oidc_provider_config(
711732
provider_id, client_id=client_id, issuer=issuer, display_name=display_name,
712-
enabled=enabled)
733+
enabled=enabled, client_secret=client_secret, id_token_response_type=id_token_response_type,
734+
code_response_type=code_response_type)
713735

714736

715737
def delete_oidc_provider_config(provider_id, app=None):

integration/test_auth.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,9 @@ def test_create_oidc_provider_config(oidc_provider):
736736
assert oidc_provider.issuer == 'https://oidc.com/issuer'
737737
assert oidc_provider.display_name == 'OIDC_DISPLAY_NAME'
738738
assert oidc_provider.enabled is True
739+
assert oidc_provider.response_type.id_token is True
740+
assert oidc_provider.response_type.code is False
741+
assert oidc_provider.client_secret is None
739742

740743

741744
def test_get_oidc_provider_config(oidc_provider):
@@ -746,6 +749,9 @@ def test_get_oidc_provider_config(oidc_provider):
746749
assert provider_config.issuer == 'https://oidc.com/issuer'
747750
assert provider_config.display_name == 'OIDC_DISPLAY_NAME'
748751
assert provider_config.enabled is True
752+
assert provider_config.response_type.id_token is True
753+
assert provider_config.response_type.code is False
754+
assert provider_config.client_secret is None
749755

750756

751757
def test_list_oidc_provider_configs(oidc_provider):
@@ -767,11 +773,17 @@ def test_update_oidc_provider_config():
767773
client_id='UPDATED_OIDC_CLIENT_ID',
768774
issuer='https://oidc.com/updated_issuer',
769775
display_name='UPDATED_OIDC_DISPLAY_NAME',
770-
enabled=False)
776+
enabled=False,
777+
client_secret='CLIENT_SECRET',
778+
id_token_response_type=False,
779+
code_response_type=True)
771780
assert provider_config.client_id == 'UPDATED_OIDC_CLIENT_ID'
772781
assert provider_config.issuer == 'https://oidc.com/updated_issuer'
773782
assert provider_config.display_name == 'UPDATED_OIDC_DISPLAY_NAME'
774783
assert provider_config.enabled is False
784+
assert provider_config.response_type.id_token is False
785+
assert provider_config.response_type.code is True
786+
assert provider_config.client_secret == 'CLIENT_SECRET'
775787
finally:
776788
auth.delete_oidc_provider_config(provider_config.provider_id)
777789

@@ -863,7 +875,9 @@ def _create_oidc_provider_config():
863875
client_id='OIDC_CLIENT_ID',
864876
issuer='https://oidc.com/issuer',
865877
display_name='OIDC_DISPLAY_NAME',
866-
enabled=True)
878+
enabled=True,
879+
id_token_response_type=True,
880+
code_response_type=False)
867881

868882

869883
def _create_saml_provider_config():

tests/test_auth_providers.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,21 @@ class TestOIDCProviderConfig:
7979
'issuer': 'https://oidc.com/issuer',
8080
'display_name': 'oidcProviderName',
8181
'enabled': True,
82+
'id_token_response_type': True,
83+
'code_response_type': True,
84+
'client_secret': 'CLIENT_SECRET',
8285
}
8386

8487
OIDC_CONFIG_REQUEST = {
8588
'displayName': 'oidcProviderName',
8689
'enabled': True,
8790
'clientId': 'CLIENT_ID',
91+
'clientSecret': 'CLIENT_SECRET',
8892
'issuer': 'https://oidc.com/issuer',
93+
'responseType': {
94+
'code': True,
95+
'idToken': True,
96+
},
8997
}
9098

9199
@pytest.mark.parametrize('provider_id', INVALID_PROVIDER_IDS + ['saml.provider'])
@@ -112,6 +120,11 @@ def test_get(self, user_mgt_app):
112120
{'issuer': None}, {'issuer': ''}, {'issuer': 'not a url'},
113121
{'display_name': True},
114122
{'enabled': 'true'},
123+
{'id_token_response_type': 'true'}, {'code_response_type': 'true'},
124+
{'code_response_type': True, 'client_secret': ''},
125+
{'code_response_type': True, 'client_secret': True},
126+
{'code_response_type': True, 'client_secret': None},
127+
{'code_response_type': False, 'id_token_response_type': False},
115128
])
116129
def test_create_invalid_args(self, user_mgt_app, invalid_opts):
117130
options = dict(self.VALID_CREATE_OPTIONS)
@@ -139,9 +152,14 @@ def test_create_minimal(self, user_mgt_app):
139152
options = dict(self.VALID_CREATE_OPTIONS)
140153
del options['display_name']
141154
del options['enabled']
155+
del options['client_secret']
156+
del options['id_token_response_type']
157+
del options['code_response_type']
142158
want = dict(self.OIDC_CONFIG_REQUEST)
143159
del want['displayName']
144160
del want['enabled']
161+
del want['clientSecret']
162+
del want['responseType']
145163

146164
provider_config = auth.create_oidc_provider_config(**options, app=user_mgt_app)
147165

@@ -159,9 +177,15 @@ def test_create_empty_values(self, user_mgt_app):
159177
options = dict(self.VALID_CREATE_OPTIONS)
160178
options['display_name'] = ''
161179
options['enabled'] = False
180+
options['code_response_type'] = False
162181
want = dict(self.OIDC_CONFIG_REQUEST)
163182
want['displayName'] = ''
164183
want['enabled'] = False
184+
want['responseType'] = {
185+
'code': False,
186+
'idToken': True,
187+
}
188+
del want['clientSecret']
165189

166190
provider_config = auth.create_oidc_provider_config(**options, app=user_mgt_app)
167191

@@ -181,6 +205,11 @@ def test_create_empty_values(self, user_mgt_app):
181205
{'issuer': ''}, {'issuer': 'not a url'},
182206
{'display_name': True},
183207
{'enabled': 'true'},
208+
{'id_token_response_type': 'true'}, {'code_response_type': 'true'},
209+
{'code_response_type': True, 'client_secret': ''},
210+
{'code_response_type': True, 'client_secret': True},
211+
{'code_response_type': True, 'client_secret': None},
212+
{'code_response_type': False, 'id_token_response_type': False},
184213
])
185214
def test_update_invalid_args(self, user_mgt_app, invalid_opts):
186215
options = {'provider_id': 'oidc.provider'}
@@ -198,7 +227,8 @@ def test_update(self, user_mgt_app):
198227
assert len(recorder) == 1
199228
req = recorder[0]
200229
assert req.method == 'PATCH'
201-
mask = ['clientId', 'displayName', 'enabled', 'issuer']
230+
mask = ['clientId', 'clientSecret', 'displayName', 'enabled', 'issuer',
231+
'responseType.code', 'responseType.idToken']
202232
assert req.url == '{0}/oauthIdpConfigs/oidc.provider?updateMask={1}'.format(
203233
USER_MGT_URLS['PREFIX'], ','.join(mask))
204234
got = json.loads(req.body.decode())
@@ -223,17 +253,18 @@ def test_update_empty_values(self, user_mgt_app):
223253
recorder = _instrument_provider_mgt(user_mgt_app, 200, OIDC_PROVIDER_CONFIG_RESPONSE)
224254

225255
provider_config = auth.update_oidc_provider_config(
226-
'oidc.provider', display_name=auth.DELETE_ATTRIBUTE, enabled=False, app=user_mgt_app)
256+
'oidc.provider', display_name=auth.DELETE_ATTRIBUTE, enabled=False,
257+
id_token_response_type=False, app=user_mgt_app)
227258

228259
self._assert_provider_config(provider_config)
229260
assert len(recorder) == 1
230261
req = recorder[0]
231262
assert req.method == 'PATCH'
232-
mask = ['displayName', 'enabled']
263+
mask = ['displayName', 'enabled', 'responseType.idToken']
233264
assert req.url == '{0}/oauthIdpConfigs/oidc.provider?updateMask={1}'.format(
234265
USER_MGT_URLS['PREFIX'], ','.join(mask))
235266
got = json.loads(req.body.decode())
236-
assert got == {'displayName': None, 'enabled': False}
267+
assert got == {'displayName': None, 'enabled': False, 'responseType': {'idToken': False}}
237268

238269
@pytest.mark.parametrize('provider_id', INVALID_PROVIDER_IDS + ['saml.provider'])
239270
def test_delete_invalid_provider_id(self, user_mgt_app, provider_id):

0 commit comments

Comments
 (0)
0