11import json
2+ import warnings
23from urllib .parse import urlparse
34
45from django .contrib .auth import logout
@@ -225,6 +226,8 @@ def validate_logout_request(request, id_token_hint, client_id, post_logout_redir
225226 will be validated against each other.
226227 """
227228
229+ warnings .warn ("This method is deprecated and will be removed in version 2.5.0." , DeprecationWarning )
230+
228231 id_token = None
229232 must_prompt_logout = True
230233 token_user = None
@@ -315,17 +318,16 @@ def get(self, request, *args, **kwargs):
315318 state = request .GET .get ("state" )
316319
317320 try :
318- prompt , (redirect_uri , application ), token_user = validate_logout_request (
319- request = request ,
321+ application , token_user = self .validate_logout_request (
320322 id_token_hint = id_token_hint ,
321323 client_id = client_id ,
322324 post_logout_redirect_uri = post_logout_redirect_uri ,
323325 )
324326 except OIDCError as error :
325327 return self .error_response (error )
326328
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 )
329331
330332 self .oidc_data = {
331333 "id_token_hint" : id_token_hint ,
@@ -347,21 +349,100 @@ def form_valid(self, form):
347349 state = form .cleaned_data .get ("state" )
348350
349351 try :
350- prompt , (redirect_uri , application ), token_user = validate_logout_request (
351- request = self .request ,
352+ application , token_user = self .validate_logout_request (
352353 id_token_hint = id_token_hint ,
353354 client_id = client_id ,
354355 post_logout_redirect_uri = post_logout_redirect_uri ,
355356 )
356357
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 )
359360 else :
360361 raise LogoutDenied ()
361362
362363 except OIDCError as error :
363364 return self .error_response (error )
364365
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+
365446 def do_logout (self , application = None , post_logout_redirect_uri = None , state = None , token_user = None ):
366447 user = token_user or self .request .user
367448 # Delete Access Tokens if a user was found
0 commit comments