def test_Saml_handle_logout_request(self): not_on_or_after = time.time() + 3600 identity = { 'id-1': { 'https://sso.example.com/idp/metadata': (not_on_or_after, { 'authn_info': [], 'name_id': 'id-1', 'not_on_or_after': not_on_or_after, 'came_from': '/next', 'ava': { 'uid': ['123456'] } }) } } state = { 'entity_ids': ['https://sso.example.com/idp/metadata'], 'subject_id': 'id-1', 'return_to': '/next' } # modifying config in this test, make copy so as not to effect # following tests. tmp_sp_config = copy.deepcopy(sp_config) # create a response to assert upon sp = auth.Saml(tmp_sp_config) logout_request = create_logout_request( 'id-1', destination='https://foo.example.com/sp/slo', issuer_entity_id='https://sso.example.com/idp/metadata', req_entity_id='https://sso.example.com/idp/metadata') # test SAMLRequest logout with self.app.test_request_context( '/', method='GET', query_string=dict( SAMLRequest=deflate_and_base64_encode(str(logout_request)), RelayState=deflate_and_base64_encode(logout_request.id))): # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {logout_request.id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assert_("SAMLResponse" in resp.headers['Location']) url = urlparse.urlparse(resp.headers['Location']) params = urlparse.parse_qs(url.query) self.assert_('SAMLResponse' in params) logout = samlp.logout_response_from_string( decode_base64_and_inflate(params['SAMLResponse'][0])) self.assertEqual(logout.status.status_code.value, 'urn:oasis:names:tc:SAML:2.0:status:Success') self.assertEqual(logout.destination, 'https://sso.example.com/idp/slo')
def test_Saml_handle_logout_request(self): not_on_or_after = time.time()+3600 identity = {'id-1': { 'https://sso.example.com/idp/metadata': ( not_on_or_after, { 'authn_info': [], 'name_id': 'id-1', 'not_on_or_after': not_on_or_after, 'came_from': '/next', 'ava': {'uid': ['123456']} } ) }} state = { 'entity_ids': ['https://sso.example.com/idp/metadata'], 'subject_id': 'id-1', 'return_to': '/next' } # modifying config in this test, make copy so as not to effect # following tests. tmp_sp_config = copy.deepcopy(sp_config) # create a response to assert upon sp = auth.Saml(tmp_sp_config) logout_request = create_logout_request('id-1', destination='https://foo.example.com/sp/slo', issuer_entity_id='https://sso.example.com/idp/metadata', req_entity_id='https://sso.example.com/idp/metadata') # test SAMLRequest logout with self.app.test_request_context('/', method='GET', query_string=dict( SAMLRequest=deflate_and_base64_encode(str(logout_request)), RelayState=deflate_and_base64_encode(logout_request.id))): # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {logout_request.id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assert_("SAMLResponse" in resp.headers['Location']) url = urlparse.urlparse(resp.headers['Location']) params = urlparse.parse_qs(url.query) self.assert_('SAMLResponse' in params) logout = samlp.logout_response_from_string( decode_base64_and_inflate(params['SAMLResponse'][0])) self.assertEqual(logout.status.status_code.value, 'urn:oasis:names:tc:SAML:2.0:status:Success') self.assertEqual(logout.destination, 'https://sso.example.com/idp/slo')
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_authn_request_http_redirect_right_signature(self): xml_message = generate_authn_request() encoded_message = deflate_and_base64_encode(xml_message) args = { 'SAMLRequest': encoded_message, 'SigAlg': SIG_RSA_SHA256, } query_string = "&".join([urlencode({k: args[k]}) for k in REQ_ORDER if k in args]).encode('ascii') pkey = import_rsa_key_from_file(os.path.join(DATA_DIR, 'sp.key')) signer = self.idp_server.server.sec.sec_backend.get_signer(SIG_RSA_SHA256, pkey) args["Signature"] = base64.b64encode(signer.sign(query_string)) query_string = urlencode(args) self.assertEqual(len(self.idp_server.ticket), 0) self.assertEqual(len(self.idp_server.responses), 0) response = self.test_client.get( '/sso-test?{}'.format(query_string), follow_redirects=True ) self.assertEqual(response.status_code, 200) response_text = response.get_data(as_text=True) self.assertIn( '<form class="Form Form--spaced u-margin-bottom-l " name="login" method="post" action="/login">', response_text ) self.assertEqual(len(self.idp_server.ticket), 1) self.assertEqual(len(self.idp_server.responses), 0) key = list(self.idp_server.ticket.keys())[0] xmlstr = self.idp_server.ticket[key].xmlstr self.assertEqual(xml_message, xmlstr)
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 http_redirect_message(message, location, relay_state="", typ="SAMLRequest"): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding technique, and transmitted using the HTTP GET method. The DEFLATE Encoding is used in this function. :param message: The message :param location: Where the message should be posted to :param relay_state: for preserving and conveying state information :return: A tuple containing header information and a HTML message. """ if not isinstance(message, basestring): message = "%s" % (message,) args = {typ: deflate_and_base64_encode(message)} if relay_state: args["RelayState"] = relay_state login_url = "?".join([location, urllib.urlencode(args)]) headers = [('Location', login_url)] body = [""] return headers, body
def test_slo_soap(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": "foba0001", "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after" : soon, "user": { "givenName": "Leo", "surName": "Laport", } } sp = client.Saml2Client(config_file="server_conf") sp.users.add_information_about_person(sinfo) logout_request = sp.construct_logout_request(subject_id = "foba0001", destination = "http://localhost:8088/slo", issuer_entity_id = "urn:mace:example.com:saml:roland:idp", reason = "I'm tired of this") _ = s_utils.deflate_and_base64_encode("%s" % (logout_request,)) saml_soap = make_soap_enveloped_saml_thingy(logout_request) idp = Server("idp_soap_conf") request = idp.parse_logout_request(saml_soap) assert request
def test_logout_service_startingIDP(self): self.config.testing_securitypolicy(userid='*****@*****.**', permissive=True) self.set_user_cookie('*****@*****.**') came_from = '/afterlogin/' session_id = self.add_outstanding_query(came_from) saml_response = auth_response(session_id, "hubba-bubba@test") # Log in through IDP SAMLResponse res = self.testapp.post('/saml2/acs/', params={ 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': came_from, }) res = self.testapp.get('/saml2/ls/', params={ 'SAMLRequest': deflate_and_base64_encode( logout_request(session_id) ), 'RelayState': 'testing-relay-state', }) self.assertEqual(res.status, '302 Found') self.assertIn('https://idp.example.com/simplesaml/saml2/idp/' 'SingleLogoutService.php?SAMLResponse=', res.location) # Set a expired cookie (just the logout header) self.assertIn('auth_tkt=""; Path=/; Domain=localhost; Max-Age=0; ' 'Expires=Wed, 31-Dec-97 23:59:59 GMT', res.headers.getall('Set-Cookie'))
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest", sigalg=None, key=None): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding technique, and transmitted using the HTTP GET method. The DEFLATE Encoding is used in this function. :param message: The message :param location: Where the message should be posted to :param relay_state: for preserving and conveying state information :param typ: What type of message it is SAMLRequest/SAMLResponse/SAMLart :param sigalg: The signature algorithm to use. :param key: Key to use for signing :return: A tuple containing header information and a HTML message. """ if not isinstance(message, str): message = "%s" % (message,) _order = None if typ in ["SAMLRequest", "SAMLResponse"]: if typ == "SAMLRequest": _order = REQ_ORDER else: _order = RESP_ORDER args = {typ: deflate_and_base64_encode(message)} elif typ == "SAMLart": args = {typ: message} else: raise Exception("Unknown message type: %s" % typ) if relay_state: args["RelayState"] = relay_state if sigalg: # sigalgs # http://www.w3.org/2000/09/xmldsig#dsa-sha1 # http://www.w3.org/2000/09/xmldsig#rsa-sha1 args["SigAlg"] = sigalg try: signer = SIGNER_ALGS[sigalg] except: raise Unsupported("Signing algorithm") else: string = "&".join([urllib.parse.urlencode({k: args[k]}) for k in _order if k in args]) args["Signature"] = base64.b64encode(signer.sign(string, key)) string = urllib.parse.urlencode(args) else: string = urllib.parse.urlencode(args) glue_char = "&" if urllib.parse.urlparse(location).query else "?" login_url = glue_char.join([location, string]) headers = [('Location', str(login_url))] body = [] return {"headers": headers, "data": body}
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_authn_response_with_encrypted_assertion(self, sp_conf, context): with open(os.path.join(TEST_RESOURCE_BASE_PATH, "idp_metadata_for_encrypted_signed_auth_response.xml")) as idp_metadata_file: sp_conf["metadata"]["inline"] = [idp_metadata_file.read()] samlbackend = SAMLBackend(Mock(), INTERNAL_ATTRIBUTES, {"sp_config": sp_conf, "disco_srv": DISCOSRV_URL}, "base_url", "samlbackend") response_binding = BINDING_HTTP_REDIRECT relay_state = "test relay state" with open(os.path.join(TEST_RESOURCE_BASE_PATH, "auth_response_with_encrypted_signed_assertion.xml")) as auth_response_file: auth_response = auth_response_file.read() context.request = {"SAMLResponse": deflate_and_base64_encode(auth_response), "RelayState": relay_state} context.state[self.samlbackend.name] = {"relay_state": relay_state} with open(os.path.join(TEST_RESOURCE_BASE_PATH, "encryption_key.pem")) as encryption_key_file: samlbackend.encryption_keys = [encryption_key_file.read()] assertion_issued_at = 1479315212 with patch('saml2.validate.time_util.shift_time') as mock_shift_time, \ patch('saml2.validate.time_util.utc_now') as mock_utc_now: mock_utc_now.return_value = assertion_issued_at + 1 mock_shift_time.side_effect = [datetime.utcfromtimestamp(assertion_issued_at + 1), datetime.utcfromtimestamp(assertion_issued_at - 1)] samlbackend.authn_response(context, response_binding) context, internal_resp = samlbackend.auth_callback_func.call_args[0] assert Counter(internal_resp.attributes.keys()) == Counter({"mail", "givenname", "displayname", "surname"})
def saml_request(**kwargs): details = { "issue_instant": datetime.datetime.now().isoformat("T", "seconds") + "Z", "issuer": "http://testsp/saml2/metadata/", "entity_id": "http://localhost:8000", "destination": "http://localhost:8000/idp/sso/redirect", "acs_url": "https://testing.com/saml2/acs/", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "name_id_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", } details.update(**kwargs) saml_request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_f50197d87df55f22010d8b1e1f4de4cb56396cd6ff" Version="2.0" IssueInstant="{issue_instant}" Destination="{destination}" AssertionConsumerServiceURL="{acs_url}" ProtocolBinding="{binding}"> <saml:Issuer>{issuer}</saml:Issuer> <samlp:NameIDPolicy Format="{name_id_format}" AllowCreate="true" /></samlp:AuthnRequest>""".format( **details).encode() return deflate_and_base64_encode(saml_request).decode()
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest"): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding technique, and transmitted using the HTTP GET method. The DEFLATE Encoding is used in this function. :param message: The message :param location: Where the message should be posted to :param relay_state: for preserving and conveying state information :return: A tuple containing header information and a HTML message. """ if not isinstance(message, basestring): message = "%s" % (message,) args = {typ: deflate_and_base64_encode(message)} if relay_state: args["RelayState"] = relay_state glue_char = "&" if urlparse.urlparse(location).query else "?" login_url = glue_char.join([location, urllib.urlencode(args)]) headers = [('Location', login_url)] body = [""] return headers, body
def test_authn_response_with_encrypted_assertion(self, sp_conf, context): with open(os.path.join(TEST_RESOURCE_BASE_PATH, "idp_metadata_for_encrypted_signed_auth_response.xml")) as idp_metadata_file: sp_conf["metadata"]["inline"] = [idp_metadata_file.read()] samlbackend = SAMLBackend(Mock(), INTERNAL_ATTRIBUTES, {"sp_config": sp_conf, "disco_srv": DISCOSRV_URL}, "base_url", "samlbackend") response_binding = BINDING_HTTP_REDIRECT relay_state = "test relay state" with open(os.path.join(TEST_RESOURCE_BASE_PATH, "auth_response_with_encrypted_signed_assertion.xml")) as auth_response_file: auth_response = auth_response_file.read() context.request = {"SAMLResponse": deflate_and_base64_encode(auth_response), "RelayState": relay_state} context.state[self.samlbackend.name] = {"relay_state": relay_state} with open(os.path.join(TEST_RESOURCE_BASE_PATH, "encryption_key.pem")) as encryption_key_file: samlbackend.encryption_keys = [encryption_key_file.read()] assertion_issued_at = 1479315212 with patch('saml2.validate.time_util.shift_time') as mock_shift_time, \ patch('saml2.validate.time_util.utc_now') as mock_utc_now: mock_utc_now.return_value = assertion_issued_at + 1 mock_shift_time.side_effect = [datetime.utcfromtimestamp(assertion_issued_at + 1), datetime.utcfromtimestamp(assertion_issued_at - 1)] samlbackend.authn_response(context, response_binding) context, internal_resp = samlbackend.auth_callback_func.call_args[0] assert Counter(internal_resp.attributes.keys()) == Counter({"mail", "givenname", "displayname", "surname"})
def test_Saml_handle_logout_response(self): not_on_or_after = time.time()+3600 identity = {'id-1': { 'https://sso.example.com/idp/metadata': ( not_on_or_after, { 'authn_info': [], 'name_id': 'id-1', 'not_on_or_after': not_on_or_after, 'came_from': '/next', 'ava': {'uid': ['123456']} } ) }} state = { 'entity_ids': ['https://sso.example.com/idp/metadata'], 'subject_id': 'id-1', 'return_to': '/next' } # modifying config in this test, make copy so as not to effect # following tests. tmp_sp_config = copy.deepcopy(sp_config) # create a response to assert upon sp = auth.Saml(tmp_sp_config) session_id, logout_response = create_logout_response('id-1', destination='https://sso.example.com/idp/slo', issuer_entity_id='https://sso.example.com/idp/metadata', req_entity_id='https://foo.example.com/sp/metadata') self.assert_('Signature' in logout_response) # test SAMLResponse logout as GET with self.app.test_request_context('/', method='GET', query_string=dict( SAMLResponse=deflate_and_base64_encode(logout_response), RelayState='/next')): # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {session_id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.headers['Location'], '/next') # test SAMLResponse logout as POST with self.app.test_request_context('/', method='POST', data=dict( SAMLResponse=base64.b64encode(logout_response), RelayState='/next')): endpoints = tmp_sp_config['service']['sp']['endpoints'] slo = endpoints['single_logout_service'][0][0] endpoints['single_logout_service'] = [(slo, BINDING_HTTP_POST)] # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {session_id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.headers['Location'], '/next')
def test_inflate_then_deflate(): txt = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland, Sweden. She was brought up on Mårbacka, the family estate, which she did not leave until 1881, when she went to a teachers' college at Stockholm""" interm = utils.deflate_and_base64_encode(txt) bis = utils.decode_base64_and_inflate(interm) assert bis == txt
def test_inflate_then_deflate(): str = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland, Sweden. She was brought up on Mårbacka, the family estate, which she did not leave until 1881, when she went to a teachers' college at Stockholm""" interm = utils.deflate_and_base64_encode(str) bis = utils.decode_base64_and_inflate(interm) assert bis == str
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest", sigalg='', signer=None, **kwargs): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding technique, and transmitted using the HTTP GET method. The DEFLATE Encoding is used in this function. :param message: The message :param location: Where the message should be posted to :param relay_state: for preserving and conveying state information :param typ: What type of message it is SAMLRequest/SAMLResponse/SAMLart :param sigalg: Which algorithm the signature function will use to sign the message :param signer: A signature function that can be used to sign the message :return: A tuple containing header information and a HTML message. """ if not isinstance(message, six.string_types): message = "%s" % (message,) _order = None if typ in ["SAMLRequest", "SAMLResponse"]: if typ == "SAMLRequest": _order = REQ_ORDER else: _order = RESP_ORDER args = {typ: deflate_and_base64_encode(message)} elif typ == "SAMLart": args = {typ: message} else: raise Exception("Unknown message type: %s" % typ) if relay_state: args["RelayState"] = relay_state if signer: # sigalgs, should be one defined in xmldsig assert sigalg in [b for a, b in SIG_ALLOWED_ALG] args["SigAlg"] = sigalg string = "&".join([urlencode({k: args[k]}) for k in _order if k in args]).encode('ascii') args["Signature"] = base64.b64encode(signer.sign(string)) string = urlencode(args) else: string = urlencode(args) glue_char = "&" if urlparse(location).query else "?" login_url = glue_char.join([location, string]) headers = [('Location', str(login_url))] body = [] return {"headers": headers, "data": body}
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest", sigalg='', signer=None, **kwargs): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding technique, and transmitted using the HTTP GET method. The DEFLATE Encoding is used in this function. :param message: The message :param location: Where the message should be posted to :param relay_state: for preserving and conveying state information :param typ: What type of message it is SAMLRequest/SAMLResponse/SAMLart :param sigalg: Which algorithm the signature function will use to sign the message :param signer: A signature function that can be used to sign the message :return: A tuple containing header information and a HTML message. """ if not isinstance(message, six.string_types): message = "%s" % (message,) _order = None if typ in ["SAMLRequest", "SAMLResponse"]: if typ == "SAMLRequest": _order = REQ_ORDER else: _order = RESP_ORDER args = {typ: deflate_and_base64_encode(message)} elif typ == "SAMLart": args = {typ: message} else: raise Exception("Unknown message type: %s" % typ) if relay_state: args["RelayState"] = relay_state if signer: # sigalgs, should be one defined in xmldsig assert sigalg in [b for a, b in SIG_ALLOWED_ALG] args["SigAlg"] = sigalg string = "&".join([urlencode({k: args[k]}) for k in _order if k in args]).encode('ascii') args["Signature"] = base64.b64encode(signer.sign(string)) string = urlencode(args) else: string = urlencode(args) glue_char = "&" if urlparse(location).query else "?" login_url = glue_char.join([location, string]) headers = [('Location', str(login_url))] body = [] return {"headers": headers, "data": body}
def test_inflate_then_deflate(): txt = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland, Sweden. She was brought up on Mårbacka, the family estate, which she did not leave until 1881, when she went to a teachers' college at Stockholm""" if not isinstance(txt, six.binary_type): txt = txt.encode('utf-8') interm = utils.deflate_and_base64_encode(txt) bis = utils.decode_base64_and_inflate(interm) if not isinstance(bis, six.binary_type): bis = bis.encode('utf-8') assert bis == txt
def test_parse_faulty_request(self): authn_request = self.client.authn_request( query_id = "id1", destination = "http://www.example.com", service_url = "http://www.example.org", spentityid = "urn:mace:example.com:saml:roland:sp", my_name = "My real name", ) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) # should raise an error because faulty spentityid raises(OtherError, self.server.parse_authn_request, intermed)
def test_authn_request_http_redirect_bad_signature(self): xml_message = generate_authn_request() encoded_message = deflate_and_base64_encode(xml_message) self.assertEqual(len(self.idp_server.ticket), 0) self.assertEqual(len(self.idp_server.responses), 0) response = self.test_client.get( '/sso-test?SAMLRequest={}&SigAlg={}&Signature=sign'.format( quote(encoded_message), quote(SIG_RSA_SHA256)), follow_redirects=True) self.assertEqual(response.status_code, 200) response_text = response.get_data(as_text=True) self.assertIn('Verifica della firma del messaggio fallita.', response_text) self.assertEqual(len(self.idp_server.ticket), 0) self.assertEqual(len(self.idp_server.responses), 0)
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_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_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_parse_faulty_request_to_err_status(self): authn_request = self.client.authn_request( query_id = "id1", destination = "http://www.example.com", service_url = "http://www.example.org", spentityid = "urn:mace:example.com:saml:roland:sp", my_name = "My real name", ) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) try: self.server.parse_authn_request(intermed) status = None except OtherError, oe: print oe.args status = s_utils.error_status_factory(oe)
def test_authn_request_http_redirect_missing_sigalg_and_signature_parameter( self): # See: https://github.com/italia/spid-testenv2/issues/36 xml_message = generate_authn_request() encoded_message = deflate_and_base64_encode(xml_message) self.assertEqual(len(self.idp_server.ticket), 0) self.assertEqual(len(self.idp_server.responses), 0) response = self.test_client.get('/sso-test?SAMLRequest={}'.format( quote(encoded_message), quote(SIG_RSA_SHA256)), follow_redirects=True) self.assertEqual(response.status_code, 200) response_text = response.get_data(as_text=True) self.assertIn( 'I parametri Signature e SigAlg sono entrambi necessari per le richieste di tipo HTTP-REDIRECT', response_text) self.assertEqual(len(self.idp_server.ticket), 0) self.assertEqual(len(self.idp_server.responses), 0)
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_logout_service_startingSP_already_logout(self): came_from = '/afterlogin/' session_id = self.add_outstanding_query(came_from) res = self.testapp.get('/saml2/ls/', params={ 'SAMLResponse': deflate_and_base64_encode( logout_response(session_id) ), 'RelayState': 'testing-relay-state', }) self.assertEqual(res.status, '302 Found') self.assertIn(self.settings['saml2.logout_redirect_url'], res.location) # Set a expired cookie (just the logout header) self.assertIn('auth_tkt=""; Path=/; Domain=localhost; Max-Age=0; ' 'Expires=Wed, 31-Dec-97 23:59:59 GMT', res.headers.getall('Set-Cookie'))
def test_logout_service_startingSP_already_logout(self): came_from = '/afterlogin/' session_id = self.add_outstanding_query(came_from) with self.app.test_request_context( '/saml2-ls', method='POST', data={ 'SAMLResponse': deflate_and_base64_encode(logout_response(session_id)), 'RelayState': 'testing-relay-state', }): response = self.app.dispatch_request() self.assertEqual(response.status, '302 FOUND') self.assertIn('testing-relay-state', response.location)
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_logout_service_startingIDP(self): eppn = 'hubba-bubba' came_from = '/afterlogin/' session_id = self.add_outstanding_query(came_from) cookie = self.dump_session_cookie(session_id) saml_response = auth_response(session_id, eppn).encode('utf-8') # Log in through IDP SAMLResponse with self.app.test_request_context( '/saml2-acs', method='POST', headers={'Cookie': cookie}, data={ 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': '/testing-relay-state', }, ): self.app.dispatch_request() session.persist( ) # Explicit session.persist is needed when working within a test_request_context with self.app.test_request_context( '/saml2-ls', method='POST', headers={'Cookie': cookie}, data={ 'SAMLRequest': deflate_and_base64_encode(logout_request(session_id)), 'RelayState': '/testing-relay-state', }, ): response = self.app.dispatch_request() self.assertEqual(response.status, '302 FOUND') self.assertIn( 'https://idp.example.com/simplesaml/saml2/idp/' 'SingleLogoutService.php?SAMLResponse=', response.location, )
def test_parse_ok_request(self): authn_request = self.client.authn_request( query_id = "id1", destination = "http://localhost:8088/sso", service_url = "http://localhost:8087/", spentityid = "urn:mace:example.com:saml:roland:sp", my_name = "My real name", ) print authn_request intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) response = self.server.parse_authn_request(intermed) # returns a dictionary print response assert response["consumer_url"] == "http://localhost:8087/" assert response["id"] == "id1" name_id_policy = response["request"].name_id_policy assert _eq(name_id_policy.keyswv(), ["format", "allow_create"]) assert name_id_policy.format == saml.NAMEID_FORMAT_TRANSIENT assert response["sp_entity_id"] == "urn:mace:example.com:saml:roland:sp"
def test_logout_service_startingIDP_no_subject_id(self): eppn = 'hubba-bubba' came_from = '/afterlogin/' session_id = self.add_outstanding_query(came_from) cookie = self.dump_session_cookie(session_id) saml_response = auth_response(session_id, eppn) # Log in through IDP SAMLResponse with self.app.test_request_context('/saml2-acs', method='POST', headers={'Cookie': cookie}, data={ 'SAMLResponse': base64.b64encode(saml_response), 'RelayState': 'testing-relay-state', }): response = self.app.dispatch_request() with self.app.test_request_context('/saml2-ls', method='POST', headers={'Cookie': cookie}, data={ 'SAMLRequest': deflate_and_base64_encode( logout_request(session_id)), 'RelayState': 'testing-relay-state', }): del session['_saml2_session_name_id'] session.persist() response = self.app.dispatch_request() self.assertEqual(response.status, '302 FOUND') self.assertIn('testing-relay-state', response.location)
def test_slo_http_post(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": "foba0001", "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after" : soon, "user": { "givenName": "Leo", "surName": "Laport", } } self.client.users.add_information_about_person(sinfo) logout_request = self.client.construct_logout_request( subject_id="foba0001", destination = "http://localhost:8088/slop", issuer_entity_id = "urn:mace:example.com:saml:roland:idp", reason = "I'm tired of this") intermed = s_utils.deflate_and_base64_encode("%s" % (logout_request,)) #saml_soap = make_soap_enveloped_saml_thingy(logout_request) request = self.server.parse_logout_request(intermed, BINDING_HTTP_POST) assert request
def _create_idp_logout_request(self): """ enc_resp is an encrypted and base64 encoded logout response """ logout_xml = '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="s26bce413ad43ae7ff430a065122b7b16f31e17802" Version="2.0" IssueInstant="2012-05-08T12:46:11Z" Destination="http://nohost/logout" NotOnOrAfter="2012-05-08T12:56:11Z"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://nohost/auth</saml:Issuer><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" NameQualifier="http://nohost/auth" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">1kFn6vXCTJ7Uo5v572Z1IsaLK8yQ</saml:NameID><samlp:SessionIndex xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">s26997d5ef9cbf708cc46b732624d90bba15cfb101</samlp:SessionIndex></samlp:LogoutRequest>' return deflate_and_base64_encode(logout_xml)
from saml2 import config, s_utils from saml2.server import Server from saml2.client import Saml2Client __author__ = 'rolandh' server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") client = Saml2Client(conf) id, authn_request = client.create_authn_request( id="id1", destination="http://localhost:8088/sso") print(authn_request) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) response = server.parse_authn_request(intermed)
from saml2 import config, s_utils from saml2.server import Server from saml2.client import Saml2Client __author__ = 'rolandh' server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") client = Saml2Client(conf) id, authn_request = client.create_authn_request(id = "id1", destination = "http://localhost:8088/sso") print(authn_request) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) response = server.parse_authn_request(intermed)
def test_Saml_handle_logout_response(self): not_on_or_after = time.time() + 3600 identity = { 'id-1': { 'https://sso.example.com/idp/metadata': (not_on_or_after, { 'authn_info': [], 'name_id': 'id-1', 'not_on_or_after': not_on_or_after, 'came_from': '/next', 'ava': { 'uid': ['123456'] } }) } } state = { 'entity_ids': ['https://sso.example.com/idp/metadata'], 'subject_id': 'id-1', 'return_to': '/next' } # modifying config in this test, make copy so as not to effect # following tests. tmp_sp_config = copy.deepcopy(sp_config) # create a response to assert upon sp = auth.Saml(tmp_sp_config) session_id, logout_response = create_logout_response( 'id-1', destination='https://sso.example.com/idp/slo', issuer_entity_id='https://sso.example.com/idp/metadata', req_entity_id='https://foo.example.com/sp/metadata') self.assert_('Signature' in logout_response) # test SAMLResponse logout as GET with self.app.test_request_context( '/', method='GET', query_string=dict( SAMLResponse=deflate_and_base64_encode(logout_response), RelayState='/next')): # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {session_id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.headers['Location'], '/next') # test SAMLResponse logout as POST with self.app.test_request_context( '/', method='POST', data=dict(SAMLResponse=base64.b64encode(logout_response), RelayState='/next')): endpoints = tmp_sp_config['service']['sp']['endpoints'] slo = endpoints['single_logout_service'][0][0] endpoints['single_logout_service'] = [(slo, BINDING_HTTP_POST)] # first need to be logged in, let's pretend session['_saml_identity'] = identity session['_saml_subject_id'] = 'id-1' session['_saml_state'] = {session_id: state} success, resp = sp.handle_logout(request, next_url='/next') self.assertTrue(success) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.headers['Location'], '/next')