def correctly_signed_logout_response(self, decoded_xml, must=False, origdoc=None): """ Check if a request is correctly signed, if we have metadata for the SP that sent the info use that, if not use the key that are in the message if any. :param decoded_xml: The SAML message as a XML string :param must: Whether there must be a signature :return: None if the signature can not be verified otherwise the response as a samlp.LogoutResponse instance """ response = samlp.logout_response_from_string(decoded_xml) if not response: raise TypeError("Not a LogoutResponse") if not response.signature: if must: raise SignatureError("Missing must signature") else: return response return self._check_signature(decoded_xml, response, class_name(response), origdoc)
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 testUsingTestData(self): """Test for logout_response_from_string() using test data""" new_lr = samlp.logout_response_from_string( samlp_data.TEST_LOGOUT_RESPONSE) assert new_lr.id == "response id" assert new_lr.in_response_to == "request id" assert new_lr.version == saml2.VERSION assert new_lr.issue_instant == "2007-09-14T01:05:02Z" assert new_lr.destination == "http://www.example.com/Destination" assert new_lr.consent == saml.CONSENT_UNSPECIFIED assert isinstance(new_lr.issuer, saml.Issuer) assert isinstance(new_lr.signature, ds.Signature) assert isinstance(new_lr.extensions, samlp.Extensions) assert isinstance(new_lr.status, samlp.Status)
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 create_logout_response(subject_id, destination, issuer_entity_id, req_entity_id, sign=True): config = IdPConfig() config.load(idp_config) idp_server = Server(config=config) # construct a request logout_request = create_logout_request( subject_id=subject_id, destination=destination, issuer_entity_id=issuer_entity_id, req_entity_id=req_entity_id) #idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident')) resp, headers, message = idp_server.logout_response( request=logout_request, bindings=[BINDING_HTTP_REDIRECT], sign=sign) location = dict(headers).get('Location') url = urlparse.urlparse(location) params = urlparse.parse_qs(url.query) logout_response_xml = decode_base64_and_inflate(params['SAMLResponse'][0]) response = samlp.logout_response_from_string(logout_response_xml) return response.in_response_to, logout_response_xml
def create_logout_response(subject_id, destination, issuer_entity_id, req_entity_id, sign=True): config = IdPConfig() config.load(idp_config) idp_server = Server(config=config) # construct a request logout_request = create_logout_request(subject_id=subject_id, destination=destination, issuer_entity_id=issuer_entity_id, req_entity_id=req_entity_id) #idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident')) resp, headers, message = idp_server.logout_response( request=logout_request, bindings=[BINDING_HTTP_REDIRECT], sign=sign) location = dict(headers).get('Location') url = urlparse.urlparse(location) params = urlparse.parse_qs(url.query) logout_response_xml = decode_base64_and_inflate(params['SAMLResponse'][0]) response = samlp.logout_response_from_string(logout_response_xml) return response.in_response_to, logout_response_xml
def testAccessors(self): """Test for LogoutResponse accessors""" self.lr.id = "response id" self.lr.in_response_to = "request id" self.lr.version = saml2.VERSION self.lr.issue_instant = "2007-09-14T01:05:02Z" self.lr.destination = "http://www.example.com/Destination" self.lr.consent = saml.CONSENT_UNSPECIFIED self.lr.issuer = saml.Issuer() self.lr.signature = ds.Signature() self.lr.extensions = samlp.Extensions() self.lr.status = samlp.Status() new_lr = samlp.logout_response_from_string(self.lr.to_string()) assert new_lr.id == "response id" assert new_lr.in_response_to == "request id" assert new_lr.version == saml2.VERSION assert new_lr.issue_instant == "2007-09-14T01:05:02Z" assert new_lr.destination == "http://www.example.com/Destination" assert new_lr.consent == saml.CONSENT_UNSPECIFIED assert isinstance(new_lr.issuer, saml.Issuer) assert isinstance(new_lr.signature, ds.Signature) assert isinstance(new_lr.extensions, samlp.Extensions) assert isinstance(new_lr.status, samlp.Status)
def _parse_logout_response(self, enc_response): xmlstr = decode_base64_and_inflate(urllib.unquote(enc_response)) return logout_response_from_string(xmlstr)
def do_logout(self, subject_id, entity_ids, reason, expire, sign=None): """ :param subject_id: Identifier of the Subject :param entity_ids: List of entity ids for the IdPs that have provided information concerning the subject :param reason: The reason for doing the logout :param expire: Try to logout before this time. :param sign: Whether to sign the request or not :return: """ # check time if not not_on_or_after(expire): # I've run out of time # Do the local logout anyway self.local_logout(subject_id) return 0, "504 Gateway Timeout", [], [] # for all where I can use the SOAP binding, do those first not_done = entity_ids[:] responses = {} for entity_id in entity_ids: response = False for binding in [#BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]: srvs = self.metadata.single_logout_service(entity_id, "idpsso", binding=binding) if not srvs: continue destination = destinations(srvs)[0] logger.info("destination to provider: %s" % destination) request = self.create_logout_request(destination, entity_id, subject_id, reason=reason, expire=expire) to_sign = [] if binding.startswith("http://"): sign = True if sign is None: sign = self.logout_requests_signed_default if sign: request.signature = pre_signature_part(request.id, self.sec.my_cert, 1) to_sign = [(class_name(request), request.id)] logger.info("REQUEST: %s" % request) srequest = signed_instance_factory(request, self.sec, to_sign) if binding == BINDING_SOAP: response = self.send_using_soap(srequest, destination) if response: logger.info("Verifying response") response = self.logout_request_response(response) if response: not_done.remove(entity_id) logger.info("OK response from %s" % destination) responses[entity_id] = logout_response_from_string(response) else: logger.info("NOT OK response from %s" % destination) else: session_id = request.id rstate = self._relay_state(session_id) self.state[session_id] = {"entity_id": entity_id, "operation": "SLO", "entity_ids": entity_ids, "subject_id": subject_id, "reason": reason, "not_on_of_after": expire, "sign": sign} if binding == BINDING_HTTP_POST: response = self.use_http_form_post(srequest, destination, rstate) else: response = self.use_http_get(srequest, destination, rstate) responses[entity_id] = response not_done.remove(entity_id) # only try one binding break if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids,)) return responses