Skip to content

Commit 1012121

Browse files
tonialn2ygk
authored andcommitted
logout refactor : split prompt checks from logout request validation
1 parent 993e0c7 commit 1012121

File tree

2 files changed

+46
-39
lines changed

2 files changed

+46
-39
lines changed

oauth2_provider/views/oidc.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -212,44 +212,28 @@ def _validate_claims(request, claims):
212212
def validate_logout_request(request, id_token_hint, client_id, post_logout_redirect_uri):
213213
"""
214214
Validate an OIDC RP-Initiated Logout Request.
215-
`(prompt_logout, application, token_user)` is returned.
215+
`(application, token_user)` is returned.
216216
217-
`prompt_logout` indicates whether the logout has to be confirmed by the user. This happens if the
218-
specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`.
219-
If it is set `application` will also
220-
be set to the Application that is requesting the logout. `token_user` is the id_token user, which will
221-
used to revoke the tokens if found.
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.
222219
223220
The `id_token_hint` will be validated if given. If both `client_id` and `id_token_hint` are given they
224221
will be validated against each other.
225222
"""
226223

227224
id_token = None
228-
must_prompt_logout = True
229-
token_user = None
230225
if id_token_hint:
231226
# Only basic validation has been done on the IDToken at this point.
232227
id_token, claims = _load_id_token(id_token_hint)
233228

234229
if not id_token or not _validate_claims(request, claims):
235230
raise InvalidIDTokenError()
236231

237-
token_user = id_token.user
238-
239-
if id_token.user == request.user:
240-
# A logout without user interaction (i.e. no prompt) is only allowed
241-
# if an ID Token is provided that matches the current user.
242-
must_prompt_logout = False
243-
244232
# If both id_token_hint and client_id are given it must be verified that they match.
245233
if client_id:
246234
if id_token.application.client_id != client_id:
247235
raise ClientIdMissmatch()
248236

249-
# The standard states that a prompt should always be shown.
250-
# This behaviour can be configured with OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT.
251-
prompt_logout = must_prompt_logout or oauth2_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT
252-
253237
application = None
254238
# Determine the application that is requesting the logout.
255239
if client_id:
@@ -273,7 +257,7 @@ def validate_logout_request(request, id_token_hint, client_id, post_logout_redir
273257
if not application.post_logout_redirect_uri_allowed(post_logout_redirect_uri):
274258
raise InvalidOIDCRedirectURIError("This client does not have this redirect uri registered.")
275259

276-
return prompt_logout, application, token_user
260+
return application, id_token.user if id_token else None
277261

278262

279263
class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView):
@@ -314,7 +298,7 @@ def get(self, request, *args, **kwargs):
314298
state = request.GET.get("state")
315299

