3232from django .views .decorators .http import require_POST
3333from django .shortcuts import render
3434from django .template import TemplateDoesNotExist
35- from django .utils .six import text_type , binary_type , PY3
35+
36+ try :
37+ from django .utils .six import text_type , binary_type , PY3
38+ except ImportError :
39+ import sys
40+ PY3 = sys .version_info [0 ] == 3
41+ text_type = str
42+ binary_type = bytes
43+
3644from django .views .decorators .csrf import csrf_exempt
3745from django .views .generic import View
3846from django .utils .decorators import method_decorator
4452from saml2 .s_utils import UnsupportedBinding
4553from saml2 .response import (
4654 StatusError , StatusAuthnFailed , SignatureError , StatusRequestDenied ,
47- UnsolicitedResponse ,
55+ UnsolicitedResponse , StatusNoAuthnContext ,
4856)
4957from saml2 .validate import ResponseLifetimeExceed , ToEarly
5058from saml2 .xmldsig import SIG_RSA_SHA1 , SIG_RSA_SHA256 # support for SHA1 is required by spec
5159
5260from djangosaml2 .cache import IdentityCache , OutstandingQueriesCache
5361from djangosaml2 .cache import StateCache
62+ from djangosaml2 .exceptions import IdPConfigurationMissing
5463from djangosaml2 .conf import get_config
5564from djangosaml2 .overrides import Saml2Client
5665from djangosaml2 .signals import post_authenticated
@@ -138,6 +147,13 @@ def login(request,
138147 selected_idp = request .GET .get ('idp' , None )
139148 conf = get_config (config_loader_path , request )
140149
150+ kwargs = {}
151+ # pysaml needs a string otherwise: "cannot serialize True (type bool)"
152+ if getattr (conf , '_sp_force_authn' , False ):
153+ kwargs ['force_authn' ] = "true"
154+ if getattr (conf , '_sp_allow_create' , False ):
155+ kwargs ['allow_create' ] = "true"
156+
141157 # is a embedded wayf needed?
142158 idps = available_idps (conf )
143159 if selected_idp is None and len (idps ) > 1 :
@@ -146,6 +162,13 @@ def login(request,
146162 'available_idps' : idps .items (),
147163 'came_from' : came_from ,
148164 })
165+ else :
166+ # is the first one, otherwise next logger message will print None
167+ if not idps :
168+ raise IdPConfigurationMissing (('IdP configuration is missing or '
169+ 'its metadata is expired.' ))
170+ if selected_idp is None :
171+ selected_idp = list (idps .keys ())[0 ]
149172
150173 # choose a binding to try first
151174 sign_requests = getattr (conf , '_sp_authn_requests_signed' , False )
@@ -186,7 +209,7 @@ def login(request,
186209 session_id , result = client .prepare_for_authenticate (
187210 entityid = selected_idp , relay_state = came_from ,
188211 binding = binding , sign = False , sigalg = sigalg ,
189- nsprefix = nsprefix )
212+ nsprefix = nsprefix , ** kwargs )
190213 except TypeError as e :
191214 logger .error ('Unable to know which IdP to use' )
192215 return HttpResponse (text_type (e ))
@@ -202,10 +225,11 @@ def login(request,
202225 return HttpResponse (text_type (e ))
203226 session_id , request_xml = client .create_authn_request (
204227 location ,
205- binding = binding )
228+ binding = binding ,
229+ ** kwargs )
206230 try :
207231 if PY3 :
208- saml_request = base64 .b64encode (binary_type (request_xml , 'UTF-8' ))
232+ saml_request = base64 .b64encode (binary_type (request_xml , 'UTF-8' )). decode ( 'utf-8' )
209233 else :
210234 saml_request = base64 .b64encode (binary_type (request_xml ))
211235
@@ -283,25 +307,28 @@ def post(self,
283307 response = client .parse_authn_request_response (xmlstr , BINDING_HTTP_POST , outstanding_queries )
284308 except (StatusError , ToEarly ):
285309 logger .exception ("Error processing SAML Assertion." )
286- return fail_acs_response (self . request )
310+ return fail_acs_response (request )
287311 except ResponseLifetimeExceed :
288312 logger .info ("SAML Assertion is no longer valid. Possibly caused by network delay or replay attack." , exc_info = True )
289- return fail_acs_response (self . request )
313+ return fail_acs_response (request )
290314 except SignatureError :
291315 logger .info ("Invalid or malformed SAML Assertion." , exc_info = True )
292- return fail_acs_response (self . request )
316+ return fail_acs_response (request )
293317 except StatusAuthnFailed :
294318 logger .info ("Authentication denied for user by IdP." , exc_info = True )
295- return fail_acs_response (self . request )
319+ return fail_acs_response (request )
296320 except StatusRequestDenied :
297321 logger .warning ("Authentication interrupted at IdP." , exc_info = True )
298- return fail_acs_response (self .request )
322+ return fail_acs_response (request )
323+ except StatusNoAuthnContext :
324+ logger .warning ("Missing Authentication Context from IdP." , exc_info = True )
325+ return fail_acs_response (request )
299326 except MissingKey :
300327 logger .exception ("SAML Identity Provider is not configured correctly: certificate key is missing!" )
301- return fail_acs_response (self . request )
328+ return fail_acs_response (request )
302329 except UnsolicitedResponse :
303330 logger .exception ("Received SAMLResponse when no request has been made." )
304- return fail_acs_response (self . request )
331+ return fail_acs_response (request )
305332
306333 if response is None :
307334 logger .warning ("Invalid SAML Assertion received (unknown error)." )
@@ -502,7 +529,17 @@ def do_logout_service(request, data, binding, config_loader_path=None, next_page
502529 relay_state = data .get ('RelayState' , '' ))
503530 state .sync ()
504531 auth .logout (request )
505- return HttpResponseRedirect (get_location (http_info ))
532+ if (
533+ http_info .get ('method' , 'GET' ) == 'POST' and
534+ 'data' in http_info and
535+ ('Content-type' , 'text/html' ) in http_info .get ('headers' , [])
536+ ):
537+ # need to send back to the IDP a signed POST response with user session
538+ # return HTML form content to browser with auto form validation
539+ # to finally send request to the IDP
540+ return HttpResponse (http_info ['data' ])
541+ else :
542+ return HttpResponseRedirect (get_location (http_info ))
506543 else :
507544 logger .error ('No SAMLResponse or SAMLRequest parameter found' )
508545 raise Http404 ('No SAMLResponse or SAMLRequest parameter found' )
0 commit comments