4949from  django .utils .six  import  text_type , binary_type , PY3 
5050from  django .views .decorators .csrf  import  csrf_exempt 
5151
52- from  saml2  import  BINDING_HTTP_REDIRECT , BINDING_HTTP_POST 
52+ from  saml2  import  (
53+     ecp , create_class_from_xml_string ,
54+     BINDING_HTTP_REDIRECT , BINDING_HTTP_POST ,
55+ )
56+ from  saml2 .client  import  Saml2Client 
57+ from  saml2 .client_base  import  MIME_PAOS 
5358from  saml2 .metadata  import  entity_descriptor 
5459from  saml2 .ident  import  code , decode 
5560from  saml2 .sigver  import  MissingKey 
61+ from  saml2 .ecp_client  import  PAOS_HEADER_INFO 
62+ from  saml2 .profile .ecp  import  RelayState 
5663from  saml2 .s_utils  import  UnsupportedBinding 
5764from  saml2 .response  import  StatusError , StatusAuthnFailed , SignatureError , StatusRequestDenied 
5865from  saml2 .validate  import  ResponseLifetimeExceed , ToEarly 
6168from  djangosaml2 .cache  import  IdentityCache , OutstandingQueriesCache 
6269from  djangosaml2 .cache  import  StateCache 
6370from  djangosaml2 .conf  import  get_config 
64- from  djangosaml2 .overrides  import  Saml2Client 
6571from  djangosaml2 .signals  import  post_authenticated 
6672from  djangosaml2 .utils  import  (
6773    available_idps , fail_acs_response , get_custom_setting ,
6874    get_idp_sso_supported_bindings , get_location , is_safe_url_compat ,
75+     XmlResponse , SoapFaultResponse 
6976)
7077
7178
@@ -113,7 +120,13 @@ def login(request,
113120    If set to None or nonexistent template, default form from the saml2 library 
114121    will be rendered. 
115122    """ 
116-     logger .debug ('Login process started' )
123+     is_ecp  =  ("HTTP_PAOS"  in  request .META  and 
124+               request .META ["HTTP_PAOS" ] ==  PAOS_HEADER_INFO  and 
125+               MIME_PAOS  in  request .META ["HTTP_ACCEPT" ])
126+     if  is_ecp :
127+         logger .debug ('ECP login process started' )
128+     else :
129+         logger .debug ('Login process started' )
117130
118131    came_from  =  request .GET .get ('next' , settings .LOGIN_REDIRECT_URL )
119132    if  not  came_from :
@@ -138,11 +151,15 @@ def login(request,
138151        redirect_authenticated_user  =  getattr (settings , 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN' , True )
139152        if  redirect_authenticated_user :
140153            return  HttpResponseRedirect (came_from )
154+         elif  is_ecp :
155+             return  HttpResponse ()
141156        else :
142157            logger .debug ('User is already logged in' )
143-             return  render (request , authorization_error_template , {
144-                     'came_from' : came_from ,
145-                     })
158+             return  render (
159+                 request ,
160+                 authorization_error_template ,
161+                 {'came_from' : came_from , }
162+             )
146163
147164    selected_idp  =  request .GET .get ('idp' , None )
148165    conf  =  get_config (config_loader_path , request )
@@ -151,10 +168,14 @@ def login(request,
151168    idps  =  available_idps (conf )
152169    if  selected_idp  is  None  and  len (idps ) >  1 :
153170        logger .debug ('A discovery process is needed' )
154-         return  render (request , wayf_template , {
171+         return  render (
172+             request ,
173+             wayf_template ,
174+             {
155175                'available_idps' : idps .items (),
156176                'came_from' : came_from ,
157-                 })
177+             }
178+         )
158179
159180    # choose a binding to try first 
160181    sign_requests  =  getattr (conf , '_sp_authn_requests_signed' , False )
@@ -180,9 +201,37 @@ def login(request,
180201                                     selected_idp , BINDING_HTTP_POST , BINDING_HTTP_REDIRECT )
181202
182203    client  =  Saml2Client (conf )
204+     try :
205+         if  is_ecp :
206+             (session_id , result ) =  ecp .ecp_auth_request (
207+                 cls = client ,
208+                 entityid = None ,
209+                 relay_state = came_from 
210+             )
211+             if  not  session_id  >  0 :
212+                 logger .error ("Error in ECP auth request." )
213+         else :
214+             (session_id , result ) =  client .prepare_for_authenticate (
215+                 entityid = selected_idp , relay_state = came_from ,
216+                 binding = binding ,
217+             )
218+     except  TypeError  as  e :
219+         message  =  'Unable to know which IdP to use' 
220+         logger .error (message )
221+         if  is_ecp :
222+             return  SoapFaultResponse (message , status = 400 )
223+         return  HttpResponseBadRequest (message )
224+ 
225+     logger .debug ('Saving the session_id in the OutstandingQueries cache' )
226+     oq_cache  =  OutstandingQueriesCache (request .session )
227+     oq_cache .set (session_id , came_from )
228+ 
229+     if  is_ecp :
230+         logger .debug ('Redirecting the ECP client to the IdP' )
231+         return  XmlResponse (result )
183232    http_response  =  None 
233+     logger .debug ('Redirecting user to the IdP via %s binding.' , binding .split (':' )[- 1 ])
184234
185-     logger .debug ('Redirecting user to the IdP via %s binding.' , binding )
186235    if  binding  ==  BINDING_HTTP_REDIRECT :
187236        try :
188237            # do not sign the xml itself, instead use the sigalg to 
@@ -261,45 +310,65 @@ def assertion_consumer_service(request,
261310    djangosaml2.backends.Saml2Backend that should be 
262311    enabled in the settings.py 
263312    """ 
264-     attribute_mapping  =  attribute_mapping  or  get_custom_setting ('SAML_ATTRIBUTE_MAPPING' , {'uid' : ('username' , )})
265-     create_unknown_user  =  create_unknown_user  if  create_unknown_user  is  not None  else  \
266-                           get_custom_setting ('SAML_CREATE_UNKNOWN_USER' , True )
267-     conf  =  get_config (config_loader_path , request )
268-     try :
269-         xmlstr  =  request .POST ['SAMLResponse' ]
270-     except  KeyError :
271-         logger .warning ('Missing "SAMLResponse" parameter in POST data.' )
272-         raise  SuspiciousOperation 
313+     is_ecp  =  MIME_PAOS  ==  request .META ["CONTENT_TYPE" ]
314+ 
315+     attribute_mapping  =  attribute_mapping  or  get_custom_setting (
316+             'SAML_ATTRIBUTE_MAPPING' , {'uid' : ('username' , )})
317+     create_unknown_user  =  create_unknown_user  or  get_custom_setting (
318+             'SAML_CREATE_UNKNOWN_USER' , True )
319+     logger .debug ('Assertion Consumer Service started' )
273320
321+     conf  =  get_config (config_loader_path , request )
274322    client  =  Saml2Client (conf , identity_cache = IdentityCache (request .session ))
275323
324+     if  is_ecp :
325+         data  =  client .unpack_soap_message (request .body )
326+         relay_state_found  =  False 
327+         for  header  in  data ["header" ]:
328+             inst  =  create_class_from_xml_string (RelayState , header )
329+             if  isinstance (inst , RelayState ):
330+                 relay_state_found  =  True 
331+         if  not  relay_state_found :
332+             return  SoapFaultResponse ('Couldn\' t find RelayState data.' ,
333+                                      status = 400 )
334+         xmlstr  =  data ["body" ]
335+     else :
336+         if  'SAMLResponse'  not  in request .POST :
337+             return  HttpResponseBadRequest (
338+                 'Couldn\' t find "SAMLResponse" in POST data.' )
339+         xmlstr  =  request .POST ['SAMLResponse' ]
340+ 
276341    oq_cache  =  OutstandingQueriesCache (request .session )
277342    outstanding_queries  =  oq_cache .outstanding_queries ()
278343
279344    try :
280-         response  =  client .parse_authn_request_response (xmlstr , BINDING_HTTP_POST , outstanding_queries )
345+         # process the authentication response 
346+         binding  =  None  if  is_ecp  else  BINDING_HTTP_POST 
347+         response  =  client .parse_authn_request_response (xmlstr , binding ,
348+                                                        outstanding_queries )
281349    except  (StatusError , ToEarly ):
282350        logger .exception ("Error processing SAML Assertion." )
283-         return  fail_acs_response (request )
351+         return  fail_acs_response (request ,  soap = is_ecp )
284352    except  ResponseLifetimeExceed :
285353        logger .info ("SAML Assertion is no longer valid. Possibly caused by network delay or replay attack." , exc_info = True )
286-         return  fail_acs_response (request )
354+         return  fail_acs_response (request ,  soap = is_ecp )
287355    except  SignatureError :
288356        logger .info ("Invalid or malformed SAML Assertion." , exc_info = True )
289-         return  fail_acs_response (request )
357+         return  fail_acs_response (request ,  soap = is_ecp )
290358    except  StatusAuthnFailed :
291359        logger .info ("Authentication denied for user by IdP." , exc_info = True )
292-         return  fail_acs_response (request )
360+         return  fail_acs_response (request ,  soap = is_ecp )
293361    except  StatusRequestDenied :
294362        logger .warning ("Authentication interrupted at IdP." , exc_info = True )
295-         return  fail_acs_response (request )
363+         return  fail_acs_response (request ,  soap = is_ecp )
296364    except  MissingKey :
297365        logger .exception ("SAML Identity Provider is not configured correctly: certificate key is missing!" )
298-         return  fail_acs_response (request )
366+         return  fail_acs_response (request ,  soap = is_ecp )
299367
300368    if  response  is  None :
301369        logger .warning ("Invalid SAML Assertion received (unknown error)." )
302-         return  fail_acs_response (request , status = 400 , exc_class = SuspiciousOperation )
370+         return  fail_acs_response (request , status = 400 ,
371+                                  exc_class = SuspiciousOperation , soap = is_ecp )
303372
304373    session_id  =  response .session_id ()
305374    oq_cache .delete (session_id )
@@ -318,6 +387,10 @@ def assertion_consumer_service(request,
318387                             attribute_mapping = attribute_mapping ,
319388                             create_unknown_user = create_unknown_user )
320389    if  user  is  None :
390+         message  =  'The user is None' 
391+         logger .error (message )
392+         if  is_ecp :
393+             return  SoapFaultResponse (message , status = 403 )
321394        logger .warning ("Could not authenticate user received in SAML Assertion. Session info: %s" , session_info )
322395        raise  PermissionDenied 
323396
@@ -421,7 +494,7 @@ def logout_service_post(request, *args, **kwargs):
421494
422495
423496def  do_logout_service (request , data , binding , config_loader_path = None , next_page = None ,
424-                    logout_error_template = 'djangosaml2/logout_error.html' ):
497+                        logout_error_template = 'djangosaml2/logout_error.html' ):
425498    """SAML Logout Response endpoint 
426499
427500    The IdP will send the logout response to this view, 
@@ -509,4 +582,5 @@ def register_namespace_prefixes():
509582        for  prefix , namespace  in  prefixes :
510583            ElementTree ._namespace_map [namespace ] =  prefix 
511584
585+ 
512586register_namespace_prefixes ()
0 commit comments