Skip to content

Commit 293bcea

Browse files
committed
oidclogout refactor: move validate_logout_request in view
1 parent 1012121 commit 293bcea

File tree

2 files changed

+64
-76
lines changed

2 files changed

+64
-76
lines changed

oauth2_provider/views/oidc.py

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -209,57 +209,6 @@ def _validate_claims(request, claims):
209209
return True
210210

211211

212-
def validate_logout_request(request, id_token_hint, client_id, post_logout_redirect_uri):
213-
"""
214-
Validate an OIDC RP-Initiated Logout Request.
215-
`(application, token_user)` is returned.
216-
217-
If it is set, `application` is the Application that is requesting the logout.
218-
`token_user` is the id_token user, which will used to revoke the tokens if found.
219-
220-
The `id_token_hint` will be validated if given. If both `client_id` and `id_token_hint` are given they
221-
will be validated against each other.
222-
"""
223-
224-
id_token = None
225-
if id_token_hint:
226-
# Only basic validation has been done on the IDToken at this point.
227-
id_token, claims = _load_id_token(id_token_hint)
228-
229-
if not id_token or not _validate_claims(request, claims):
230-
raise InvalidIDTokenError()
231-
232-
# If both id_token_hint and client_id are given it must be verified that they match.
233-
if client_id:
234-
if id_token.application.client_id != client_id:
235-
raise ClientIdMissmatch()
236-
237-
application = None
238-
# Determine the application that is requesting the logout.
239-
if client_id:
240-
application = get_application_model().objects.get(client_id=client_id)
241-
elif id_token:
242-
application = id_token.application
243-
244-
# Validate `post_logout_redirect_uri`
245-
if post_logout_redirect_uri:
246-
if not application:
247-
raise InvalidOIDCClientError()
248-
scheme = urlparse(post_logout_redirect_uri)[0]
249-
if not scheme:
250-
raise InvalidOIDCRedirectURIError("A Scheme is required for the redirect URI.")
251-
if oauth2_settings.OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS and (
252-
scheme == "http" and application.client_type != "confidential"
253-
):
254-
raise InvalidOIDCRedirectURIError("http is only allowed with confidential clients.")
255-
if scheme not in application.get_allowed_schemes():
256-
raise InvalidOIDCRedirectURIError(f'Redirect to scheme "{scheme}" is not permitted.')
257-
if not application.post_logout_redirect_uri_allowed(post_logout_redirect_uri):
258-
raise InvalidOIDCRedirectURIError("This client does not have this redirect uri registered.")
259-
260-
return application, id_token.user if id_token else None
261-
262-
263212
class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView):
264213
template_name = "oauth2_provider/logout_confirm.html"
265214
form_class = ConfirmLogoutForm
@@ -298,8 +247,7 @@ def get(self, request, *args, **kwargs):
298247
state = request.GET.get("state")
299248

