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
@@ -104,6 +105,19 @@ def _get_subject_id(session):
104
105
return None
105
106
106
107
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
+
107
121
class SPConfigMixin :
108
122
"""Mixin for some of the SAML views with re-usable methods."""
109
123
@@ -154,20 +168,6 @@ class LoginView(SPConfigMixin, View):
154
168
"djangosaml2/post_binding_form.html" ,
155
169
)
156
170
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
-
171
171
def unknown_idp (self , request , idp ):
172
172
msg = f"Error: IdP EntityID { escape (idp )} was not found in metadata"
173
173
logger .error (msg )
@@ -190,21 +190,25 @@ def load_sso_kwargs(self, sso_kwargs):
190
190
def add_idp_hinting (self , http_response ):
191
191
return add_idp_hinting (self .request , http_response ) or http_response
192
192
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:
198
195
# A) He has this URL in two browser windows and in the other one he
199
196
# has already initiated the authenticated session.
200
197
# B) He comes from a view that (incorrectly) send him here because
201
198
# he does not have enough permissions. That view should have shown
202
199
# 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.
208
212
if get_custom_setting ("SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN" , True ):
209
213
return HttpResponseRedirect (next_path )
210
214
logger .debug ("User is already logged in" )
@@ -566,7 +570,48 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
566
570
if callable (create_unknown_user ):
567
571
create_unknown_user = create_unknown_user ()
568
572
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"""
569
613
logger .debug ("Trying to authenticate the user. Session info: %s" , session_info )
614
+
570
615
user = auth .authenticate (
571
616
request = request ,
572
617
session_info = session_info ,
@@ -579,11 +624,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
579
624
"Could not authenticate user received in SAML Assertion. Session info: %s" ,
580
625
session_info ,
581
626
)
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." )
587
628
588
629
auth .login (self .request , user )
589
630
_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):
592
633
self .post_login_hook (request , user , session_info )
593
634
self .customize_session (user , session_info )
594
635
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
602
637
603
638
def post_login_hook (
604
639
self , request : HttpRequest , user : settings .AUTH_USER_MODEL , session_info : dict
@@ -814,10 +849,19 @@ def finish_logout(request, response):
814
849
815
850
auth .logout (request )
816
851
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 )
819
861
else :
820
862
current_site = get_current_site (request )
863
+ logger .debug ("No valid RelayState or LOGOUT_REDIRECT_URL found, "
864
+ "rendering fallback template." )
821
865
return render (
822
866
request ,
823
867
"registration/logged_out.html" ,
0 commit comments