def test_class_instances_from_soap_enveloped_saml_thingies_xxe(): xml = """<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> ]> <lolz>&lol1;</lolz> """ with raises(soap.XmlParseError): soap.class_instances_from_soap_enveloped_saml_thingies(xml, None)
def parse_soap_message(text): """ :param text: The SOAP message :return: A dictionary with two keys "body" and "header" """ return class_instances_from_soap_enveloped_saml_thingies( text, [paos, ecp, samlp])
def parse_soap_message(self, text): """ :param text: The SOAP message :return: A dictionary with two keys "body" and "header" """ return class_instances_from_soap_enveloped_saml_thingies(text, [paos, ecp, samlp])
def parse_ecp_authn_response(self, txt, outstanding=None): rdict = soap.class_instances_from_soap_enveloped_saml_thingies( txt, [paos, ecp, samlp]) _relay_state = None for item in rdict["header"]: if item.c_tag == "RelayState" and \ item.c_namespace == ecp.NAMESPACE: _relay_state = item response = self.parse_authn_request_response(rdict["body"], BINDING_PAOS, outstanding) return response, _relay_state
def parse_ecp_authn_response(self, txt, outstanding=None): rdict = soap.class_instances_from_soap_enveloped_saml_thingies(txt, [paos, ecp, samlp]) _relay_state = None for item in rdict["header"]: if item.c_tag == "RelayState" and\ item.c_namespace == ecp.NAMESPACE: _relay_state = item response = self.parse_authn_request_response(rdict["body"], BINDING_PAOS, outstanding) return response, _relay_state
def handle_ecp_authn_response(cls, soap_message, outstanding=None): rdict = soap.class_instances_from_soap_enveloped_saml_thingies( soap_message, [paos, ecp, samlp]) _relay_state = None for item in rdict["header"]: if item.c_tag == "RelayState" and item.c_namespace == ecp.NAMESPACE: _relay_state = item response = authn_response(cls.config, cls.service_url(), outstanding, allow_unsolicited=True) response.loads("%s" % rdict["body"], False, soap_message) response.verify() cls.users.add_information_about_person(response.session_info()) return response, _relay_state
def test_ecp_authn(self): ssid, soap_req = ecp.ecp_auth_request(self.client, "urn:mace:example.com:saml:roland:idp", "id1") print soap_req response = soap.class_instances_from_soap_enveloped_saml_thingies( soap_req, [paos, ecp_prof, samlp]) print response assert len(response["header"]) == 2 assert response["body"].c_tag == "AuthnRequest" assert response["body"].c_namespace == samlp.NAMESPACE headers = ["{%s}%s" % (i.c_namespace, i.c_tag) for i in response["header"]] print headers assert _eq(headers,['{urn:liberty:paos:2003-08}Request', #'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState'])
def test_ecp_authn(self): ssid, soap_req = ecp.ecp_auth_request( self.client, "urn:mace:example.com:saml:roland:idp", "id1") print soap_req response = soap.class_instances_from_soap_enveloped_saml_thingies( soap_req, [paos, ecp_prof, samlp]) print response assert len(response["header"]) == 2 assert response["body"].c_tag == "AuthnRequest" assert response["body"].c_namespace == samlp.NAMESPACE headers = [ "{%s}%s" % (i.c_namespace, i.c_tag) for i in response["header"] ] print headers assert _eq( headers, [ '{urn:liberty:paos:2003-08}Request', #'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState' ])
def test_multiple_soap_headers(): xml_str = open("ecp_soap.xml").read() res = soap.class_instances_from_soap_enveloped_saml_thingies( xml_str, [ecp_prof, paos, samlp]) assert res["body"].c_tag == "AuthnRequest" assert len(res["header"]) == 3 headers = ["{%s}%s" % (i.c_namespace, i.c_tag) for i in res["header"]] print headers assert _eq(headers, [ '{urn:liberty:paos:2003-08}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState' ]) _relay_state = None for item in res["header"]: if item.c_tag == "RelayState" and item.c_namespace == ecp_prof.NAMESPACE: _relay_state = item assert _relay_state assert _relay_state.actor == "http://schemas.xmlsoap.org/soap/actor/next"
def test_multiple_soap_headers(): xml_str = open("ecp_soap.xml").read() res = soap.class_instances_from_soap_enveloped_saml_thingies(xml_str, [ecp_prof, paos, samlp]) assert res["body"].c_tag == "AuthnRequest" assert len(res["header"]) == 3 headers = ["{%s}%s" % (i.c_namespace, i.c_tag) for i in res["header"]] print headers assert _eq(headers,['{urn:liberty:paos:2003-08}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request', '{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState']) _relay_state = None for item in res["header"]: if item.c_tag == "RelayState" and item.c_namespace == ecp_prof.NAMESPACE: _relay_state = item assert _relay_state assert _relay_state.actor == "http://schemas.xmlsoap.org/soap/actor/next"
def operation(self, idp_entity_id, op, **opargs): if "path" not in opargs: opargs["path"] = self._sp # ******************************************** # Phase 1 - First conversation with the SP # ******************************************** # headers needed to indicate to the SP that I'm ECP enabled if "headers" in opargs and opargs["headers"]: opargs["headers"]["PAOS"] = PAOS_HEADER_INFO if "Accept" in opargs["headers"]: opargs["headers"]["Accept"] += ";application/vnd.paos+xml" elif "accept" in opargs["headers"]: opargs["headers"]["Accept"] = opargs["headers"]["accept"] opargs["headers"]["Accept"] += ";application/vnd.paos+xml" del opargs["headers"]["accept"] else: opargs["headers"] = { 'Accept': 'text/html; application/vnd.paos+xml', 'PAOS': PAOS_HEADER_INFO } # request target from SP # can remove the PAOS header now # try: # del opargs["headers"]["PAOS"] # except KeyError: # pass response = op(**opargs) self._debug_info("[Op] SP response: %s" % response) if not response: raise Exception( "Request to SP failed: %s" % self.http.error_description) # The response might be a AuthnRequest instance in a SOAP envelope # body. If so it's the start of the ECP conversation # Two SOAP header blocks; paos:Request and ecp:Request # may also contain a ecp:RelayState SOAP header block # If channel-binding was part of the PAOS header any number of # <cb:ChannelBindings> header blocks may also be present # if 'holder-of-key' option then one or more <ecp:SubjectConfirmation> # header blocks may also be present try: respdict = soap.class_instances_from_soap_enveloped_saml_thingies( response,[paos, ecp,samlp]) self.ecp_conversation(respdict, idp_entity_id) # should by now be authenticated so this should go smoothly response = op(**opargs) except (soap.XmlParseError, AssertionError, KeyError): pass #print "RESP",response, self.http.response if not response: if self.http.response.status != 404: raise Exception("Error performing operation: %s" % ( self.http.error_description,)) return response
def phase2(self, authn_request, rc_url, idp_entity_id, headers=None, idp_endpoint=None, sign=False, sec=""): """ Doing the second phase of the ECP conversation :param authn_request: The AuthenticationRequest :param rc_url: The assertion consumer service url :param idp_entity_id: The EntityID of the IdP :param headers: Possible extra headers :param idp_endpoint: Where to send it all :param sign: If the message should be signed :param sec: security context :return: The response from the IdP """ idp_request = soap.make_soap_enveloped_saml_thingy(authn_request) if sign: _signed = sec.sign_statement_using_xmlsec(idp_request, class_name(authn_request), nodeid=authn_request.id) idp_request = _signed if not idp_endpoint: idp_endpoint = self.find_idp_endpoint(idp_entity_id) if self.user and self.passwd: self.http.add_credentials(self.user, self.passwd) self._debug_info("[P2] Sending request: %s" % idp_request) # POST the request to the IdP response = self.http.post(idp_request, headers=headers, path=idp_endpoint) self._debug_info("[P2] Got IdP response: %s" % response) if response is None or response is False: raise Exception( "Request to IdP failed (%s): %s" % (self.http.response.status, self.http.error_description)) # SAMLP response in a SOAP envelope body, ecp response in headers respdict = soap.class_instances_from_soap_enveloped_saml_thingies( response, [paos, ecp,samlp]) if respdict is None: raise Exception("Unexpected reply from the IdP") self._debug_info("[P2] IdP response dict: %s" % respdict) idp_response = respdict["body"] assert idp_response.c_tag == "Response" self._debug_info("[P2] IdP AUTHN response: %s" % idp_response) _ecp_response = None for item in respdict["header"]: if item.c_tag == "Response" and\ item.c_namespace == ecp.NAMESPACE: _ecp_response = item _acs_url = _ecp_response.assertion_consumer_service_url if rc_url != _acs_url: error = ("response_consumer_url '%s' does not match" % rc_url, "assertion_consumer_service_url '%s" % _acs_url) # Send an error message to the SP fault_text = soap.soap_fault(error) _ = self.http.post(fault_text, path=rc_url) # Raise an exception so the user knows something went wrong raise Exception(error) return idp_response
def operation(self, idp_entity_id, op, **opargs): if "path" not in opargs: opargs["path"] = self._sp # ******************************************** # Phase 1 - First conversation with the SP # ******************************************** # headers needed to indicate to the SP that I'm ECP enabled if "headers" in opargs and opargs["headers"]: opargs["headers"]["PAOS"] = PAOS_HEADER_INFO if "Accept" in opargs["headers"]: opargs["headers"]["Accept"] += ";application/vnd.paos+xml" elif "accept" in opargs["headers"]: opargs["headers"]["Accept"] = opargs["headers"]["accept"] opargs["headers"]["Accept"] += ";application/vnd.paos+xml" del opargs["headers"]["accept"] else: opargs["headers"] = { 'Accept': 'text/html; application/vnd.paos+xml', 'PAOS': PAOS_HEADER_INFO } # request target from SP # can remove the PAOS header now # try: # del opargs["headers"]["PAOS"] # except KeyError: # pass response = op(**opargs) self._debug_info("[Op] SP response: %s" % response) if not response: raise Exception("Request to SP failed: %s" % self.http.error_description) # The response might be a AuthnRequest instance in a SOAP envelope # body. If so it's the start of the ECP conversation # Two SOAP header blocks; paos:Request and ecp:Request # may also contain a ecp:RelayState SOAP header block # If channel-binding was part of the PAOS header any number of # <cb:ChannelBindings> header blocks may also be present # if 'holder-of-key' option then one or more <ecp:SubjectConfirmation> # header blocks may also be present try: respdict = soap.class_instances_from_soap_enveloped_saml_thingies( response, [paos, ecp, samlp]) self.ecp_conversation(respdict, idp_entity_id) # should by now be authenticated so this should go smoothly response = op(**opargs) except (soap.XmlParseError, AssertionError, KeyError): pass #print "RESP",response, self.http.response if not response: if self.http.response.status != 404: raise Exception("Error performing operation: %s" % (self.http.error_description, )) return response
def phase2(self, authn_request, rc_url, idp_entity_id, headers=None, idp_endpoint=None, sign=False, sec=""): """ Doing the second phase of the ECP conversation :param authn_request: The AuthenticationRequest :param rc_url: The assertion consumer service url :param idp_entity_id: The EntityID of the IdP :param headers: Possible extra headers :param idp_endpoint: Where to send it all :param sign: If the message should be signed :param sec: security context :return: The response from the IdP """ idp_request = soap.make_soap_enveloped_saml_thingy(authn_request) if sign: _signed = sec.sign_statement_using_xmlsec( idp_request, class_name(authn_request), nodeid=authn_request.id) idp_request = _signed if not idp_endpoint: idp_endpoint = self.find_idp_endpoint(idp_entity_id) if self.user and self.passwd: self.http.add_credentials(self.user, self.passwd) self._debug_info("[P2] Sending request: %s" % idp_request) # POST the request to the IdP response = self.http.post(idp_request, headers=headers, path=idp_endpoint) self._debug_info("[P2] Got IdP response: %s" % response) if response is None or response is False: raise Exception( "Request to IdP failed (%s): %s" % (self.http.response.status, self.http.error_description)) # SAMLP response in a SOAP envelope body, ecp response in headers respdict = soap.class_instances_from_soap_enveloped_saml_thingies( response, [paos, ecp, samlp]) if respdict is None: raise Exception("Unexpected reply from the IdP") self._debug_info("[P2] IdP response dict: %s" % respdict) idp_response = respdict["body"] assert idp_response.c_tag == "Response" self._debug_info("[P2] IdP AUTHN response: %s" % idp_response) _ecp_response = None for item in respdict["header"]: if item.c_tag == "Response" and\ item.c_namespace == ecp.NAMESPACE: _ecp_response = item _acs_url = _ecp_response.assertion_consumer_service_url if rc_url != _acs_url: error = ("response_consumer_url '%s' does not match" % rc_url, "assertion_consumer_service_url '%s" % _acs_url) # Send an error message to the SP fault_text = soap.soap_fault(error) _ = self.http.post(fault_text, path=rc_url) # Raise an exception so the user knows something went wrong raise Exception(error) return idp_response