1515
1616import base64
1717import logging
18+ from typing import Optional
1819from urllib .parse import quote
1920
2021from django .conf import settings
@@ -89,6 +90,19 @@ def _get_subject_id(session):
8990 return None
9091
9192
93+ def _get_next_path (request : HttpRequest ) -> Optional [str ]:
94+ if "next" in request .GET :
95+ next_path = request .GET ["next" ]
96+ elif "RelayState" in request .GET :
97+ next_path = request .GET ["RelayState" ]
98+ else :
99+ return None
100+
101+ next_path = validate_referral_url (request , next_path )
102+
103+ return next_path
104+
105+
92106class SPConfigMixin :
93107 """Mixin for some of the SAML views with re-usable methods."""
94108
@@ -138,20 +152,6 @@ class LoginView(SPConfigMixin, View):
138152 "djangosaml2/post_binding_form.html" ,
139153 )
140154
141- def get_next_path (self , request : HttpRequest ) -> str :
142- """Returns the path to put in the RelayState to redirect the user to after having logged in.
143- If the user is already logged in (and if allowed), he will redirect to there immediately.
144- """
145-
146- next_path = get_fallback_login_redirect_url ()
147- if "next" in request .GET :
148- next_path = request .GET ["next" ]
149- elif "RelayState" in request .GET :
150- next_path = request .GET ["RelayState" ]
151-
152- next_path = validate_referral_url (request , next_path )
153- return next_path
154-
155155 def unknown_idp (self , request , idp ):
156156 msg = f"Error: IdP EntityID { escape (idp )} was not found in metadata"
157157 logger .error (msg )
@@ -174,21 +174,25 @@ def load_sso_kwargs(self, sso_kwargs):
174174 def add_idp_hinting (self , http_response ):
175175 return add_idp_hinting (self .request , http_response ) or http_response
176176
177- def get (self , request , * args , ** kwargs ):
178- logger .debug ("Login process started" )
179- next_path = self .get_next_path (request )
180-
181- # if the user is already authenticated that maybe because of two reasons:
177+ def should_prevent_auth (self , request ) -> bool :
178+ # If the user is already authenticated that maybe because of two reasons:
182179 # A) He has this URL in two browser windows and in the other one he
183180 # has already initiated the authenticated session.
184181 # B) He comes from a view that (incorrectly) send him here because
185182 # he does not have enough permissions. That view should have shown
186183 # an authorization error in the first place.
187- # We can only make one thing here and that is configurable with the
188- # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting
189- # is True (default value) we will redirect him to the next_path path.
190- # Otherwise, we will show an (configurable) authorization error.
191- if request .user .is_authenticated :
184+ return request .user .is_authenticated
185+
186+ def get (self , request , * args , ** kwargs ):
187+ logger .debug ("Login process started" )
188+ next_path = _get_next_path (request )
189+ if next_path is None :
190+ next_path = get_fallback_login_redirect_url ()
191+
192+ if self .should_prevent_auth (request ):
193+ # If the SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting is True
194+ # (default value), redirect to the next_path. Otherwise, show a
195+ # configurable authorization error.
192196 if get_custom_setting ("SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN" , True ):
193197 return HttpResponseRedirect (next_path )
194198 logger .debug ("User is already logged in" )
@@ -550,7 +554,48 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
550554 if callable (create_unknown_user ):
551555 create_unknown_user = create_unknown_user ()
552556
557+ try :
558+ user = self .authenticate_user (
559+ request ,
560+ session_info ,
561+ attribute_mapping ,
562+ create_unknown_user ,
563+ assertion_info
564+ )
565+ except PermissionDenied as e :
566+ return self .handle_acs_failure (
567+ request ,
568+ exception = e ,
569+ session_info = session_info ,
570+ )
571+
572+ relay_state = self .build_relay_state ()
573+ custom_redirect_url = self .custom_redirect (user , relay_state , session_info )
574+ if custom_redirect_url :
575+ return HttpResponseRedirect (custom_redirect_url )
576+
577+ relay_state = validate_referral_url (request , relay_state )
578+ if not relay_state :
579+ logger .debug (
580+ "RelayState is not a valid URL, redirecting to fallback: %s" ,
581+ relay_state
582+ )
583+ return HttpResponseRedirect (get_fallback_login_redirect_url ())
584+
585+ logger .debug ("Redirecting to the RelayState: %s" , relay_state )
586+ return HttpResponseRedirect (relay_state )
587+
588+ def authenticate_user (
589+ self ,
590+ request ,
591+ session_info ,
592+ attribute_mapping ,
593+ create_unknown_user ,
594+ assertion_info
595+ ):
596+ """Calls Django's authenticate method after the SAML response is verified"""
553597 logger .debug ("Trying to authenticate the user. Session info: %s" , session_info )
598+
554599 user = auth .authenticate (
555600 request = request ,
556601 session_info = session_info ,
@@ -563,11 +608,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
563608 "Could not authenticate user received in SAML Assertion. Session info: %s" ,
564609 session_info ,
565610 )
566- return self .handle_acs_failure (
567- request ,
568- exception = PermissionDenied ("No user could be authenticated." ),
569- session_info = session_info ,
570- )
611+ raise PermissionDenied ("No user could be authenticated." )
571612
572613 auth .login (self .request , user )
573614 _set_subject_id (request .saml_session , session_info ["name_id" ])
@@ -576,13 +617,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
576617 self .post_login_hook (request , user , session_info )
577618 self .customize_session (user , session_info )
578619
579- relay_state = self .build_relay_state ()
580- custom_redirect_url = self .custom_redirect (user , relay_state , session_info )
581- if custom_redirect_url :
582- return HttpResponseRedirect (custom_redirect_url )
583- relay_state = validate_referral_url (request , relay_state )
584- logger .debug ("Redirecting to the RelayState: %s" , relay_state )
585- return HttpResponseRedirect (relay_state )
620+ return user
586621
587622 def post_login_hook (
588623 self , request : HttpRequest , user : settings .AUTH_USER_MODEL , session_info : dict
@@ -797,10 +832,19 @@ def finish_logout(request, response):
797832
798833 auth .logout (request )
799834
800- if settings .LOGOUT_REDIRECT_URL is not None :
801- return HttpResponseRedirect (resolve_url (settings .LOGOUT_REDIRECT_URL ))
835+ next_path = _get_next_path (request )
836+ if next_path is not None :
837+ logger .debug ("Redirecting to the RelayState: %s" , next_path )
838+ return HttpResponseRedirect (next_path )
839+ elif settings .LOGOUT_REDIRECT_URL is not None :
840+ fallback_url = resolve_url (settings .LOGOUT_REDIRECT_URL )
841+ logger .debug ("No valid RelayState found; Redirecting to "
842+ "LOGOUT_REDIRECT_URL" )
843+ return HttpResponseRedirect (fallback_url )
802844 else :
803845 current_site = get_current_site (request )
846+ logger .debug ("No valid RelayState or LOGOUT_REDIRECT_URL found, "
847+ "rendering fallback template." )
804848 return render (
805849 request ,
806850 "registration/logged_out.html" ,
0 commit comments