def test_assertion_consumer_service_no_session(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' self.add_outstanding_query(session_id, came_from) # Authentication is confirmed. saml_response = auth_response(session_id, 'student') response = self.client.post(reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.path, came_from) # Session should no longer be in outstanding queries. saml_response = auth_response(session_id, 'student') response = self.client.post(reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 403)
def test_idplist_templatetag(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp1.example.com', 'idp2.example.com', 'idp3.example.com'], metadata_file='remote_metadata_three_idps.xml', ) rendered = self.render_template( '{% load idplist %}' '{% idplist as idps %}' '{% for url, name in idps.items %}' '{{ url }} - {{ name }}; ' '{% endfor %}' ) # the idplist is unordered, so convert the result into a set. rendered = set(rendered.split('; ')) expected = set([ u'https://idp1.example.com/simplesaml/saml2/idp/metadata.php - idp1.example.com IdP', u'https://idp2.example.com/simplesaml/saml2/idp/metadata.php - idp2.example.com IdP', u'https://idp3.example.com/simplesaml/saml2/idp/metadata.php - idp3.example.com IdP', u'', ]) self.assertEqual(rendered, expected)
def test_metadata(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) valid_until = datetime.datetime.utcnow() + datetime.timedelta(hours=24) valid_until = valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") expected_metadata = """<?xml version='1.0' encoding='UTF-8'?> <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 UzEQMA4GA1UECBMHU2V2aWxsYTEbMBkGA1UEChMSWWFjbyBTaXN0ZW1hcyBTLkwu MRAwDgYDVQQHEwdTZXZpbGxhMREwDwYDVQQDEwh0aWNvdGljbzAeFw0wOTEyMDQx OTQzNTJaFw0xMDEyMDQxOTQzNTJaMGExCzAJBgNVBAYTAkVTMRAwDgYDVQQIEwdT ZXZpbGxhMRswGQYDVQQKExJZYWNvIFNpc3RlbWFzIFMuTC4xEDAOBgNVBAcTB1Nl dmlsbGExETAPBgNVBAMTCHRpY290aWNvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEA7rMOMOaIZ/YYD5hYS6Hpjpovcu4k8gaIY+om9zCxLV5F8BLEfkxo Pk9IA3cRQNRxf7AXCFxEOH3nKy56AIi1gU7X6fCT30JBT8NQlYdgOVMLlR+tjy1b YV07tDa9U8gzjTyKQHgVwH0436+rmSPnacGj3fMwfySTMhtmrJmax0bIa8EB+gY1 77DBtvf8dIZIXLlGMQFloZeUspvHOrgNoEA9xU4E9AanGnV9HeV37zv3mLDUOQLx 4tk9sMQmylCpij7WZmcOV07DyJ/cEmnvHSalBTcyIgkcwlhmjtSgfCy6o5zuWxYd T9ia80SZbWzn8N6B0q+nq23+Oee9H0lvcwIDAQABMA0GCSqGSIb3DQEBBQUAA4IB AQCQBhKOqucJZAqGHx4ybDXNzpPethszonLNVg5deISSpWagy55KlGCi5laio/xq hHRx18eTzeCeLHQYvTQxw0IjZOezJ1X30DD9lEqPr6C+IrmZc6bn/pF76xsvdaRS gduNQPT1B25SV2HrEmbf8wafSlRARmBsyUHh860TqX7yFVjhYIAUF/El9rLca51j ljCIqqvT+klPdjQoZwODWPFHgute2oNRmoIcMjSnoy1+mxOC2Q/j7kcD8/etulg2 XDxB3zD81gfdtT8VBFP+G4UrBa+5zFk6fT6U8a7ZqVsyH+rCXAdCyVlEC4Y5fZri ID4zT0FcZASGuthM56rRJJSx </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>""" expected_metadata = expected_metadata % valid_until response = self.client.get('/metadata/') self.assertEquals(response['Content-type'], 'text/xml; charset=utf8') self.assertEquals(response.status_code, 200) self.assertEquals(response.content, expected_metadata)
def test_assertion_consumer_service_no_session(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' self.add_outstanding_query(session_id, came_from) # Authentication is confirmed. saml_response = auth_response(session_id, 'student') response = self.client.post( reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.path, came_from) # Session should no longer be in outstanding queries. saml_response = auth_response(session_id, 'student') response = self.client.post( reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 403)
def test_logout(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get(reverse('saml2_logout')) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.hostname, 'idp.example.com') self.assertEqual(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = parse_qs(url.query) self.assertIn('SAMLRequest', params) saml_request = params['SAMLRequest'][0] if 'LogoutRequest xmlns' not in decode_base64_and_inflate( saml_request).decode('utf-8'): raise Exception('Not a valid LogoutRequest')
def test_logout_service_global(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() # now simulate a global logout process initiated by another SP subject_id = views._get_subject_id(self.client.session) instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/"><saml:Issuer>https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>""" % ( instant, subject_id.text) response = self.client.get( reverse('saml2_ls'), { 'SAMLRequest': deflate_and_base64_encode(saml_request), }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.hostname, 'idp.example.com') self.assertEqual(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = parse_qs(url.query) self.assertIn('SAMLResponse', params) saml_response = params['SAMLResponse'][0] if 'Response xmlns' not in decode_base64_and_inflate( saml_response).decode('utf-8'): raise Exception('Not a valid Response')
def test_no_redirect(self): """ Make sure that if we give an empty path as the next parameter, it is replaced with the fallback login redirect url. """ # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) for redirect_url in ['/dashboard/', 'testprofiles:dashboard']: with self.subTest(LOGIN_REDIRECT_URL=redirect_url): with override_settings(LOGIN_REDIRECT_URL=redirect_url): response = self.client.get( reverse('saml2_login') + '?next=') url = urlparse(response['Location']) params = parse_qs(url.query) self.assertEqual(params['RelayState'], ['/dashboard/']) with self.subTest(ACS_DEFAULT_REDIRECT_URL=redirect_url): with override_settings(ACS_DEFAULT_REDIRECT_URL=redirect_url): response = self.client.get( reverse('saml2_login') + '?next=') url = urlparse(response['Location']) params = parse_qs(url.query) self.assertEqual(params['RelayState'], ['/dashboard/'])
def test_logout(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get('/logout/') self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = urlparse.parse_qs(url.query) self.assert_('SAMLRequest' in params) saml_request = params['SAMLRequest'][0] expected_request26 = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" expected_request27 = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request), {'2.6': expected_request26, '2.7': expected_request27})
def test_login_several_idps(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp1.example.com', 'idp2.example.com', 'idp3.example.com']) response = self.client.get('/login/') # a WAYF page should be displayed self.assertContains(response, 'Where are you from?', status_code=200) for i in range(1, 4): link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/' self.assertContains(response, link % i) # click on the second idp response = self.client.get('/login/', { 'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php', 'next': '/', }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'idp2.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = urlparse.parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) saml_request = params['SAMLRequest'][0] expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ProviderName="Test SP" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" /></samlp:AuthnRequest>""" xml = decode_base64_and_inflate(saml_request) self.assertSAMLRequestsEquals(expected_request, xml)
def test_login_several_idps(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp1.example.com', 'idp2.example.com', 'idp3.example.com'], metadata_file='remote_metadata_three_idps.xml', ) response = self.client.get(reverse('saml2_login')) # a WAYF page should be displayed self.assertContains(response, 'Where are you from?', status_code=200) for i in range(1, 4): link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/' self.assertContains(response, link % i) # click on the second idp response = self.client.get(reverse('saml2_login'), { 'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php', 'next': '/', }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.hostname, 'idp2.example.com') self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assertIn('SAMLRequest', params) self.assertIn('RelayState', params) saml_request = params['SAMLRequest'][0] self.assertIn('AuthnRequest xmlns', decode_base64_and_inflate( saml_request).decode('utf-8'))
def test_logout_service_global(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) self.do_login() # now simulate a global logout process initiated by another SP subject_id = views._get_subject_id(self.client.session) instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_request = '<samlp:LogoutRequest ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % ( instant, subject_id) response = self.client.get('/ls/', { 'SAMLRequest': deflate_and_base64_encode(saml_request), }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = urlparse.parse_qs(url.query) self.assert_('SAMLResponse' in params) saml_response = params['SAMLResponse'][0] expected_response = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutResponse Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="_9961abbaae6d06d251226cb25e38bf8f468036e57e" IssueInstant="2010-09-05T09:10:12Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" xml = decode_base64_and_inflate(saml_response) self.assertSAMLRequestsEquals(expected_response, xml)
def test_assertion_consumer_service_default_relay_state(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) new_user = User.objects.create(username='******', password='******') response = self.client.get(reverse('saml2_login')) saml2_req = saml2_from_httpredirect_request(response.url) session_id = get_session_id_from_saml2(saml2_req) saml_response = auth_response(session_id, 'teacher') self.add_outstanding_query(session_id, '/') response = self.client.post( reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), }) self.assertEqual(response.status_code, 302) # The RelayState is missing, redirect to ACS_DEFAULT_REDIRECT_URL self.assertRedirects(response, '/dashboard/') self.assertEqual(force_text(new_user.id), self.client.session[SESSION_KEY])
def test_logout_service_global(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) self.do_login() # now simulate a global logout process initiated by another SP subject_id = views._get_subject_id(self.client.session) instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_request = '<samlp:LogoutRequest ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % ( instant, subject_id) response = self.client.get( '/ls/', { 'SAMLRequest': deflate_and_base64_encode(saml_request), }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = urlparse.parse_qs(url.query) self.assert_('SAMLResponse' in params) saml_response = params['SAMLResponse'][0] expected_response = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutResponse Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="_9961abbaae6d06d251226cb25e38bf8f468036e57e" IssueInstant="2010-09-05T09:10:12Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" xml = decode_base64_and_inflate(saml_response) self.assertSAMLRequestsEquals(expected_response, xml)
def test_logout(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get('/logout/') self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = urlparse.parse_qs(url.query) self.assert_('SAMLRequest' in params) saml_request = params['SAMLRequest'][0] expected_request26 = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" expected_request27 = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request), { '2.6': expected_request26, '2.7': expected_request27 })
def test_unsigned_post_authn_request(self): """ Test that unsigned authentication requests via POST binding does not error. https://github.com/knaperek/djangosaml2/issues/168 """ settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_post_binding.xml', authn_requests_signed=False) response = self.client.get(reverse('saml2_login')) self.assertEqual(response.status_code, 200) # Using POST-binding returns a page with form containing the SAMLRequest response_parser = SAMLPostFormParser() response_parser.feed(response.content.decode('utf-8')) saml_request = response_parser.saml_request_value self.assertIsNotNone(saml_request) if 'AuthnRequest xmlns' not in base64.b64decode(saml_request).decode( 'utf-8'): raise Exception( 'test_unsigned_post_authn_request: Not a valid AuthnRequest')
def test_config_loader_with_real_conf(request): config = SPConfig() config.load( conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml')) return config
def test_echo_view_success(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() request = RequestFactory().get('/') request.user = User.objects.last() middleware = SamlSessionMiddleware() middleware.process_request(request) saml_session_name = getattr(settings, 'SAML_SESSION_COOKIE_NAME', 'saml_session') getattr( request, saml_session_name )['_saml2_subject_id'] = '1f87035b4c1325b296a53d92097e6b3fa36d7e30ee82e3fcb0680d60243c1f03' getattr(request, saml_session_name).save() response = EchoAttributesView.as_view()(request) self.assertEqual(response.status_code, 200) self.assertIn('<h1>SAML attributes</h1>', response.content.decode(), 'Echo page not rendered')
def test_get_idp_sso_supported_bindings_unknown_idp(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.assertEqual( get_idp_sso_supported_bindings(idp_entity_id='random'), [])
def test_get_idp_sso_supported_bindings_no_idps(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=[], metadata_file='remote_metadata_no_idp.xml', ) with self.assertRaisesMessage(ImproperlyConfigured, "No IdP configured!"): get_idp_sso_supported_bindings()
def test_get_idp_sso_supported_bindings_noargs(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) idp_id = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php' self.assertEqual(get_idp_sso_supported_bindings()[0], list( settings.SAML_CONFIG['service']['sp']['idp'][idp_id]['single_sign_on_service'].keys())[0])
def test_assertion_consumer_service(self): # Get initial number of users initial_user_count = User.objects.count() settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) response = self.client.get(reverse('saml2_login')) saml2_req = saml2_from_httpredirect_request(response.url) session_id = get_session_id_from_saml2(saml2_req) # session_id should start with a letter since it is a NCName came_from = '/another-view/' self.add_outstanding_query(session_id, came_from) # this will create a user saml_response = auth_response(session_id, 'student') _url = reverse('saml2_acs') response = self.client.post( _url, { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.path, came_from) self.assertEqual(User.objects.count(), initial_user_count + 1) user_id = self.client.session[SESSION_KEY] user = User.objects.get(id=user_id) self.assertEqual(user.username, 'student') # let's create another user and log in with that one new_user = User.objects.create(username='******', password='******') # session_id = "a1111111111111111111111111111111" client = Client() response = client.get(reverse('saml2_login')) saml2_req = saml2_from_httpredirect_request(response.url) session_id = get_session_id_from_saml2(saml2_req) came_from = '' # bad, let's see if we can deal with this saml_response = auth_response(session_id, 'teacher') self.add_outstanding_query(session_id, '/') response = client.post( reverse('saml2_acs'), { 'SAMLResponse': self.b64_for_post(saml_response), 'RelayState': came_from, }) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) # as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL self.assertEqual(url.path, settings.LOGIN_REDIRECT_URL) self.assertEqual(force_text(new_user.id), client.session[SESSION_KEY])
def test_unknown_idp(self): # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', metadata_file='remote_metadata_three_idps.xml', ) response = self.client.get( reverse('saml2_login') + '?idp=https://unknown.org') self.assertEqual(response.status_code, 403)
def test_discovery_service(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_three_idps.xml', ) response = self.client.get(reverse('saml2_login')) self.assertEqual(response.status_code, 302) self.assertIn("https://that-ds.org/ds", response.url)
def test_assertion_consumer_service(self): # Get initial number of users initial_user_count = User.objects.count() settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.init_cookies() # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' self.add_outstanding_query(session_id, came_from) # this will create a user saml_response = auth_response(session_id, 'student') response = self.client.post( '/acs/', { 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') self.assertEquals(url.path, came_from) self.assertEquals(User.objects.count(), initial_user_count + 1) user_id = self.client.session[SESSION_KEY] user = User.objects.get(id=user_id) self.assertEquals(user.username, 'student') # let's create another user and log in with that one new_user = User.objects.create(username='******', password='******') session_id = "a1111111111111111111111111111111" came_from = '' # bad, let's see if we can deal with this saml_response = auth_response(session_id, 'teacher') self.add_outstanding_query(session_id, '/') response = self.client.post( '/acs/', { 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') # as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL self.assertEquals(url.path, '/accounts/profile/') self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
def test_assertion_consumer_service(self): # Get initial number of users initial_user_count = User.objects.count() settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.init_cookies() # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' self.add_outstanding_query(session_id, came_from) # this will create a user saml_response = auth_response(session_id, 'student') response = self.client.post('/acs/', { 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') self.assertEquals(url.path, came_from) self.assertEquals(User.objects.count(), initial_user_count + 1) user_id = self.client.session[SESSION_KEY] user = User.objects.get(id=user_id) self.assertEquals(user.username, 'student') # let's create another user and log in with that one new_user = User.objects.create(username='******', password='******') session_id = "a1111111111111111111111111111111" came_from = '' # bad, let's see if we can deal with this saml_response = auth_response(session_id, 'teacher') self.add_outstanding_query(session_id, '/') response = self.client.post('/acs/', { 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') # as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL self.assertEquals(url.path, '/accounts/profile/') self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
def test_login_one_idp(self): # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) response = self.client.get(reverse('saml2_login')) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) saml_request = params['SAMLRequest'][0] if PY_VERSION < (2, 7): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" elif PY_VERSION < (3, ): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" else: expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" self.assertSAMLRequestsEquals( decode_base64_and_inflate(saml_request).decode('utf-8'), expected_request) # if we set a next arg in the login view, it is preserverd # in the RelayState argument next = '/another-view/' response = self.client.get(reverse('saml2_login'), {'next': next}) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) self.assertEquals(params['RelayState'][0], next)
def test_login_one_idp(self): # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) response = self.client.get(reverse('saml2_login')) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) saml_request = params['SAMLRequest'][0] if PY_VERSION < (2, 7): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" elif PY_VERSION < (3,): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" else: expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" self.assertSAMLRequestsEquals( decode_base64_and_inflate(saml_request).decode('utf-8'), expected_request) # if we set a next arg in the login view, it is preserverd # in the RelayState argument next = '/another-view/' response = self.client.get(reverse('saml2_login'), {'next': next}) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) self.assertEquals(params['RelayState'][0], next)
def test_incomplete_logout(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) # don't do a login # now simulate a global logout process initiated by another SP instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_request = '<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/"><saml:Issuer>https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % ( instant, 'invalid-subject-id') response = self.client.get(reverse('saml2_ls'), { 'SAMLRequest': deflate_and_base64_encode(saml_request), }) self.assertContains(response, 'Logout error', status_code=403)
def test_incomplete_logout(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) # don't do a login # now simulate a global logout process initiated by another SP instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_request = '<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/"><saml:Issuer>https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % ( instant, 'invalid-subject-id') response = self.client.get('/ls/', { 'SAMLRequest': deflate_and_base64_encode(saml_request), }) self.assertContains(response, 'Logout error', status_code=200)
def test_logout_service_local(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get(reverse('saml2_logout')) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) saml_request = params['SAMLRequest'][0] if PY_VERSION < (2, 7): expected_request = """<?xml version='1.0' encoing='UTF-8'?> <samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" elif PY_VERSION < (3, ): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>""" else: expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>""" self.assertSAMLRequestsEquals( decode_base64_and_inflate(saml_request).decode('utf-8'), expected_request) # now simulate a logout response sent by the idp request_id = re.findall(r' ID="(.*?)" ', xml)[0] instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_response = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % ( request_id, instant) response = self.client.get( reverse('saml2_ls'), { 'SAMLResponse': deflate_and_base64_encode(saml_response), }) self.assertContains(response, "Logged out", status_code=200) self.assertEquals(self.client.session.keys(), [])
def test_idplist_templatetag(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp1.example.com', 'idp2.example.com', 'idp3.example.com']) rendered = self.render_template( '{% load idplist %}' '{% idplist as idps %}' '{% for url, name in idps.items %}' '{{ url }} - {{ name }}; ' '{% endfor %}' ) expected = u'https://idp2.example.com/simplesaml/saml2/idp/metadata.php - idp2.example.com IdP; https://idp3.example.com/simplesaml/saml2/idp/metadata.php - idp3.example.com IdP; https://idp1.example.com/simplesaml/saml2/idp/metadata.php - idp1.example.com IdP; ' self.assertEqual(rendered, expected)
def test_idplist_templatetag(self): settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=[ 'idp1.example.com', 'idp2.example.com', 'idp3.example.com' ]) rendered = self.render_template('{% load idplist %}' '{% idplist as idps %}' '{% for url, name in idps.items %}' '{{ url }} - {{ name }}; ' '{% endfor %}') expected = u'https://idp2.example.com/simplesaml/saml2/idp/metadata.php - idp2.example.com IdP; https://idp3.example.com/simplesaml/saml2/idp/metadata.php - idp3.example.com IdP; https://idp1.example.com/simplesaml/saml2/idp/metadata.php - idp1.example.com IdP; ' self.assertEqual(rendered, expected)
def test_assertion_consumer_service(self): # Get initial number of users initial_user_count = User.objects.count() settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) config = get_config() # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' saml_response = auth_response({'uid': 'student'}, session_id, config) self.init_cookies() self.add_outstanding_query(session_id, came_from) # this will create a user response = self.client.post( '/acs/', { 'SAMLResponse': base64.b64encode(str(saml_response)), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') self.assertEquals(url.path, came_from) self.assertEquals(User.objects.count(), initial_user_count + 1) user_id = self.client.session[SESSION_KEY] user = User.objects.get(id=user_id) self.assertEquals(user.username, 'student') # let's create another user and log in with that one new_user = User.objects.create(username='******', password='******') session_id = "a1111111111111111111111111111111" came_from = '/' saml_response = auth_response({'uid': 'teacher'}, session_id, config) self.add_outstanding_query(session_id, came_from) response = self.client.post( '/acs/', { 'SAMLResponse': base64.b64encode(str(saml_response)), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
def test_login_evil_redirect(self): """ Make sure that if we give an URL other than our own host as the next parameter, it is replaced with the default LOGIN_REDIRECT_URL. """ # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) response = self.client.get(reverse('saml2_login') + '?next=http://evil.com') url = urlparse(response['Location']) params = parse_qs(url.query) self.assertEqual(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
def test_no_redirect(self): """ Make sure that if we give an empty path as the next parameter, it is replaced with the default LOGIN_REDIRECT_URL. """ # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) response = self.client.get(reverse('saml2_login') + '?next=') url = urlparse(response['Location']) params = parse_qs(url.query) self.assertEqual(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
def test_sigalg_not_passed_when_not_signing_request(self): # monkey patch SAML configuration settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) with mock.patch( 'djangosaml2.views.Saml2Client.prepare_for_authenticate', return_value=('session_id', {'url': 'fake'}), ) as prepare_for_auth_mock: self.client.get(reverse('saml2_login')) prepare_for_auth_mock.assert_called_once() _args, kwargs = prepare_for_auth_mock.call_args self.assertNotIn('sigalg', kwargs)
def test_logout_service_local(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get(reverse('saml2_logout')) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) saml_request = params['SAMLRequest'][0] if PY_VERSION < (2, 7): expected_request = """<?xml version='1.0' encoing='UTF-8'?> <samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>""" elif PY_VERSION < (3,): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>""" else: expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>""" self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'), expected_request) # now simulate a logout response sent by the idp request_id = re.findall(r' ID="(.*?)" ', xml)[0] instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_response = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % ( request_id, instant) response = self.client.get(reverse('saml2_ls'), { 'SAMLResponse': deflate_and_base64_encode(saml_response), }) self.assertContains(response, "Logged out", status_code=200) self.assertEquals(self.client.session.keys(), [])
def test_login_several_idps(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=[ 'idp1.example.com', 'idp2.example.com', 'idp3.example.com' ], metadata_file='remote_metadata_three_idps.xml', ) response = self.client.get(reverse('saml2_login')) # a WAYF page should be displayed self.assertContains(response, 'Where are you from?', status_code=200) for i in range(1, 4): link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/' self.assertContains(response, link % i) # click on the second idp response = self.client.get( reverse('saml2_login'), { 'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php', 'next': '/', }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEquals(url.hostname, 'idp2.example.com') self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php') params = parse_qs(url.query) self.assert_('SAMLRequest' in params) self.assert_('RelayState' in params) saml_request = params['SAMLRequest'][0] if PY_VERSION < (2, 7): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" elif PY_VERSION < (3, ): expected_request = """<?xml version='1.0' encoding='UTF-8'?> <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" else: expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>""" self.assertSAMLRequestsEquals( decode_base64_and_inflate(saml_request).decode('utf-8'), expected_request)
def test_assertion_consumer_service(self): # Get initial number of users initial_user_count = User.objects.count() settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com']) config = get_config() # session_id should start with a letter since it is a NCName session_id = "a0123456789abcdef0123456789abcdef" came_from = '/another-view/' saml_response = auth_response({'uid': 'student'}, session_id, config) self.init_cookies() self.add_outstanding_query(session_id, came_from) # this will create a user response = self.client.post('/acs/', { 'SAMLResponse': base64.b64encode(str(saml_response)), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) location = response['Location'] url = urlparse.urlparse(location) self.assertEquals(url.hostname, 'testserver') self.assertEquals(url.path, came_from) self.assertEquals(User.objects.count(), initial_user_count + 1) user_id = self.client.session[SESSION_KEY] user = User.objects.get(id=user_id) self.assertEquals(user.username, 'student') # let's create another user and log in with that one new_user = User.objects.create(username='******', password='******') session_id = "a1111111111111111111111111111111" came_from = '/' saml_response = auth_response({'uid': 'teacher'}, session_id, config) self.add_outstanding_query(session_id, came_from) response = self.client.post('/acs/', { 'SAMLResponse': base64.b64encode(str(saml_response)), 'RelayState': came_from, }) self.assertEquals(response.status_code, 302) self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
def test_echo_view_no_saml_session(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() request = RequestFactory().get('/bar/foo') request.COOKIES = self.client.cookies request.user = User.objects.last() middleware = SamlSessionMiddleware() middleware.process_request(request) response = EchoAttributesView.as_view()(request) self.assertEqual(response.status_code, 200) self.assertEqual(response.content.decode( ), 'No active SAML identity found. Are you sure you have logged in via SAML?')
def test_logout_service_local(self): settings.SAML_CONFIG = conf.create_conf( sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml', ) self.do_login() response = self.client.get(reverse('saml2_logout')) self.assertEqual(response.status_code, 302) location = response['Location'] url = urlparse(location) self.assertEqual(url.hostname, 'idp.example.com') self.assertEqual(url.path, '/simplesaml/saml2/idp/SingleLogoutService.php') params = parse_qs(url.query) self.assertIn('SAMLRequest', params) saml_request = params['SAMLRequest'][0] self.assertIn('LogoutRequest xmlns', decode_base64_and_inflate(saml_request).decode('utf-8'), 'Not a valid LogoutRequest') # now simulate a logout response sent by the idp expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="XXXXXXXXXXXXXXXXXXXXXX" Version="2.0" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" Reason=""><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">1f87035b4c1325b296a53d92097e6b3fa36d7e30ee82e3fcb0680d60243c1f03</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>""" request_id = re.findall(r' ID="(.*?)" ', expected_request)[0] instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') saml_response = """<?xml version='1.0' encoding='UTF-8'?> <samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % ( request_id, instant) response = self.client.get( reverse('saml2_ls'), { 'SAMLResponse': deflate_and_base64_encode(saml_response), }) self.assertContains(response, "Logged out", status_code=200) self.assertListEqual(list(self.client.session.keys()), [])
def test_config_loader_with_real_conf(request): config = SPConfig() config.load(conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com'])) return config
def test_config_loader_with_real_conf(request): config = SPConfig() config.load(conf.create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com'], metadata_file='remote_metadata_one_idp.xml')) return config