def test_slo_soap(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": nid, "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after": soon, "user": { "givenName": "Leo", "sn": "Laport", } } sp = client.Saml2Client(config_file="server_conf") sp.users.add_information_about_person(sinfo) req_id, logout_request = sp.create_logout_request( name_id=nid, 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) self.server.ident.close() with closing(Server("idp_soap_conf")) as idp: request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
def create_ecp_authn_request(self, entityid=None, relay_state="", sign=False, **kwargs): """ Makes an authentication request. :param entityid: The entity ID of the IdP to send the request to :param relay_state: A token that can be used by the SP to know where to continue the conversation with the client :param sign: Whether the request should be signed or not. :return: SOAP message with the AuthnRequest """ # ---------------------------------------- # <paos:Request> # ---------------------------------------- my_url = self.service_urls(BINDING_PAOS)[0] # must_understand and act according to the standard # paos_request = paos.Request(must_understand="1", actor=ACTOR, response_consumer_url=my_url, service=ECP_SERVICE) # ---------------------------------------- # <ecp:RelayState> # ---------------------------------------- relay_state = ecp.RelayState(actor=ACTOR, must_understand="1", text=relay_state) # ---------------------------------------- # <samlp:AuthnRequest> # ---------------------------------------- try: authn_req = kwargs["authn_req"] except KeyError: try: _binding = kwargs["binding"] except KeyError: _binding = BINDING_SOAP kwargs["binding"] = _binding logger.debug("entityid: %s, binding: %s" % (entityid, _binding)) # The IDP publishes support for ECP by using the SOAP binding on # SingleSignOnService _, location = self.pick_binding("single_sign_on_service", [_binding], entity_id=entityid) authn_req = self.create_authn_request( location, service_url_binding=BINDING_PAOS, **kwargs) # ---------------------------------------- # The SOAP envelope # ---------------------------------------- soap_envelope = make_soap_enveloped_saml_thingy(authn_req, [paos_request, relay_state]) return authn_req.id, "%s" % soap_envelope
def test_slo_soap(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": nid, "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) req_id, logout_request = sp.create_logout_request( name_id=nid, 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) self.server.ident.close() with closing(Server("idp_soap_conf")) as idp: request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
def attribute_query_endpoint(self, xml_str, binding): if binding == BINDING_SOAP: _str = parse_soap_enveloped_saml_attribute_query(xml_str) else: _str = xml_str aquery = attribute_query_from_string(_str) extra = {"eduPersonAffiliation": "faculty"} #userid = "Pavill" name_id = aquery.subject.name_id attr_resp = self.create_attribute_response(extra, aquery.id, None, sp_entity_id=aquery.issuer .text, name_id=name_id, attributes=aquery.attribute) if binding == BINDING_SOAP: # SOAP packing #headers = {"content-type": "application/soap+xml"} soap_message = make_soap_enveloped_saml_thingy(attr_resp) # if self.sign and self.sec: # _signed = self.sec.sign_statement_using_xmlsec(soap_message, # class_name(attr_resp), # nodeid=attr_resp.id) # soap_message = _signed response = "%s" % soap_message else: # Just POST response = "%s" % attr_resp return DummyResponse(200, response)
def logout_endpoint(self, xml_str, binding): if binding == BINDING_SOAP: _str = parse_soap_enveloped_saml_logout_request(xml_str) else: _str = xml_str req = logout_request_from_string(_str) _resp = self.create_logout_response(req, [binding]) if binding == BINDING_SOAP: # SOAP packing #headers = {"content-type": "application/soap+xml"} soap_message = make_soap_enveloped_saml_thingy(_resp) # if self.sign and self.sec: # _signed = self.sec.sign_statement_using_xmlsec(soap_message, # class_name(attr_resp), # nodeid=attr_resp.id) # soap_message = _signed response = "%s" % soap_message else: # Just POST response = "%s" % _resp return DummyResponse(200, response)
def create_ecp_authn_request(self, entityid=None, relay_state="", sign=False, **kwargs): """ Makes an authentication request. :param entityid: The entity ID of the IdP to send the request to :param relay_state: A token that can be used by the SP to know where to continue the conversation with the client :param sign: Whether the request should be signed or not. :return: SOAP message with the AuthnRequest """ # ---------------------------------------- # <paos:Request> # ---------------------------------------- my_url = self.service_urls(BINDING_PAOS)[0] # must_understand and act according to the standard # paos_request = paos.Request(must_understand="1", actor=ACTOR, response_consumer_url=my_url, service=ECP_SERVICE) # ---------------------------------------- # <ecp:RelayState> # ---------------------------------------- relay_state = ecp.RelayState(actor=ACTOR, must_understand="1", text=relay_state) # ---------------------------------------- # <samlp:AuthnRequest> # ---------------------------------------- try: authn_req = kwargs["authn_req"] try: req_id = authn_req.id except AttributeError: req_id = 0 # Unknown but since it's SOAP it doesn't matter except KeyError: try: _binding = kwargs["binding"] except KeyError: _binding = BINDING_SOAP kwargs["binding"] = _binding logger.debug("entityid: %s, binding: %s", entityid, _binding) # The IDP publishes support for ECP by using the SOAP binding on # SingleSignOnService _, location = self.pick_binding("single_sign_on_service", [_binding], entity_id=entityid) req_id, authn_req = self.create_authn_request( location, service_url_binding=BINDING_PAOS, **kwargs) # ---------------------------------------- # The SOAP envelope # ---------------------------------------- soap_envelope = make_soap_enveloped_saml_thingy( authn_req, [paos_request, relay_state]) return req_id, "%s" % soap_envelope
def ecp_conversation(self, respdict, idp_entity_id=None): """ """ if respdict is None: raise Exception("Unexpected reply from the SP") self._debug_info("[P1] SP response dict: %s" % respdict) # AuthnRequest in the body or not authn_request = respdict["body"] assert authn_request.c_tag == "AuthnRequest" # ecp.RelayState among headers _relay_state = None _paos_request = None for item in respdict["header"]: if item.c_tag == "RelayState" and\ item.c_namespace == ecp.NAMESPACE: _relay_state = item if item.c_tag == "Request" and\ item.c_namespace == paos.NAMESPACE: _paos_request = item _rc_url = _paos_request.response_consumer_url # ********************** # Phase 2 - talk to the IdP # ********************** idp_response = self.phase2(authn_request, _rc_url, idp_entity_id) # ********************************** # Phase 3 - back to the SP # ********************************** sp_response = soap.make_soap_enveloped_saml_thingy(idp_response, [_relay_state]) self._debug_info("[P3] Post to SP: %s" % sp_response) headers = {'Content-Type': 'application/vnd.paos+xml', } # POST the package from the IdP to the SP response = self.http.post(sp_response, headers, _rc_url) if not response: if self.http.response.status == 302: # ignore where the SP is redirecting us to and go for the # url I started off with. pass else: print self.http.error_description raise Exception( "Error POSTing package to SP: %s" % self.http.response.reason) self._debug_info("[P3] IdP response: %s" % response) self.done_ecp = True logger.debug("Done ECP") return None
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 ecp_conversation(self, respdict, idp_entity_id=None): """ """ if respdict is None: raise Exception("Unexpected reply from the SP") self._debug_info("[P1] SP response dict: %s" % respdict) # AuthnRequest in the body or not authn_request = respdict["body"] assert authn_request.c_tag == "AuthnRequest" # ecp.RelayState among headers _relay_state = None _paos_request = None for item in respdict["header"]: if item.c_tag == "RelayState" and\ item.c_namespace == ecp.NAMESPACE: _relay_state = item if item.c_tag == "Request" and\ item.c_namespace == paos.NAMESPACE: _paos_request = item _rc_url = _paos_request.response_consumer_url # ********************** # Phase 2 - talk to the IdP # ********************** idp_response = self.phase2(authn_request, _rc_url, idp_entity_id) # ********************************** # Phase 3 - back to the SP # ********************************** sp_response = soap.make_soap_enveloped_saml_thingy( idp_response, [_relay_state]) self._debug_info("[P3] Post to SP: %s" % sp_response) headers = { 'Content-Type': 'application/vnd.paos+xml', } # POST the package from the IdP to the SP response = self.http.post(sp_response, headers, _rc_url) if not response: if self.http.response.status == 302: # ignore where the SP is redirecting us to and go for the # url I started off with. pass else: print self.http.error_description raise Exception("Error POSTing package to SP: %s" % self.http.response.reason) self._debug_info("[P3] IdP response: %s" % response) self.done_ecp = True logger.debug("Done ECP") return None
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