18
18
import re
19
19
import sys
20
20
from importlib import import_module
21
- from unittest import mock , skip
21
+ from unittest import mock
22
22
from urllib .parse import parse_qs , urlparse
23
23
24
24
from django .conf import settings
25
25
from django .contrib .auth import SESSION_KEY , get_user_model
26
26
from django .contrib .auth .models import AnonymousUser
27
27
from django .core .exceptions import ImproperlyConfigured
28
- from django .http .request import HttpRequest
29
28
from django .template import Context , Template
30
- from django .test import Client , TestCase
29
+ from django .test import Client , TestCase , override_settings
31
30
from django .test .client import RequestFactory
32
31
from django .urls import reverse
33
32
from django .utils .encoding import force_text
36
35
from djangosaml2 .conf import get_config
37
36
from djangosaml2 .middleware import SamlSessionMiddleware
38
37
from djangosaml2 .tests import conf
39
- from djangosaml2 .utils import (get_custom_setting ,
40
- get_idp_sso_supported_bindings ,
38
+ from djangosaml2 .utils import (get_idp_sso_supported_bindings ,
41
39
get_session_id_from_saml2 ,
42
40
get_subject_id_from_saml2 ,
43
41
saml2_from_httpredirect_request )
@@ -81,35 +79,6 @@ class SAML2Tests(TestCase):
81
79
82
80
urls = 'djangosaml2.tests.urls'
83
81
84
- def setUp (self ):
85
- if hasattr (settings , 'SAML_ATTRIBUTE_MAPPING' ):
86
- self .actual_attribute_mapping = settings .SAML_ATTRIBUTE_MAPPING
87
- del settings .SAML_ATTRIBUTE_MAPPING
88
- if hasattr (settings , 'SAML_CONFIG_LOADER' ):
89
- self .actual_conf_loader = settings .SAML_CONFIG_LOADER
90
- del settings .SAML_CONFIG_LOADER
91
-
92
- def tearDown (self ):
93
- if hasattr (self , 'actual_attribute_mapping' ):
94
- settings .SAML_ATTRIBUTE_MAPPING = self .actual_attribute_mapping
95
- if hasattr (self , 'actual_conf_loader' ):
96
- settings .SAML_CONFIG_LOADER = self .actual_conf_loader
97
-
98
- def assertSAMLRequestsEquals (self , real_xml , expected_xmls ):
99
-
100
- def remove_variable_attributes (xml_string ):
101
- xml_string = re .sub (r' ID=".*?" ' , ' ' , xml_string )
102
- xml_string = re .sub (r' IssueInstant=".*?" ' , ' ' , xml_string )
103
- xml_string = re .sub (
104
- r'<saml:NameID(.*)>.*</saml:NameID>' ,
105
- r'<saml:NameID\1></saml:NameID>' ,
106
- xml_string )
107
-
108
- return xml_string
109
-
110
- self .assertEqual (remove_variable_attributes (real_xml ),
111
- remove_variable_attributes (expected_xmls ))
112
-
113
82
def init_cookies (self ):
114
83
self .client .cookies [settings .SESSION_COOKIE_NAME ] = 'testing'
115
84
@@ -120,7 +89,7 @@ def add_outstanding_query(self, session_id, came_from):
120
89
self .saml_session .save ()
121
90
self .oq_cache = OutstandingQueriesCache (self .saml_session )
122
91
123
- self .oq_cache .set (session_id \
92
+ self .oq_cache .set (session_id
124
93
if isinstance (session_id , str ) else session_id .decode (),
125
94
came_from )
126
95
self .saml_session .save ()
@@ -181,8 +150,7 @@ def test_unsigned_post_authn_request(self):
181
150
saml_request = response_parser .saml_request_value
182
151
183
152
self .assertIsNotNone (saml_request )
184
- if 'AuthnRequest xmlns' not in base64 .b64decode (saml_request ).decode ('utf-8' ):
185
- raise Exception ('test_unsigned_post_authn_request: Not a valid AuthnRequest' )
153
+ self .assertIn ('AuthnRequest xmlns' , base64 .b64decode (saml_request ).decode ('utf-8' ))
186
154
187
155
def test_login_evil_redirect (self ):
188
156
"""
@@ -241,8 +209,7 @@ def test_login_one_idp(self):
241
209
self .assertIn ('RelayState' , params )
242
210
243
211
saml_request = params ['SAMLRequest' ][0 ]
244
- if 'AuthnRequest xmlns' not in decode_base64_and_inflate (saml_request ).decode ('utf-8' ):
245
- raise Exception ('Not a valid AuthnRequest' )
212
+ self .assertIn ('AuthnRequest xmlns' , decode_base64_and_inflate (saml_request ).decode ('utf-8' ))
246
213
247
214
# if we set a next arg in the login view, it is preserverd
248
215
# in the RelayState argument
@@ -292,8 +259,7 @@ def test_login_several_idps(self):
292
259
self .assertIn ('RelayState' , params )
293
260
294
261
saml_request = params ['SAMLRequest' ][0 ]
295
- if 'AuthnRequest xmlns' not in decode_base64_and_inflate (saml_request ).decode ('utf-8' ):
296
- raise Exception ('Not a valid AuthnRequest' )
262
+ self .assertIn ('AuthnRequest xmlns' , decode_base64_and_inflate (saml_request ).decode ('utf-8' ))
297
263
298
264
def test_assertion_consumer_service (self ):
299
265
# Get initial number of users
@@ -440,7 +406,6 @@ def do_login(self):
440
406
self .assertEqual (response .status_code , 302 )
441
407
return subject_id
442
408
443
- @skip ("This is a known issue caused by pysaml2. Needs more investigation. Fixes are welcome." )
444
409
def test_logout (self ):
445
410
settings .SAML_CONFIG = conf .create_conf (
446
411
sp_host = 'sp.example.com' ,
@@ -466,8 +431,6 @@ def test_logout(self):
466
431
if 'LogoutRequest xmlns' not in decode_base64_and_inflate (saml_request ).decode ('utf-8' ):
467
432
raise Exception ('Not a valid LogoutRequest' )
468
433
469
-
470
-
471
434
def test_logout_service_local (self ):
472
435
settings .SAML_CONFIG = conf .create_conf (
473
436
sp_host = 'sp.example.com' ,
@@ -562,43 +525,6 @@ def test_finish_logout_renders_error_template(self):
562
525
response = finish_logout (request , None )
563
526
self .assertContains (response , "<h1>Logout error</h1>" , status_code = 200 )
564
527
565
- def _test_metadata (self ):
566
- settings .SAML_CONFIG = conf .create_conf (
567
- sp_host = 'sp.example.com' ,
568
- idp_hosts = ['idp.example.com' ],
569
- metadata_file = 'remote_metadata_one_idp.xml' ,
570
- )
571
- valid_until = datetime .datetime .utcnow () + datetime .timedelta (hours = 24 )
572
- valid_until = valid_until .strftime ("%Y-%m-%dT%H:%M:%SZ" )
573
- expected_metadata = """<?xml version='1.0' encoding='UTF-8'?>
574
- <md:EntityDescriptor entityID="http://sp.example.com/saml2/metadata/" validUntil="%s" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"><md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDPjCCAiYCCQCkHjPQlll+mzANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJF
575
- UzEQMA4GA1UECBMHU2V2aWxsYTEbMBkGA1UEChMSWWFjbyBTaXN0ZW1hcyBTLkwu
576
- MRAwDgYDVQQHEwdTZXZpbGxhMREwDwYDVQQDEwh0aWNvdGljbzAeFw0wOTEyMDQx
577
- OTQzNTJaFw0xMDEyMDQxOTQzNTJaMGExCzAJBgNVBAYTAkVTMRAwDgYDVQQIEwdT
578
- ZXZpbGxhMRswGQYDVQQKExJZYWNvIFNpc3RlbWFzIFMuTC4xEDAOBgNVBAcTB1Nl
579
- dmlsbGExETAPBgNVBAMTCHRpY290aWNvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
580
- MIIBCgKCAQEA7rMOMOaIZ/YYD5hYS6Hpjpovcu4k8gaIY+om9zCxLV5F8BLEfkxo
581
- Pk9IA3cRQNRxf7AXCFxEOH3nKy56AIi1gU7X6fCT30JBT8NQlYdgOVMLlR+tjy1b
582
- YV07tDa9U8gzjTyKQHgVwH0436+rmSPnacGj3fMwfySTMhtmrJmax0bIa8EB+gY1
583
- 77DBtvf8dIZIXLlGMQFloZeUspvHOrgNoEA9xU4E9AanGnV9HeV37zv3mLDUOQLx
584
- 4tk9sMQmylCpij7WZmcOV07DyJ/cEmnvHSalBTcyIgkcwlhmjtSgfCy6o5zuWxYd
585
- T9ia80SZbWzn8N6B0q+nq23+Oee9H0lvcwIDAQABMA0GCSqGSIb3DQEBBQUAA4IB
586
- AQCQBhKOqucJZAqGHx4ybDXNzpPethszonLNVg5deISSpWagy55KlGCi5laio/xq
587
- hHRx18eTzeCeLHQYvTQxw0IjZOezJ1X30DD9lEqPr6C+IrmZc6bn/pF76xsvdaRS
588
- gduNQPT1B25SV2HrEmbf8wafSlRARmBsyUHh860TqX7yFVjhYIAUF/El9rLca51j
589
- ljCIqqvT+klPdjQoZwODWPFHgute2oNRmoIcMjSnoy1+mxOC2Q/j7kcD8/etulg2
590
- XDxB3zD81gfdtT8VBFP+G4UrBa+5zFk6fT6U8a7ZqVsyH+rCXAdCyVlEC4Y5fZri
591
- ID4zT0FcZASGuthM56rRJJSx
592
- </ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://sp.example.com/saml2/ls/" /><md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://sp.example.com/saml2/acs/" index="1" /><md:AttributeConsumingService index="1"><md:ServiceName xml:lang="en">Test SP</md:ServiceName><md:RequestedAttribute FriendlyName="uid" Name="urn:oid:0.9.2342.19200300.100.1.1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" /><md:RequestedAttribute FriendlyName="eduPersonAffiliation" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="false" /></md:AttributeConsumingService></md:SPSSODescriptor><md:Organization><md:OrganizationName xml:lang="es">Ejemplo S.A.</md:OrganizationName><md:OrganizationName xml:lang="en">Example Inc.</md:OrganizationName><md:OrganizationDisplayName xml:lang="es">Ejemplo</md:OrganizationDisplayName><md:OrganizationDisplayName xml:lang="en">Example</md:OrganizationDisplayName><md:OrganizationURL xml:lang="es">http://www.example.es</md:OrganizationURL><md:OrganizationURL xml:lang="en">http://www.example.com</md:OrganizationURL></md:Organization><md:ContactPerson contactType="technical"><md:Company>Example Inc.</md:Company><md:GivenName>Technical givenname</md:GivenName><md:SurName>Technical surname</md:SurName><md:EmailAddress>[email protected] </md:EmailAddress></md:ContactPerson><md:ContactPerson contactType="administrative"><md:Company>Example Inc.</md:Company><md:GivenName>Administrative givenname</md:GivenName><md:SurName>Administrative surname</md:SurName><md:EmailAddress>[email protected] </md:EmailAddress></md:ContactPerson></md:EntityDescriptor>"""
593
-
594
- expected_metadata = expected_metadata % valid_until
595
-
596
- response = self .client .get (reverse ('saml2_metadata' ))
597
- self .assertEqual (response ['Content-type' ], 'text/xml; charset=utf8' )
598
- self .assertEqual (response .status_code , 200 )
599
- self .assertEqual (response .content , expected_metadata )
600
-
601
-
602
528
def test_sigalg_not_passed_when_not_signing_request (self ):
603
529
# monkey patch SAML configuration
604
530
settings .SAML_CONFIG = conf .create_conf (
@@ -690,3 +616,79 @@ def test_custom_conf_loader_from_view(self):
690
616
url = urlparse (location )
691
617
self .assertEqual (url .hostname , 'idp.example.com' )
692
618
self .assertEqual (url .path , '/simplesaml/saml2/idp/SSOService.php' )
619
+
620
+
621
+ class SessionEnabledTestCase (TestCase ):
622
+ def get_session (self ):
623
+ if self .client .session :
624
+ session = self .client .session
625
+ else :
626
+ engine = import_module (settings .SESSION_ENGINE )
627
+ session = engine .SessionStore ()
628
+ return session
629
+
630
+ def set_session_cookies (self , session ):
631
+ # Set the cookie to represent the session
632
+ session_cookie = settings .SESSION_COOKIE_NAME
633
+ self .client .cookies [session_cookie ] = session .session_key
634
+ cookie_data = {
635
+ 'max-age' : None ,
636
+ 'path' : '/' ,
637
+ 'domain' : settings .SESSION_COOKIE_DOMAIN ,
638
+ 'secure' : settings .SESSION_COOKIE_SECURE or None ,
639
+ 'expires' : None }
640
+ self .client .cookies [session_cookie ].update (cookie_data )
641
+
642
+
643
+ class MiddlewareTests (SessionEnabledTestCase ):
644
+ def test_middleware_cookie_expireatbrowserclose (self ):
645
+ with override_settings (SESSION_EXPIRE_AT_BROWSER_CLOSE = True ):
646
+ session = self .get_session ()
647
+ session .save ()
648
+ self .set_session_cookies (session )
649
+
650
+ config_loader_path = 'djangosaml2.tests.test_config_loader_with_real_conf'
651
+ request = RequestFactory ().get ('/login/' )
652
+ request .user = AnonymousUser ()
653
+ request .session = session
654
+ middleware = SamlSessionMiddleware ()
655
+ middleware .process_request (request )
656
+
657
+ saml_session_name = getattr (settings , 'SAML_SESSION_COOKIE_NAME' , 'saml_session' )
658
+ getattr (request , saml_session_name ).save ()
659
+
660
+ response = views .LoginView .as_view (config_loader_path = config_loader_path )(request )
661
+
662
+ response = middleware .process_response (request , response )
663
+
664
+ cookie = response .cookies [saml_session_name ]
665
+
666
+ self .assertEqual (cookie ['expires' ], '' )
667
+ self .assertEqual (cookie ['max-age' ], '' )
668
+
669
+ def test_middleware_cookie_with_expiry (self ):
670
+ with override_settings (SESSION_EXPIRE_AT_BROWSER_CLOSE = False ):
671
+ session = self .get_session ()
672
+ session .save ()
673
+ self .set_session_cookies (session )
674
+
675
+ config_loader_path = 'djangosaml2.tests.test_config_loader_with_real_conf'
676
+ request = RequestFactory ().get ('/login/' )
677
+ request .user = AnonymousUser ()
678
+ request .session = session
679
+ middleware = SamlSessionMiddleware ()
680
+ middleware .process_request (request )
681
+
682
+ saml_session_name = getattr (settings , 'SAML_SESSION_COOKIE_NAME' , 'saml_session' )
683
+ getattr (request , saml_session_name ).save ()
684
+
685
+ response = views .LoginView .as_view (config_loader_path = config_loader_path )(request )
686
+
687
+ response = middleware .process_response (request , response )
688
+
689
+ cookie = response .cookies [saml_session_name ]
690
+
691
+ self .assertIsNotNone (cookie ['expires' ])
692
+
693
+ self .assertNotEqual (cookie ['expires' ], '' )
694
+ self .assertNotEqual (cookie ['max-age' ], '' )
0 commit comments