1
1
import json
2
+ import warnings
2
3
from urllib .parse import urlparse
3
4
4
5
from django .contrib .auth import logout
@@ -225,6 +226,8 @@ def validate_logout_request(request, id_token_hint, client_id, post_logout_redir
225
226
will be validated against each other.
226
227
"""
227
228
229
+ warnings .warn ("This method is deprecated and will be removed in version 2.5.0." , DeprecationWarning )
230
+
228
231
id_token = None
229
232
must_prompt_logout = True
230
233
token_user = None
@@ -315,17 +318,16 @@ def get(self, request, *args, **kwargs):
315
318
state = request .GET .get ("state" )
316
319
317
320
try :
318
- prompt , (redirect_uri , application ), token_user = validate_logout_request (
319
- request = request ,
321
+ application , token_user = self .validate_logout_request (
320
322
id_token_hint = id_token_hint ,
321
323
client_id = client_id ,
322
324
post_logout_redirect_uri = post_logout_redirect_uri ,
323
325
)
324
326
except OIDCError as error :
325
327
return self .error_response (error )
326
328
327
- if not prompt :
328
- return self .do_logout (application , redirect_uri , state , token_user )
329
+ if not self . must_prompt ( token_user ) :
330
+ return self .do_logout (application , post_logout_redirect_uri , state , token_user )
329
331
330
332
self .oidc_data = {
331
333
"id_token_hint" : id_token_hint ,
@@ -347,21 +349,100 @@ def form_valid(self, form):
347
349
state = form .cleaned_data .get ("state" )
348
350
349
351
try :
350
- prompt , (redirect_uri , application ), token_user = validate_logout_request (
351
- request = self .request ,
352
+ application , token_user = self .validate_logout_request (
352
353
id_token_hint = id_token_hint ,
353
354
client_id = client_id ,
354
355
post_logout_redirect_uri = post_logout_redirect_uri ,
355
356
)
356
357
357
- if not prompt or form .cleaned_data .get ("allow" ):
358
- return self .do_logout (application , redirect_uri , state , token_user )
358
+ if not self . must_prompt ( token_user ) or form .cleaned_data .get ("allow" ):
359
+ return self .do_logout (application , post_logout_redirect_uri , state , token_user )
359
360
else :
360
361
raise LogoutDenied ()
361
362
362
363
except OIDCError as error :
363
364
return self .error_response (error )
364
365
366
+ def validate_post_logout_redirect_uri (self , application , post_logout_redirect_uri ):
367
+ """
368
+ Validate the OIDC RP-Initiated Logout Request post_logout_redirect_uri parameter
369
+ """
370
+
371
+ if not post_logout_redirect_uri :
372
+ return
373
+
374
+ if not application :
375
+ raise InvalidOIDCClientError ()
376
+ scheme = urlparse (post_logout_redirect_uri )[0 ]
377
+ if not scheme :
378
+ raise InvalidOIDCRedirectURIError ("A Scheme is required for the redirect URI." )
379
+ if oauth2_settings .OIDC_RP_INITIATED_LOGOUT_STRICT_REDIRECT_URIS and (
380
+ scheme == "http" and application .client_type != "confidential"
381
+ ):
382
+ raise InvalidOIDCRedirectURIError ("http is only allowed with confidential clients." )
383
+ if scheme not in application .get_allowed_schemes ():
384
+ raise InvalidOIDCRedirectURIError (f'Redirect to scheme "{ scheme } " is not permitted.' )
385
+ if not application .post_logout_redirect_uri_allowed (post_logout_redirect_uri ):
386
+ raise InvalidOIDCRedirectURIError ("This client does not have this redirect uri registered." )
387
+
388
+ def validate_logout_request_user (self , id_token_hint , client_id ):
389
+ """
390
+ Validate the an OIDC RP-Initiated Logout Request user
391
+ """
392
+
393
+ if not id_token_hint :
394
+ return
395
+
396
+ # Only basic validation has been done on the IDToken at this point.
397
+ id_token , claims = _load_id_token (id_token_hint )
398
+
399
+ if not id_token or not _validate_claims (self .request , claims ):
400
+ raise InvalidIDTokenError ()
401
+
402
+ # If both id_token_hint and client_id are given it must be verified that they match.
403
+ if client_id :
404
+ if id_token .application .client_id != client_id :
405
+ raise ClientIdMissmatch ()
406
+
407
+ return id_token
408
+
409
+ def get_request_application (self , id_token , client_id ):
410
+ if client_id :
411
+ return get_application_model ().objects .get (client_id = client_id )
412
+ if id_token :
413
+ return id_token .application
414
+
415
+ def validate_logout_request (self , id_token_hint , client_id , post_logout_redirect_uri ):
416
+ """
417
+ Validate an OIDC RP-Initiated Logout Request.
418
+ `(application, token_user)` is returned.
419
+
420
+ If it is set, `application` is the Application that is requesting the logout.
421
+ `token_user` is the id_token user, which will used to revoke the tokens if found.
422
+
423
+ The `id_token_hint` will be validated if given. If both `client_id` and `id_token_hint` are given they
424
+ will be validated against each other.
425
+ """
426
+
427
+ id_token = self .validate_logout_request_user (id_token_hint , client_id )
428
+ application = self .get_request_application (id_token , client_id )
429
+ self .validate_post_logout_redirect_uri (application , post_logout_redirect_uri )
430
+
431
+ return application , id_token .user if id_token else None
432
+
433
+ def must_prompt (self , token_user ):
434
+ """Indicate whether the logout has to be confirmed by the user. This happens if the
435
+ specifications force a confirmation, or it is enabled by `OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT`.
436
+
437
+ A logout without user interaction (i.e. no prompt) is only allowed
438
+ if an ID Token is provided that matches the current user.
439
+ """
440
+ return (
441
+ oauth2_settings .OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT
442
+ or token_user is None
443
+ or token_user != self .request .user
444
+ )
445
+
365
446
def do_logout (self , application = None , post_logout_redirect_uri = None , state = None , token_user = None ):
366
447
user = token_user or self .request .user
367
448
# Delete Access Tokens if a user was found
0 commit comments