15
15
16
16
import base64
17
17
import logging
18
+ from typing import Optional
18
19
from urllib .parse import quote
19
20
20
21
from django .conf import settings
@@ -89,6 +90,19 @@ def _get_subject_id(session):
89
90
return None
90
91
91
92
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
+
92
106
class SPConfigMixin :
93
107
"""Mixin for some of the SAML views with re-usable methods."""
94
108
@@ -138,20 +152,6 @@ class LoginView(SPConfigMixin, View):
138
152
"djangosaml2/post_binding_form.html" ,
139
153
)
140
154
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
-
155
155
def unknown_idp (self , request , idp ):
156
156
msg = f"Error: IdP EntityID { escape (idp )} was not found in metadata"
157
157
logger .error (msg )
@@ -174,21 +174,25 @@ def load_sso_kwargs(self, sso_kwargs):
174
174
def add_idp_hinting (self , http_response ):
175
175
return add_idp_hinting (self .request , http_response ) or http_response
176
176
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:
182
179
# A) He has this URL in two browser windows and in the other one he
183
180
# has already initiated the authenticated session.
184
181
# B) He comes from a view that (incorrectly) send him here because
185
182
# he does not have enough permissions. That view should have shown
186
183
# 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.
192
196
if get_custom_setting ("SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN" , True ):
193
197
return HttpResponseRedirect (next_path )
194
198
logger .debug ("User is already logged in" )
@@ -550,7 +554,48 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
550
554
if callable (create_unknown_user ):
551
555
create_unknown_user = create_unknown_user ()
552
556
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"""
553
597
logger .debug ("Trying to authenticate the user. Session info: %s" , session_info )
598
+
554
599
user = auth .authenticate (
555
600
request = request ,
556
601
session_info = session_info ,
@@ -563,11 +608,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
563
608
"Could not authenticate user received in SAML Assertion. Session info: %s" ,
564
609
session_info ,
565
610
)
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." )
571
612
572
613
auth .login (self .request , user )
573
614
_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):
576
617
self .post_login_hook (request , user , session_info )
577
618
self .customize_session (user , session_info )
578
619
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
586
621
587
622
def post_login_hook (
588
623
self , request : HttpRequest , user : settings .AUTH_USER_MODEL , session_info : dict
@@ -797,10 +832,19 @@ def finish_logout(request, response):
797
832
798
833
auth .logout (request )
799
834
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 )
802
844
else :
803
845
current_site = get_current_site (request )
846
+ logger .debug ("No valid RelayState or LOGOUT_REDIRECT_URL found, "
847
+ "rendering fallback template." )
804
848
return render (
805
849
request ,
806
850
"registration/logged_out.html" ,
0 commit comments