1515
1616import base64
1717import logging
18+ from typing import Optional
1819from urllib .parse import quote
1920
2021from django .conf import settings
@@ -104,6 +105,19 @@ def _get_subject_id(session):
104105 return None
105106
106107
108+ def _get_next_path (request : HttpRequest ) -> Optional [str ]:
109+ if "next" in request .GET :
110+ next_path = request .GET ["next" ]
111+ elif "RelayState" in request .GET :
112+ next_path = request .GET ["RelayState" ]
113+ else :
114+ return None
115+
116+ next_path = validate_referral_url (request , next_path )
117+
118+ return next_path
119+
120+
107121class SPConfigMixin :
108122 """Mixin for some of the SAML views with re-usable methods."""
109123
@@ -154,20 +168,6 @@ class LoginView(SPConfigMixin, View):
154168 "djangosaml2/post_binding_form.html" ,
155169 )
156170
157- def get_next_path (self , request : HttpRequest ) -> str :
158- """Returns the path to put in the RelayState to redirect the user to after having logged in.
159- If the user is already logged in (and if allowed), he will redirect to there immediately.
160- """
161-
162- next_path = get_fallback_login_redirect_url ()
163- if "next" in request .GET :
164- next_path = request .GET ["next" ]
165- elif "RelayState" in request .GET :
166- next_path = request .GET ["RelayState" ]
167-
168- next_path = validate_referral_url (request , next_path )
169- return next_path
170-
171171 def unknown_idp (self , request , idp ):
172172 msg = f"Error: IdP EntityID { escape (idp )} was not found in metadata"
173173 logger .error (msg )
@@ -190,21 +190,25 @@ def load_sso_kwargs(self, sso_kwargs):
190190 def add_idp_hinting (self , http_response ):
191191 return add_idp_hinting (self .request , http_response ) or http_response
192192
193- def get (self , request , * args , ** kwargs ):
194- logger .debug ("Login process started" )
195- next_path = self .get_next_path (request )
196-
197- # if the user is already authenticated that maybe because of two reasons:
193+ def should_prevent_auth (self , request ) -> bool :
194+ # If the user is already authenticated that maybe because of two reasons:
198195 # A) He has this URL in two browser windows and in the other one he
199196 # has already initiated the authenticated session.
200197 # B) He comes from a view that (incorrectly) send him here because
201198 # he does not have enough permissions. That view should have shown
202199 # an authorization error in the first place.
203- # We can only make one thing here and that is configurable with the
204- # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting
205- # is True (default value) we will redirect him to the next_path path.
206- # Otherwise, we will show an (configurable) authorization error.
207- if request .user .is_authenticated :
200+ return request .user .is_authenticated
201+
202+ def get (self , request , * args , ** kwargs ):
203+ logger .debug ("Login process started" )
204+ next_path = _get_next_path (request )
205+ if next_path is None :
206+ next_path = get_fallback_login_redirect_url ()
207+
208+ if self .should_prevent_auth (request ):
209+ # If the SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting is True
210+ # (default value), redirect to the next_path. Otherwise, show a
211+ # configurable authorization error.
208212 if get_custom_setting ("SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN" , True ):
209213 return HttpResponseRedirect (next_path )
210214 logger .debug ("User is already logged in" )
@@ -566,7 +570,48 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
566570 if callable (create_unknown_user ):
567571 create_unknown_user = create_unknown_user ()
568572
573+ try :
574+ user = self .authenticate_user (
575+ request ,
576+ session_info ,
577+ attribute_mapping ,
578+ create_unknown_user ,
579+ assertion_info
580+ )
581+ except PermissionDenied as e :
582+ return self .handle_acs_failure (
583+ request ,
584+ exception = e ,
585+ session_info = session_info ,
586+ )
587+
588+ relay_state = self .build_relay_state ()
589+ custom_redirect_url = self .custom_redirect (user , relay_state , session_info )
590+ if custom_redirect_url :
591+ return HttpResponseRedirect (custom_redirect_url )
592+
593+ relay_state = validate_referral_url (request , relay_state )
594+ if not relay_state :
595+ logger .debug (
596+ "RelayState is not a valid URL, redirecting to fallback: %s" ,
597+ relay_state
598+ )
599+ return HttpResponseRedirect (get_fallback_login_redirect_url ())
600+
601+ logger .debug ("Redirecting to the RelayState: %s" , relay_state )
602+ return HttpResponseRedirect (relay_state )
603+
604+ def authenticate_user (
605+ self ,
606+ request ,
607+ session_info ,
608+ attribute_mapping ,
609+ create_unknown_user ,
610+ assertion_info
611+ ):
612+ """Calls Django's authenticate method after the SAML response is verified"""
569613 logger .debug ("Trying to authenticate the user. Session info: %s" , session_info )
614+
570615 user = auth .authenticate (
571616 request = request ,
572617 session_info = session_info ,
@@ -579,11 +624,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
579624 "Could not authenticate user received in SAML Assertion. Session info: %s" ,
580625 session_info ,
581626 )
582- return self .handle_acs_failure (
583- request ,
584- exception = PermissionDenied ("No user could be authenticated." ),
585- session_info = session_info ,
586- )
627+ raise PermissionDenied ("No user could be authenticated." )
587628
588629 auth .login (self .request , user )
589630 _set_subject_id (request .saml_session , session_info ["name_id" ])
@@ -592,13 +633,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
592633 self .post_login_hook (request , user , session_info )
593634 self .customize_session (user , session_info )
594635
595- relay_state = self .build_relay_state ()
596- custom_redirect_url = self .custom_redirect (user , relay_state , session_info )
597- if custom_redirect_url :
598- return HttpResponseRedirect (custom_redirect_url )
599- relay_state = validate_referral_url (request , relay_state )
600- logger .debug ("Redirecting to the RelayState: %s" , relay_state )
601- return HttpResponseRedirect (relay_state )
636+ return user
602637
603638 def post_login_hook (
604639 self , request : HttpRequest , user : settings .AUTH_USER_MODEL , session_info : dict
@@ -814,10 +849,19 @@ def finish_logout(request, response):
814849
815850 auth .logout (request )
816851
817- if settings .LOGOUT_REDIRECT_URL is not None :
818- return HttpResponseRedirect (resolve_url (settings .LOGOUT_REDIRECT_URL ))
852+ next_path = _get_next_path (request )
853+ if next_path is not None :
854+ logger .debug ("Redirecting to the RelayState: %s" , next_path )
855+ return HttpResponseRedirect (next_path )
856+ elif settings .LOGOUT_REDIRECT_URL is not None :
857+ fallback_url = resolve_url (settings .LOGOUT_REDIRECT_URL )
858+ logger .debug ("No valid RelayState found; Redirecting to "
859+ "LOGOUT_REDIRECT_URL" )
860+ return HttpResponseRedirect (fallback_url )
819861 else :
820862 current_site = get_current_site (request )
863+ logger .debug ("No valid RelayState or LOGOUT_REDIRECT_URL found, "
864+ "rendering fallback template." )
821865 return render (
822866 request ,
823867 "registration/logged_out.html" ,
0 commit comments