316300
try:
317-
prompt, application, token_user = validate_logout_request(
301+
application, token_user = validate_logout_request(
318302
request=request,
319303
id_token_hint=id_token_hint,
320304
client_id=client_id,
@@ -323,7 +307,7 @@ def get(self, request, *args, **kwargs):
323307
except OIDCError as error:
324308
return self.error_response(error)
325309

326-
if not prompt:
310+
if not self.must_prompt(token_user):
327311
return self.do_logout(application, post_logout_redirect_uri, state, token_user)
328312

329313
self.oidc_data = {
@@ -346,21 +330,34 @@ def form_valid(self, form):
346330
state = form.cleaned_data.get("state")
347331

348332
try:
349-
prompt, application, token_user = validate_logout_request(
333+
application, token_user = validate_logout_request(
350334
request=self.request,
351335
id_token_hint=id_token_hint,
352336
client_id=client_id,
353337
post_logout_redirect_uri=post_logout_redirect_uri,
354338
)
355339

356-
if not prompt or form.cleaned_data.get("allow"):
340+
if not self.must_prompt(token_user) or form.cleaned_data.get("allow"):
357341
return self.do_logout(application, post_logout_redirect_uri, state, token_user)
358342
else:
359343
raise LogoutDenied()
360344

361345
except OIDCError as error:
362346
return self.error_response(error)
363347

348+
def must_prompt(self, token_user):
349+
"""Indicate whether the logout has to be confirmed by the user. This happens if the
350+
specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`.
351+
352+
A logout without user interaction (i.e. no prompt) is only allowed
353+
if an ID Token is provided that matches the current user.
354+
"""
355+
return (
356+
oauth2_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT
357+
or token_user is None
358+
or token_user != self.request.user
359+
)
360+
364361
def do_logout(self, application=None, post_logout_redirect_uri=None, state=None, token_user=None):
365362
user = token_user or self.request.user
366363
# Delete Access Tokens if a user was found

tests/test_oidc_views.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
from oauth2_provider.models import get_access_token_model, get_id_token_model, get_refresh_token_model
1111
from oauth2_provider.oauth2_validators import OAuth2Validator
1212
from oauth2_provider.settings import oauth2_settings
13-
from oauth2_provider.views.oidc import _load_id_token, _validate_claims, validate_logout_request
13+
from oauth2_provider.views.oidc import (
14+
RPInitiatedLogoutView,
15+
_load_id_token,
16+
_validate_claims,
17+
validate_logout_request,
18+
)
1419

1520
from . import presets
1621

@@ -186,9 +191,7 @@ def mock_request_for(user):
186191

187192

188193
@pytest.mark.django_db
189-
@pytest.mark.parametrize("ALWAYS_PROMPT", [True, False])
190-
def test_validate_logout_request(oidc_tokens, public_application, other_user, rp_settings, ALWAYS_PROMPT):
191-
rp_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT = ALWAYS_PROMPT
194+
def test_validate_logout_request(oidc_tokens, public_application):
192195
oidc_tokens = oidc_tokens
193196
application = oidc_tokens.application
194197
client_id = application.client_id
@@ -198,37 +201,31 @@ def test_validate_logout_request(oidc_tokens, public_application, other_user, rp
198201
id_token_hint=None,
199202
client_id=None,
200203
post_logout_redirect_uri=None,
201-
) == (True, None, None)
204+
) == (None, None)
202205
assert validate_logout_request(
203206
request=mock_request_for(oidc_tokens.user),
204207
id_token_hint=None,
205208
client_id=client_id,
206209
post_logout_redirect_uri=None,
207-
) == (True, application, None)
210+
) == (application, None)
208211
assert validate_logout_request(
209212
request=mock_request_for(oidc_tokens.user),
210213
id_token_hint=None,
211214
client_id=client_id,
212215
post_logout_redirect_uri="http://example.org",
213-
) == (True, application, None)
216+
) == (application, None)
214217
assert validate_logout_request(
215218
request=mock_request_for(oidc_tokens.user),
216219
id_token_hint=id_token,
217220
client_id=None,
218221
post_logout_redirect_uri="http://example.org",
219-
) == (ALWAYS_PROMPT, application, oidc_tokens.user)
220-
assert validate_logout_request(
221-
request=mock_request_for(other_user),
222-
id_token_hint=id_token,
223-
client_id=None,
224-
post_logout_redirect_uri="http://example.org",
225-
) == (True, application, oidc_tokens.user)
222+
) == (application, oidc_tokens.user)
226223
assert validate_logout_request(
227224
request=mock_request_for(oidc_tokens.user),
228225
id_token_hint=id_token,
229226
client_id=client_id,
230227
post_logout_redirect_uri="http://example.org",
231-
) == (ALWAYS_PROMPT, application, oidc_tokens.user)
228+
) == (application, oidc_tokens.user)
232229
with pytest.raises(ClientIdMissmatch):
233230
validate_logout_request(
234231
request=mock_request_for(oidc_tokens.user),
@@ -266,6 +263,19 @@ def test_validate_logout_request(oidc_tokens, public_application, other_user, rp
266263
)
267264

268265

266+
@pytest.mark.django_db
267+
@pytest.mark.parametrize("ALWAYS_PROMPT", [True, False])
268+
def test_must_prompt(oidc_tokens, other_user, rp_settings, ALWAYS_PROMPT):
269+
rp_settings.OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT = ALWAYS_PROMPT
270+
oidc_tokens = oidc_tokens
271+
assert RPInitiatedLogoutView(request=mock_request_for(oidc_tokens.user)).must_prompt(None) is True
272+
assert (
273+
RPInitiatedLogoutView(request=mock_request_for(oidc_tokens.user)).must_prompt(oidc_tokens.user)
274+
== ALWAYS_PROMPT
275+
)
276+
assert RPInitiatedLogoutView(request=mock_request_for(other_user)).must_prompt(oidc_tokens.user) is True
277+
278+
269279
def test__load_id_token():
270280
assert _load_id_token("Not a Valid ID Token.") == (None, None)
271281

0 commit comments

Comments
 (0)