1818import re
1919import sys
2020from importlib import import_module
21- from unittest import mock , skip
21+ from unittest import mock
2222from urllib .parse import parse_qs , urlparse
2323
2424from django .conf import settings
2525from django .contrib .auth import SESSION_KEY , get_user_model
2626from django .contrib .auth .models import AnonymousUser
2727from django .core .exceptions import ImproperlyConfigured
28- from django .http .request import HttpRequest
2928from django .template import Context , Template
30- from django .test import Client , TestCase
29+ from django .test import Client , TestCase , override_settings
3130from django .test .client import RequestFactory
3231from django .urls import reverse
3332from django .utils .encoding import force_text
3635from djangosaml2 .conf import get_config
3736from djangosaml2 .middleware import SamlSessionMiddleware
3837from 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 ,
4139 get_session_id_from_saml2 ,
4240 get_subject_id_from_saml2 ,
4341 saml2_from_httpredirect_request )
@@ -81,35 +79,6 @@ class SAML2Tests(TestCase):
8179
8280 urls = 'djangosaml2.tests.urls'
8381
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-
11382 def init_cookies (self ):
11483 self .client .cookies [settings .SESSION_COOKIE_NAME ] = 'testing'
11584
@@ -120,7 +89,7 @@ def add_outstanding_query(self, session_id, came_from):
12089 self .saml_session .save ()
12190 self .oq_cache = OutstandingQueriesCache (self .saml_session )
12291
123- self .oq_cache .set (session_id \
92+ self .oq_cache .set (session_id
12493 if isinstance (session_id , str ) else session_id .decode (),
12594 came_from )
12695 self .saml_session .save ()
@@ -181,8 +150,7 @@ def test_unsigned_post_authn_request(self):
181150 saml_request = response_parser .saml_request_value
182151
183152 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' ))
186154
187155 def test_login_evil_redirect (self ):
188156 """
@@ -241,8 +209,7 @@ def test_login_one_idp(self):
241209 self .assertIn ('RelayState' , params )
242210
243211 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' ))
246213
247214 # if we set a next arg in the login view, it is preserverd
248215 # in the RelayState argument
@@ -292,8 +259,7 @@ def test_login_several_idps(self):
292259 self .assertIn ('RelayState' , params )
293260
294261 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' ))
297263
298264 def test_assertion_consumer_service (self ):
299265 # Get initial number of users
@@ -440,7 +406,6 @@ def do_login(self):
440406 self .assertEqual (response .status_code , 302 )
441407 return subject_id
442408
443- @skip ("This is a known issue caused by pysaml2. Needs more investigation. Fixes are welcome." )
444409 def test_logout (self ):
445410 settings .SAML_CONFIG = conf .create_conf (
446411 sp_host = 'sp.example.com' ,
@@ -466,8 +431,6 @@ def test_logout(self):
466431 if 'LogoutRequest xmlns' not in decode_base64_and_inflate (saml_request ).decode ('utf-8' ):
467432 raise Exception ('Not a valid LogoutRequest' )
468433
469-
470-
471434 def test_logout_service_local (self ):
472435 settings .SAML_CONFIG = conf .create_conf (
473436 sp_host = 'sp.example.com' ,
@@ -562,43 +525,6 @@ def test_finish_logout_renders_error_template(self):
562525 response = finish_logout (request , None )
563526 self .assertContains (response , "<h1>Logout error</h1>" , status_code = 200 )
564527
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-
602528 def test_sigalg_not_passed_when_not_signing_request (self ):
603529 # monkey patch SAML configuration
604530 settings .SAML_CONFIG = conf .create_conf (
@@ -690,3 +616,79 @@ def test_custom_conf_loader_from_view(self):
690616 url = urlparse (location )
691617 self .assertEqual (url .hostname , 'idp.example.com' )
692618 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