300249
try:
301-
application, token_user = validate_logout_request(
302-
request=request,
250+
application, token_user = self.validate_logout_request(
303251
id_token_hint=id_token_hint,
304252
client_id=client_id,
305253
post_logout_redirect_uri=post_logout_redirect_uri,
@@ -330,8 +278,7 @@ def form_valid(self, form):
330278
state = form.cleaned_data.get("state")
331279

332280
try:
333-
application, token_user = validate_logout_request(
334-
request=self.request,
281+
application, token_user = self.validate_logout_request(
335282
id_token_hint=id_token_hint,
336283
client_id=client_id,
337284
post_logout_redirect_uri=post_logout_redirect_uri,
@@ -345,6 +292,56 @@ def form_valid(self, form):
345292
except OIDCError as error:
346293
return self.error_response(error)
347294

295+
def validate_logout_request(self, id_token_hint, client_id, post_logout_redirect_uri):
296+
"""
297+
Validate an OIDC RP-Initiated Logout Request.
298+
`(application, token_user)` is returned.
299+
300+
If it is set, `application` is the Application that is requesting the logout.
301+
`token_user` is the id_token user, which will used to revoke the tokens if found.
302+
303+
The `id_token_hint` will be validated if given. If both `client_id` and `id_token_hint` are given they
304+
will be validated against each other.
305+
"""
306+
307+
id_token = None
308+
if id_token_hint:
309+
# Only basic validation has been done on the IDToken at this point.
310+
id_token, claims = _load_id_token(id_token_hint)
311+
312+
if not id_token or not _validate_claims(self.request, claims):
313+
raise InvalidIDTokenError()
314+
315+
# If both id_token_hint and client_id are given it must be verified that they match.
316+
if client_id:
317+
if id_token.application.client_id != client_id:
318+
raise ClientIdMissmatch()
319+
320+
application = None
321+
# Determine the application that is requesting the logout.
322+
if client_id:
323+
application = get_application_model().objects.get(client_id=client_id)
324+
elif id_token:
325+
application = id_token.application
326+
327+
# Validate `post_logout_redirect_uri`
328+
if post_logout_redirect_uri:
329+
if not application:
330+
raise InvalidOIDCClientError()
331+
scheme = urlparse(post_logout_redirect_uri)[0]
332+
if not scheme:
333+
raise InvalidOIDCRedirectURIError("A Scheme is required for the redirect URI.")
334+
if oauth2_settings.OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS and (
335+
scheme == "http" and application.client_type != "confidential"
336+
):
337+
raise InvalidOIDCRedirectURIError("http is only allowed with confidential clients.")
338+
if scheme not in application.get_allowed_schemes():
339+
raise InvalidOIDCRedirectURIError(f'Redirect to scheme "{scheme}" is not permitted.')
340+
if not application.post_logout_redirect_uri_allowed(post_logout_redirect_uri):
341+
raise InvalidOIDCRedirectURIError("This client does not have this redirect uri registered.")
342+
343+
return application, id_token.user if id_token else None
344+
348345
def must_prompt(self, token_user):
349346
"""Indicate whether the logout has to be confirmed by the user. This happens if the
350347
specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`.

tests/test_oidc_views.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
RPInitiatedLogoutView,
1515
_load_id_token,
1616
_validate_claims,
17-
validate_logout_request,
1817
)
1918

2019
from . import presets
@@ -196,67 +195,59 @@ def test_validate_logout_request(oidc_tokens, public_application):
196195
application = oidc_tokens.application
197196
client_id = application.client_id
198197
id_token = oidc_tokens.id_token
199-
assert validate_logout_request(
200-
request=mock_request_for(oidc_tokens.user),
198+
view = RPInitiatedLogoutView()
199+
view.request = mock_request_for(oidc_tokens.user)
200+
assert view.validate_logout_request(
201201
id_token_hint=None,
202202
client_id=None,
203203
post_logout_redirect_uri=None,
204204
) == (None, None)
205-
assert validate_logout_request(
206-
request=mock_request_for(oidc_tokens.user),
205+
assert view.validate_logout_request(
207206
id_token_hint=None,
208207
client_id=client_id,
209208
post_logout_redirect_uri=None,
210209
) == (application, None)
211-
assert validate_logout_request(
212-
request=mock_request_for(oidc_tokens.user),
210+
assert view.validate_logout_request(
213211
id_token_hint=None,
214212
client_id=client_id,
215213
post_logout_redirect_uri="http://example.org",
216214
) == (application, None)
217-
assert validate_logout_request(
218-
request=mock_request_for(oidc_tokens.user),
215+
assert view.validate_logout_request(
219216
id_token_hint=id_token,
220217
client_id=None,
221218
post_logout_redirect_uri="http://example.org",
222219
) == (application, oidc_tokens.user)
223-
assert validate_logout_request(
224-
request=mock_request_for(oidc_tokens.user),
220+
assert view.validate_logout_request(
225221
id_token_hint=id_token,
226222
client_id=client_id,
227223
post_logout_redirect_uri="http://example.org",
228224
) == (application, oidc_tokens.user)
229225
with pytest.raises(ClientIdMissmatch):
230-
validate_logout_request(
231-
request=mock_request_for(oidc_tokens.user),
226+
view.validate_logout_request(
232227
id_token_hint=id_token,
233228
client_id=public_application.client_id,
234229
post_logout_redirect_uri="http://other.org",
235230
)
236231
with pytest.raises(InvalidOIDCClientError):
237-
validate_logout_request(
238-
request=mock_request_for(oidc_tokens.user),
232+
view.validate_logout_request(
239233
id_token_hint=None,
240234
client_id=None,
241235
post_logout_redirect_uri="http://example.org",
242236
)
243237
with pytest.raises(InvalidOIDCRedirectURIError):
244-
validate_logout_request(
245-
request=mock_request_for(oidc_tokens.user),
238+
view.validate_logout_request(
246239
id_token_hint=None,
247240
client_id=client_id,
248241
post_logout_redirect_uri="example.org",
249242
)
250243
with pytest.raises(InvalidOIDCRedirectURIError):
251-
validate_logout_request(
252-
request=mock_request_for(oidc_tokens.user),
244+
view.validate_logout_request(
253245
id_token_hint=None,
254246
client_id=client_id,
255247
post_logout_redirect_uri="imap://example.org",
256248
)
257249
with pytest.raises(InvalidOIDCRedirectURIError):
258-
validate_logout_request(
259-
request=mock_request_for(oidc_tokens.user),
250+
view.validate_logout_request(
260251
id_token_hint=None,
261252
client_id=client_id,
262253
post_logout_redirect_uri="http://other.org",

0 commit comments

Comments
 (0)