def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_cert=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: sign_class = [(class_name(response), response.id)] if sign: response.signature = pre_signature_part( response.id, self.sec.my_cert, 1) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part()) #template(response.assertion.id)) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_cert=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: if sign: response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part()) # template(response.assertion.id)) if sign: if to_sign: signed_instance_factory(response, self.sec, to_sign) else: # default is to sign the whole response if anything sign_class = [(class_name(response), response.id)] return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def do_authz_decision_query(self, entityid, assertion=None, sign=False): authz_decision_query = self.authz_decision_query(entityid, assertion) for destination in self.config.authz_services(entityid): to_sign = [] if sign: authz_decision_query.signature = pre_signature_part( authz_decision_query.id, self.sec.my_cert, 1) to_sign.append((class_name(authz_decision_query), authz_decision_query.id)) authz_decision_query = signed_instance_factory( authz_decision_query, self.sec, to_sign) response = send_using_soap(authz_decision_query, destination, self.config.key_file, self.config.cert_file, ca_certs=self.config.ca_certs) if response: logger.info("Verifying response") response = self.authz_decision_query_response(response) if response: #not_done.remove(entity_id) logger.info("OK response from %s" % destination) return response else: logger.info("NOT OK response from %s" % destination) return None
def test_exception_sign_verify_with_cert_from_instance(self): assertion = factory( saml.Assertion, version="2.0", id="id-11100", issuer=saml.Issuer(text="the-issuer"), issue_instant="2009-10-30T13:20:28Z", attribute_statement=do_attribute_statement({ ("name:surName", "nameformat", "surName"): ("Föö", ""), ("name:givenName", "nameformat", "givenName"): ("Bär", ""), }) ) response = factory( samlp.Response, issuer=saml.Issuer(text="the-isser"), status=success_status_factory(), assertion=assertion, version="2.0", issue_instant="2099-10-30T13:20:28Z", id="id-22222", signature=sigver.pre_signature_part( "id-22222", self.sec.my_cert ), ) to_sign = [(class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) response2 = response_from_string(s_response) # Change something that should make everything fail response2.id = "id-23456" with raises(sigver.SignatureError): self.sec._check_signature(s_response, response2, class_name(response2))
def test_exception_sign_verify_with_cert_from_instance(self): assertion = factory(saml.Assertion, version="2.0", id="11100", issue_instant="2009-10-30T13:20:28Z", #signature= sigver.pre_signature_part("11100", # self.sec.my_cert), attribute_statement=do_attribute_statement({ ("", "", "surName"): ("Foo", ""), ("", "", "givenName"): ("Bar", ""), }) ) response = factory(samlp.Response, assertion=assertion, id="22222", signature=sigver.pre_signature_part("22222", self.sec .my_cert)) to_sign = [(class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) response2 = response_from_string(s_response) # Change something that should make everything fail response2.id = "23456" raises(sigver.SignatureError, self.sec._check_signature, s_response, response2, class_name(response2))
def test_sign_verify_with_cert_from_instance(self): response = factory(samlp.Response, assertion=self._assertion, id="22222", signature=sigver.pre_signature_part("22222", self.sec .my_cert)) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) response2 = response_from_string(s_response) ci = "".join(sigver.cert_from_instance(response2)[0].split()) assert ci == self.sec.my_cert res = self.sec.verify_signature(s_response, node_name=class_name(samlp.Response())) assert res res = self.sec._check_signature(s_response, response2, class_name(response2), s_response) assert res == response2
def do_authz_decision_query(self, entityid, assertion=None, log=None, sign=False): authz_decision_query = self.authz_decision_query(entityid, assertion) for destination in self.config.authz_services(entityid): to_sign = [] if sign: authz_decision_query.signature = pre_signature_part(authz_decision_query.id, self.sec.my_cert, 1) to_sign.append((class_name(authz_decision_query), authz_decision_query.id)) authz_decision_query = signed_instance_factory(authz_decision_query, self.sec, to_sign) response = send_using_soap( authz_decision_query, destination, self.config.key_file, self.config.cert_file, log=log, ca_certs=self.config.ca_certs, ) if response: if log: log.info("Verifying response") response = self.authz_decision_query_response(response, log) if response: # not_done.remove(entity_id) if log: log.info("OK response from %s" % destination) return response else: if log: log.info("NOT OK response from %s" % destination) return None
def test_sign_verify_assertion_with_cert_from_instance(self): assertion = factory(saml.Assertion, version="2.0", id="11100", issue_instant="2009-10-30T13:20:28Z", signature=sigver.pre_signature_part("11100", self.sec .my_cert), attribute_statement=do_attribute_statement({ ("", "", "surName"): ("Fox", ""), ("", "", "givenName"): ("Bear", ""), }) ) to_sign = [(class_name(assertion), assertion.id)] s_assertion = sigver.signed_instance_factory(assertion, self.sec, to_sign) print(s_assertion) ass = assertion_from_string(s_assertion) ci = "".join(sigver.cert_from_instance(ass)[0].split()) assert ci == self.sec.my_cert res = self.sec.verify_signature(s_assertion, node_name=class_name(ass)) assert res res = self.sec._check_signature(s_assertion, ass, class_name(ass)) assert res
def test_sign_response_2(self): assertion2 = factory( saml.Assertion, version= "2.0", id= "11122", issue_instant= "2009-10-30T13:20:28Z", signature= sigver.pre_signature_part("11122", self.sec.my_cert), attribute_statement=do_attribute_statement({ ("","","surName"): ("Fox",""), ("","","givenName") :("Bear",""), }) ) response = factory(samlp.Response, assertion=assertion2, id="22233", signature=sigver.pre_signature_part("22233", self.sec.my_cert)) to_sign = [(class_name(assertion2), assertion2.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) assert s_response is not None response2 = response_from_string(s_response) sass = response2.assertion[0] assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', 'version', 'signature', 'id']) assert sass.version == "2.0" assert sass.id == "11122" item = self.sec.check_signature(response2, class_name(response), s_response) assert isinstance(item, samlp.Response)
def create_assertion_id_request_response(self, assertion_id, sign=None, sign_alg=None, digest_alg=None, **kwargs): try: (assertion, to_sign) = self.session_db.get_assertion(assertion_id) except KeyError: raise Unknown if to_sign: if assertion.signature is None: # XXX self.signing_algorithm self.digest_algorithm defined by entity # XXX this should be handled through entity.py # XXX sig/digest-allowed should be configurable sign_alg = sign_alg or self.signing_algorithm digest_alg = digest_alg or self.digest_algorithm assertion.signature = pre_signature_part( assertion.id, self.sec.my_cert, 1, sign_alg=sign_alg, digest_alg=digest_alg, ) return signed_instance_factory(assertion, self.sec, to_sign) else: return assertion
def test_sign_response(self): response = factory(samlp.Response, assertion=self._assertion, id="22222", signature=sigver.pre_signature_part("22222", self.sec .my_cert)) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) assert s_response is not None print(s_response) response = response_from_string(s_response) sass = response.assertion[0] print(sass) assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', 'version', 'signature', 'id']) assert sass.version == "2.0" assert sass.id == "11111" item = self.sec.check_signature(response, class_name(response), s_response) assert isinstance(item, samlp.Response) assert item.id == "22222"
def test_sign_verify_with_cert_from_instance(self): response = factory( samlp.Response, issuer=saml.Issuer(text="the-isser"), status=success_status_factory(), assertion=self._assertion, version="2.0", issue_instant="2099-10-30T13:20:28Z", id="id-22222", signature=sigver.pre_signature_part( "id-22222", self.sec.my_cert ), ) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) response2 = response_from_string(s_response) ci = "".join(sigver.cert_from_instance(response2)[0].split()) assert ci == self.sec.my_cert res = self.sec.verify_signature(s_response, node_name=class_name(samlp.Response())) assert res res = self.sec._check_signature(s_response, response2, class_name(response2), s_response) assert res == response2
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if sign: return self.sign(response, to_sign=to_sign) elif to_sign: return signed_instance_factory(response, self.sec, to_sign) else: return response
def create_assertion_id_request_response(self, assertion_id, sign=False, sign_alg=None, digest_alg=None, **kwargs): """ :param assertion_id: :param sign: :return: """ try: (assertion, to_sign) = self.session_db.get_assertion(assertion_id) except KeyError: raise Unknown if to_sign: if assertion.signature is None: assertion.signature = pre_signature_part(assertion.id, self.sec.my_cert, 1, sign_alg=sign_alg, digest_alg=digest_alg) return signed_instance_factory(assertion, self.sec, to_sign) else: return assertion
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if sign: self.sign(response, to_sign=to_sign) elif to_sign: return signed_instance_factory(response, self.sec, to_sign) else: return response
def slo(self, request): """ generate a SAML2 logout request; reset session; return IDP URL """ session = request.SESSION session.set(self.session_auth_key, False) del session[self.session_user_properties] config = self._saml2_config() scl = Saml2Client(config) samluid = session.get(self.session_samluid_key, "") entityid = config.metadata.keys()[0] sp_url = self.saml2_sp_url actual_url = request.get("ACTUAL_URL", "") if not actual_url.startswith(sp_url): # the request was made from within a context we cannot handle return None session.set(self.session_storedurl_key, request.URL1) # we cannot simply call global_logout on the client since it doesn't know about our user... srvs = scl.metadata.single_logout_service(entityid, BINDING_HTTP_REDIRECT, "idpsso") destination = destinations(srvs)[0] samlrequest = scl.create_logout_request(destination, entityid, name_id=saml.NameID(text=samluid)) samlrequest.session_index = samlp.SessionIndex(session.get(self.session_samlsessionindex_key)) to_sign = [] samlrequest = signed_instance_factory(samlrequest, scl.sec, to_sign) logger.info("SSO logout request: %s" % samlrequest.to_string()) session_id = samlrequest.id rstate = scl._relay_state(session_id) msg = http_redirect_message(samlrequest, destination, rstate) headers = dict(msg["headers"]) location = headers["Location"] logger.info("attempting to post: {loc}".format(loc=headers["Location"])) return location
def test_sign_verify(self): response = factory( samlp.Response, issuer=saml.Issuer(text="the-isser"), status=success_status_factory(), assertion=self._assertion, version="2.0", issue_instant="2099-10-30T13:20:28Z", id="id-22233", signature=sigver.pre_signature_part( "id-22233", self.sec.my_cert ), ) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) print(s_response) res = self.sec.verify_signature(s_response, node_name=class_name(samlp.Response())) print(res) assert res
def test_sign_response(self): response = factory( samlp.Response, issuer=saml.Issuer(text="the-isser"), status=success_status_factory(), assertion=self._assertion, version="2.0", issue_instant="2099-10-30T13:20:28Z", id="id-22222", signature=sigver.pre_signature_part( "id-22222", self.sec.my_cert ), ) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) assert s_response is not None print(s_response) response = response_from_string(s_response) sass = response.assertion[0] print(sass) assert _eq(sass.keyswv(), ['issuer', 'attribute_statement', 'issue_instant', 'version', 'signature', 'id']) assert sass.version == "2.0" assert sass.id == "id-11111" item = self.sec.check_signature(response, class_name(response), s_response) assert isinstance(item, samlp.Response) assert item.id == "id-22222"
def sign(self, msg, mid=None, to_sign=None): if msg.signature is None: msg.signature = pre_signature_part(msg.id, self.sec.my_cert, 1) if mid is None: mid = msg.id try: to_sign.append([(class_name(msg), mid)]) except AttributeError: to_sign = [(class_name(msg), mid)] logger.info("REQUEST: %s" % msg) return signed_instance_factory(msg, self.sec, to_sign)
def test_sign_verify(self): response = factory( samlp.Response, assertion=self._assertion, id="22233", signature=sigver.pre_signature_part("22233", self.sec.my_cert)) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) print(s_response) res = self.sec.verify_signature(s_response, node_name=class_name(samlp.Response())) print(res) assert res
def _message(self, request_cls, destination=None, id=0, consent=None, extensions=None, sign=False, **kwargs): """ Some parameters appear in all requests so simplify by doing it in one place :param request_cls: The specific request type :param destination: The recipient :param id: A message identifier :param consent: Whether the principal have given her consent :param extensions: Possible extensions :param kwargs: Key word arguments specific to one request type :return: An instance of the request_cls """ if not id: id = sid(self.seed) req = request_cls(id=id, version=VERSION, issue_instant=instant(), issuer=self._issuer(), **kwargs) if destination: req.destination = destination if consent: req.consent = consent if extensions: req.extensions = extensions if sign: req.signature = pre_signature_part(req.id, self.sec.my_cert, 1) to_sign = [(class_name(req), req.id)] else: to_sign = [] logger.info("REQUEST: %s" % req) return signed_instance_factory(req, self.sec, to_sign)
def test_sign_verify(self): response = factory(samlp.Response, assertion=self._assertion, id="22233", signature=sigver.pre_signature_part("22233", self.sec.my_cert)) to_sign = [(class_name(self._assertion), self._assertion.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) print s_response res = self.sec.verify_signature("%s" % s_response, node_name=class_name(samlp.Response())) print res assert res
def sign(self, msg, mid=None, to_sign=None, sign_prepare=False): if msg.signature is None: msg.signature = pre_signature_part(msg.id, self.sec.my_cert, 1) if sign_prepare: return msg if mid is None: mid = msg.id try: to_sign += [(class_name(msg), mid)] except (AttributeError, TypeError): to_sign = [(class_name(msg), mid)] logger.info("REQUEST: %s" % msg) return signed_instance_factory(msg, self.sec, to_sign)
def sign(self, msg, mid=None, to_sign=None, sign_prepare=False): if msg.signature is None: msg.signature = pre_signature_part(msg.id, self.sec.my_cert, 1) if sign_prepare: return msg if mid is None: mid = msg.id try: to_sign.append([(class_name(msg), mid)]) except AttributeError: to_sign = [(class_name(msg), mid)] logger.info("REQUEST: %s" % msg) return signed_instance_factory(msg, self.sec, to_sign)
def create_logout_response(self, request, binding, status=None, sign=False, issuer=None): """ Create a LogoutResponse. What is returned depends on which binding is used. :param request: The request this is a response to :param binding: Which binding the request came in over :param status: The return status of the response operation :param issuer: The issuer of the message :return: A logout message. """ mid = sid() if not status: status = success_status_factory() # response and packaging differs depending on binding response = "" if binding in [BINDING_SOAP, BINDING_HTTP_POST]: response = logoutresponse_factory(sign=sign, id = mid, in_response_to = request.id, status = status) elif binding == BINDING_HTTP_REDIRECT: sp_entity_id = request.issuer.text.strip() srvs = self.metadata.single_logout_service(sp_entity_id, "spsso") if not srvs: raise Exception("Nowhere to send the response") destination = destinations(srvs)[0] _issuer = self.issuer(issuer) response = logoutresponse_factory(sign=sign, id = mid, in_response_to = request.id, status = status, issuer = _issuer, destination = destination, sp_entity_id = sp_entity_id, instant=instant()) if sign: to_sign = [(class_name(response), mid)] response = signed_instance_factory(response, self.sec, to_sign) logger.info("Response: %s" % (response,)) return response
def test_sign_response_2(self): assertion2 = factory( saml.Assertion, version="2.0", id="id-11122", issuer=saml.Issuer(text="the-issuer-2"), issue_instant="2009-10-30T13:20:28Z", signature=sigver.pre_signature_part("id-11122", self.sec .my_cert), attribute_statement=do_attribute_statement({ ("name:surName", "nameformat", "surName"): ("Räv", ""), ("name:givenName", "nameformat", "givenName"): ("Björn", ""), }) ) response = factory( samlp.Response, issuer=saml.Issuer(text="the-isser"), status=success_status_factory(), assertion=assertion2, version="2.0", issue_instant="2099-10-30T13:20:28Z", id="id-22233", signature=sigver.pre_signature_part("id-22233", self.sec.my_cert), ) to_sign = [(class_name(assertion2), assertion2.id), (class_name(response), response.id)] s_response = sigver.signed_instance_factory(response, self.sec, to_sign) assert s_response is not None response2 = response_from_string(s_response) sass = response2.assertion[0] assert _eq(sass.keyswv(), ['issuer', 'attribute_statement', 'issue_instant', 'version', 'signature', 'id']) assert sass.version == "2.0" assert sass.id == "id-11122" item = self.sec.check_signature(response2, class_name(response), s_response) assert isinstance(item, samlp.Response)
def sign(self, msg, mid=None, to_sign=None, sign_prepare=False, sign_alg=None, digest_alg=None): if msg.signature is None: msg.signature = pre_signature_part(msg.id, self.sec.my_cert, 1, sign_alg=sign_alg, digest_alg=digest_alg) if sign_prepare: return msg if mid is None: mid = msg.id try: to_sign += [(class_name(msg), mid)] except (AttributeError, TypeError): to_sign = [(class_name(msg), mid)] logger.info("REQUEST: %s", msg) return signed_instance_factory(msg, self.sec, to_sign)
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, **kwargs): """ Create a Response that adhers to the ??? profile. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: What other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self.issuer(issuer) response = response_factory( issuer=_issuer, in_response_to = in_response_to, status = status, ) if consumer_url: response.destination = consumer_url for key, val in kwargs.items(): setattr(response, key, val) if sign: try: to_sign.append((class_name(response), response.id)) except AttributeError: to_sign = [(class_name(response), response.id)] return signed_instance_factory(response, self.sec, to_sign)
def slo(self, request): """ generate a SAML2 logout request; reset session; return IDP URL """ session = request.SESSION session.set(self.session_auth_key, False) del session[self.session_user_properties] config = self._saml2_config() scl = Saml2Client(config) samluid = session.get(self.session_samluid_key, '') entityid = config.metadata.keys()[0] sp_url = self.saml2_sp_url actual_url = request.get("ACTUAL_URL", '') if not actual_url.startswith(sp_url): # the request was made from within a context we cannot handle return None session.set(self.session_storedurl_key, request.URL1) # we cannot simply call global_logout on the client since it doesn't know about our user... srvs = scl.metadata.single_logout_service(entityid, BINDING_HTTP_REDIRECT, "idpsso") destination = destinations(srvs)[0] samlrequest = scl.create_logout_request( destination, entityid, name_id=saml.NameID(text=samluid)) samlrequest.session_index = samlp.SessionIndex( session.get(self.session_samlsessionindex_key)) to_sign = [] samlrequest = signed_instance_factory(samlrequest, scl.sec, to_sign) logger.info('SSO logout request: %s' % samlrequest.to_string()) session_id = samlrequest.id rstate = scl._relay_state(session_id) msg = http_redirect_message(samlrequest, destination, rstate) headers = dict(msg['headers']) location = headers['Location'] logger.info( 'attempting to post: {loc}'.format(loc=headers['Location'])) return location
def send_idp_response(self, req, resp): """ :param req: The expected request :param resp: The response type to be used :return: A response """ # make sure I got the request I expected assert isinstance(self.saml_request.message, req._class) try: self.test_sequence(req.tests["post"]) except KeyError: pass # Pick information from the request that should be in the response args = self.instance.response_args(self.saml_request.message, [resp._binding]) _mods = list(resp.__mro__[:]) _mods.reverse() for m in _mods: try: args.update(self.json_config["args"][m.__name__]) except KeyError: pass args.update(resp._response_args) for param in ["identity", "userid"]: if param in self.json_config: args[param] = self.json_config[param] if resp == ErrorResponse: func = getattr(self.instance, "create_error_response") else: _op = camel2underscore.sub(r'_\1', req._class.c_tag).lower() func = getattr(self.instance, "create_%s_response" % _op) sign = [] for styp in ["sign_assertion", "sign_response"]: if styp in args: del args[styp] sign.append(styp) response = func(**args) response = resp(self).pre_processing(response) # and now for signing if sign: to_sign = [] # Order is important, first assertion and then response if both if "sign_assertion" in sign: to_sign = [(class_name(response.assertion), response.assertion.id)] response.assertion.signature = pre_signature_part( response.assertion.id, self.instance.sec.my_cert, 1) if "sign_response" in sign: to_sign = [(class_name(response), response.id)] response.signature = pre_signature_part( response.id, self.instance.sec.my_cert, 1) response = signed_instance_factory(response, self.instance.sec, to_sign) info = self.instance.apply_binding(resp._binding, response, args["destination"], self.relay_state, "SAMLResponse", resp._sign) if resp._binding == BINDING_HTTP_REDIRECT: url = None for param, value in info["headers"]: if param == "Location": url = value break self.last_response = self.instance.send(url) elif resp._binding == BINDING_HTTP_POST: resp = base64.b64encode("%s" % response) info["data"] = urllib.urlencode({ "SAMLResponse": resp, "RelayState": self.relay_state }) info["method"] = "POST" info["headers"] = { 'Content-type': 'application/x-www-form-urlencoded' } self.last_response = self.instance.send(**info) self._log_response(self.last_response)
def logout_response(self, request, bindings, status=None, sign=False, issuer=None): """ Create a LogoutResponse. What is returned depends on which binding is used. :param request: The request this is a response to :param bindings: Which bindings that can be used to send the response :param status: The return status of the response operation :param issuer: The issuer of the message :return: A 3-tuple consisting of HTTP return code, HTTP headers and possibly a message. """ sp_entity_id = request.issuer.text.strip() binding = None destinations = [] for binding in bindings: destinations = self.conf.single_logout_services(sp_entity_id, binding) if destinations: break if not destinations: if self.log: self.log.error("Not way to return a response !!!") return ("412 Precondition Failed", [("Content-type", "text/html")], ["No return way defined"]) # Pick the first destination = destinations[0] if self.log: self.log.info("Logout Destination: %s, binding: %s" % (destination, binding)) if not status: status = success_status_factory() mid = sid() rcode = "200 OK" # response and packaging differs depending on binding if binding == BINDING_SOAP: response = logoutresponse_factory(sign=sign, id=mid, in_response_to=request.id, status=status) if sign: to_sign = [(class_name(response), mid)] response = signed_instance_factory(response, self.sec, to_sign) (headers, message) = http_soap_message(response) else: _issuer = self.issuer(issuer) response = logoutresponse_factory( sign=sign, id=mid, in_response_to=request.id, status=status, issuer=_issuer, destination=destination, sp_entity_id=sp_entity_id, instant=instant(), ) if sign: to_sign = [(class_name(response), mid)] response = signed_instance_factory(response, self.sec, to_sign) if self.log: self.log.info("Response: %s" % (response,)) if binding == BINDING_HTTP_REDIRECT: (headers, message) = http_redirect_message(response, destination, typ="SAMLResponse") rcode = "302 Found" else: (headers, message) = http_post_message(response, destination, typ="SAMLResponse") return rcode, headers, message
def send_idp_response(self, req_flow, resp_flow): """ :param req_flow: The flow to check the request :param resp_flow: The flow to prepare the response :return: The SP's HTTP response on receiving the SAML response """ # Pick information from the request that should be in the response args = self.instance.response_args(self.saml_request.message, [resp_flow._binding]) _mods = list(resp_flow.__mro__[:]) _mods.reverse() for m in _mods: try: args.update(self.json_config["args"][m.__name__]) except KeyError: pass args.update(resp_flow._response_args) for param in ["identity", "userid"]: if param in self.json_config: args[param] = self.json_config[param] if resp_flow == ErrorResponse: func = getattr(self.instance, "create_error_response") else: _op = camel2underscore.sub(r'_\1', req_flow._class.c_tag).lower() func = getattr(self.instance, "create_%s_response" % _op) # get from config which parts shall be signed sign = [] for styp in ["sign_assertion", "sign_response"]: if styp in args: try: if args[styp].lower() == "always": sign.append(styp) del args[styp] except (AttributeError, TypeError): raise AssertionError('config parameters "sign_assertion", ' '"sign_response" must be of type string') response = func(**args) response = resp_flow(self).pre_processing(response) # and now for signing if sign: to_sign = [] try: _digest_alg=args["sign_digest_alg"] except KeyError: _digest_alg=None try: _sign_alg=args["sign_signature_alg"] except KeyError: _sign_alg=None # Order is important, first assertion and then response if both if "sign_assertion" in sign: to_sign = [(class_name(response.assertion), response.assertion.id)] response.assertion.signature = pre_signature_part( response.assertion.id, self.instance.sec.my_cert, 1, digest_alg=_digest_alg, sign_alg=_sign_alg) if "sign_response" in sign: to_sign = [(class_name(response), response.id)] response.signature = pre_signature_part( response.id, self.instance.sec.my_cert, 1, digest_alg=_digest_alg, sign_alg=_sign_alg) response = signed_instance_factory(response, self.instance.sec, to_sign) info = self.instance.apply_binding(resp_flow._binding, response, args["destination"], self.relay_state, "SAMLResponse", resp_flow._sign) if resp_flow._binding == BINDING_HTTP_REDIRECT: url = None for param, value in info["headers"]: if param == "Location": url = value break self.last_response = self.instance.send(url) elif resp_flow._binding == BINDING_HTTP_POST: resp_flow = base64.b64encode("%s" % response) info["data"] = urllib.urlencode({"SAMLResponse": resp_flow, "RelayState": self.relay_state}) info["method"] = "POST" info["headers"] = { 'Content-type': 'application/x-www-form-urlencoded'} self.last_response = self.instance.send(**info) try: self.last_content = self.last_response.content except AttributeError: self.last_content = None self._log_response(self.last_response)
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, sp_entity_id=None, encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, encrypt_cert_advice=None, encrypt_cert_assertion=None,sign_assertion=None, pefim=False, **kwargs): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is true, then will the function try to encrypt the assertion in the the advice element of the main assertion. Only one assertion element is allowed in the advice element, if multiple assertions exists in the advice element the main assertion will be encrypted instead, since it's no point to encrypt If encrypted_advice_attributes is false the main assertion will be encrypted. Since the same key :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param sp_entity_id: Entity ID for the calling service provider. :param encrypt_assertion: True if assertions should be encrypted. :param encrypt_assertion_self_contained: True if all encrypted assertions should have alla namespaces selfcontained. :param encrypted_advice_attributes: True if assertions in the advice element should be encrypted. :param encrypt_cert_advice: Certificate to be used for encryption of assertions in the advice element. :param encrypt_cert_assertion: Certificate to be used for encryption of assertions. :param sign_assertion: True if assertions should be signed. :param pefim: True if a response according to the PEFIM profile should be created. :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) has_encrypt_cert = self.has_encrypt_cert_in_metadata(sp_entity_id) if not has_encrypt_cert and encrypt_cert_advice is None: encrypted_advice_attributes = False if not has_encrypt_cert and encrypt_cert_assertion is None: encrypt_assertion = False if encrypt_assertion or (encrypted_advice_attributes and response.assertion.advice is not None and len(response.assertion.advice.assertion) == 1): if sign: response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) sign_class = [(class_name(response), response.id)] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) encrypt_advice = False if encrypted_advice_attributes and response.assertion.advice is not None \ and len(response.assertion.advice.assertion) > 0: _assertions = response.assertion if not isinstance(_assertions, list): _assertions = [_assertions] for _assertion in _assertions: _assertion.advice.encrypted_assertion = [] _assertion.advice.encrypted_assertion.append(EncryptedAssertion()) _advice_assertions = copy.deepcopy(_assertion.advice.assertion) _assertion.advice.assertion = [] if not isinstance(_advice_assertions, list): _advice_assertions = [_advice_assertions] for tmp_assertion in _advice_assertions: to_sign_advice = [] if sign_assertion and not pefim: tmp_assertion.signature = pre_signature_part(tmp_assertion.id, self.sec.my_cert, 1) to_sign_advice.append((class_name(tmp_assertion), tmp_assertion.id)) #tmp_assertion = response.assertion.advice.assertion[0] _assertion.advice.encrypted_assertion[0].add_extension_element(tmp_assertion) if encrypt_assertion_self_contained: advice_tag = response.assertion.advice._to_element_tree().tag assertion_tag = tmp_assertion._to_element_tree().tag response = \ response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion( assertion_tag, advice_tag) node_xpath = ''.join(["/*[local-name()=\"%s\"]" % v for v in ["Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion"]]) if to_sign_advice: response = signed_instance_factory(response, self.sec, to_sign_advice) response = self._encrypt_assertion(encrypt_cert_advice, sp_entity_id, response, node_xpath=node_xpath) response = response_from_string(response) if encrypt_assertion: to_sign_assertion = [] if sign_assertion is not None and sign_assertion: _assertions = response.assertion if not isinstance(_assertions, list): _assertions = [_assertions] for _assertion in _assertions: _assertion.signature = pre_signature_part(_assertion.id, self.sec.my_cert, 1) to_sign_assertion.append((class_name(_assertion), _assertion.id)) if encrypt_assertion_self_contained: try: assertion_tag = response.assertion._to_element_tree().tag except: assertion_tag = response.assertion[0]._to_element_tree().tag response = pre_encrypt_assertion(response) response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion( assertion_tag) if to_sign_assertion: response = signed_instance_factory(response, self.sec, to_sign_assertion) response = self._encrypt_assertion(encrypt_cert_assertion, sp_entity_id, response) else: if to_sign: response = signed_instance_factory(response, self.sec, to_sign) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def _logout(self, subject_id, entity_ids, reason, expire, sign=None, return_to="/"): # 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[:] response = False for entity_id in entity_ids: response = False for binding in [ BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT ]: destinations = self.config.single_logout_services( entity_id, binding) if not destinations: continue destination = destinations[0] logger.info("destination to provider: %s" % destination) request = self.construct_logout_request( subject_id, destination, entity_id, reason, expire) to_sign = [] #if sign and binding != BINDING_HTTP_REDIRECT: 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) request = signed_instance_factory(request, self.sec, to_sign) if binding == BINDING_SOAP: response = send_using_soap(request, destination, self.config.key_file, self.config.cert_file, ca_certs=self.config.ca_certs) if response: logger.info("Verifying response") response = self.logout_response(response) if response: not_done.remove(entity_id) logger.info("OK response from %s" % destination) 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, "return_to": return_to } if binding == BINDING_HTTP_POST: (head, body) = http_post_message(request, destination, rstate) code = "200 OK" else: (head, body) = http_redirect_message(request, destination, rstate) code = "302 Found" return session_id, code, head, body if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids, )) return 0, "", [], response
def send_idp_response(self, req, resp): """ :param req: The expected request :param resp: The response type to be used :return: A response """ # make sure I got the request I expected assert isinstance(self.saml_request.message, req._class) try: self.test_sequence(req.tests["post"]) except KeyError: pass # Pick information from the request that should be in the response args = self.instance.response_args(self.saml_request.message, [resp._binding]) _mods = list(resp.__mro__[:]) _mods.reverse() for m in _mods: try: args.update(self.json_config["args"][m.__name__]) except KeyError: pass args.update(resp._response_args) for param in ["identity", "userid"]: if param in self.json_config: args[param] = self.json_config[param] if resp == ErrorResponse: func = getattr(self.instance, "create_error_response") else: _op = camel2underscore.sub(r'_\1', req._class.c_tag).lower() func = getattr(self.instance, "create_%s_response" % _op) sign = [] for styp in ["sign_assertion", "sign_response"]: if styp in args: del args[styp] sign.append(styp) response = func(**args) response = resp(self).pre_processing(response) # and now for signing if sign: to_sign = [] # Order is important, first assertion and then response if both if "sign_assertion" in sign: to_sign = [(class_name(response.assertion), response.assertion.id)] response.assertion.signature = pre_signature_part( response.assertion.id, self.instance.sec.my_cert, 1) if "sign_response" in sign: to_sign = [(class_name(response), response.id)] response.signature = pre_signature_part( response.id, self.instance.sec.my_cert, 1) response = signed_instance_factory(response, self.instance.sec, to_sign) info = self.instance.apply_binding(resp._binding, response, args["destination"], self.relay_state, "SAMLResponse", resp._sign) if resp._binding == BINDING_HTTP_REDIRECT: url = None for param, value in info["headers"]: if param == "Location": url = value break self.last_response = self.instance.send(url) elif resp._binding == BINDING_HTTP_POST: resp = base64.b64encode("%s" % response) info["data"] = urllib.urlencode({"SAMLResponse": resp, "RelayState": self.relay_state}) info["method"] = "POST" info["headers"] = { 'Content-type': 'application/x-www-form-urlencoded'} self.last_response = self.instance.send(**info) self._log_response(self.last_response)
def authn_request( self, query_id, destination, service_url, spentityid, my_name="", vorg="", scoping=None, log=None, sign=None, binding=saml2.BINDING_HTTP_POST, nameid_format=saml.NAMEID_FORMAT_TRANSIENT, ): """ Creates an authentication request. :param query_id: The identifier for this request :param destination: Where the request should be sent. :param service_url: Where the reply should be sent. :param spentityid: The entity identifier for this service. :param my_name: The name of this service. :param vorg: The vitual organization the service belongs to. :param scoping: The scope of the request :param log: A service to which logs should be written :param sign: Whether the request should be signed or not. :param binding: The protocol to use for the Response !! :return: <samlp:AuthnRequest> instance """ request = samlp.AuthnRequest( id=query_id, version=VERSION, issue_instant=instant(), assertion_consumer_service_url=service_url, protocol_binding=binding, ) if destination: request.destination = destination if my_name: request.provider_name = my_name if scoping: request.scoping = scoping # Profile stuff, should be configurable if nameid_format == saml.NAMEID_FORMAT_TRANSIENT: name_id_policy = samlp.NameIDPolicy(allow_create="true", format=nameid_format) else: name_id_policy = samlp.NameIDPolicy(format=nameid_format) if vorg: try: name_id_policy.sp_name_qualifier = vorg name_id_policy.format = saml.NAMEID_FORMAT_PERSISTENT except KeyError: pass if sign is None: sign = self.authn_requests_signed_default if sign: request.signature = pre_signature_part(request.id, self.sec.my_cert, 1) to_sign = [(class_name(request), request.id)] else: to_sign = [] request.name_id_policy = name_id_policy request.issuer = self.issuer(spentityid) if log is None: log = self.logger if log: log.info("REQUEST: %s" % request) return signed_instance_factory(request, self.sec, to_sign)
def _logout(self, subject_id, entity_ids, reason, expire, sign=None, log=None, return_to="/"): # 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[:] response = False if log is None: log = self.logger for entity_id in entity_ids: response = False for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]: destinations = self.config.single_logout_services(entity_id, binding) if not destinations: continue destination = destinations[0] if log: log.info("destination to provider: %s" % destination) request = self.construct_logout_request(subject_id, destination, entity_id, reason, expire) to_sign = [] # if sign and binding != BINDING_HTTP_REDIRECT: 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)] if log: log.info("REQUEST: %s" % request) request = signed_instance_factory(request, self.sec, to_sign) if binding == BINDING_SOAP: response = send_using_soap( request, destination, self.config.key_file, self.config.cert_file, log=log, ca_certs=self.config.ca_certs, ) if response: if log: log.info("Verifying response") response = self.logout_response(response, log) if response: not_done.remove(entity_id) if log: log.info("OK response from %s" % destination) else: if log: log.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, "return_to": return_to, } if binding == BINDING_HTTP_POST: (head, body) = http_post_message(request, destination, rstate) code = "200 OK" else: (head, body) = http_redirect_message(request, destination, rstate) code = "302 Found" return session_id, code, head, body if not_done: # upstream should try later raise LogoutError("%s" % (entity_ids,)) return 0, "", [], response
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, encrypt_cert=None, **kwargs): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is true, then will the function try to encrypt the assertion in the the advice element of the main assertion. Only one assertion element is allowed in the advice element, if multiple assertions exists in the advice element the main assertion will be encrypted instead, since it's no point to encrypt If encrypted_advice_attributes is false the main assertion will be encrypted. Since the same key :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: node_xpath = None if sign: response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) sign_class = [(class_name(response), response.id)] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) if encrypted_advice_attributes and response.assertion.advice is not None \ and len(response.assertion.advice.assertion) == 1: tmp_assertion = response.assertion.advice.assertion[0] response.assertion.advice.encrypted_assertion = [] response.assertion.advice.encrypted_assertion.append(EncryptedAssertion()) if isinstance(tmp_assertion, list): response.assertion.advice.encrypted_assertion[0].add_extension_elements(tmp_assertion) else: response.assertion.advice.encrypted_assertion[0].add_extension_element(tmp_assertion) response.assertion.advice.assertion = [] if encrypt_assertion_self_contained: advice_tag = response.assertion.advice._to_element_tree().tag assertion_tag = tmp_assertion._to_element_tree().tag response = response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion( assertion_tag, advice_tag) node_xpath = ''.join(["/*[local-name()=\"%s\"]" % v for v in ["Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion"]]) elif encrypt_assertion_self_contained: assertion_tag = response.assertion._to_element_tree().tag response = pre_encrypt_assertion(response) response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion( assertion_tag) else: response = pre_encrypt_assertion(response) if to_sign: response = signed_instance_factory(response, self.sec, to_sign) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part(), node_xpath=node_xpath) # template(response.assertion.id)) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, encrypt_cert=None, **kwargs): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is true, then will the function try to encrypt the assertion in the the advice element of the main assertion. Only one assertion element is allowed in the advice element, if multiple assertions exists in the advice element the main assertion will be encrypted instead, since it's no point to encrypt If encrypted_advice_attributes is false the main assertion will be encrypted. Since the same key :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: node_xpath = None if sign: response.signature = pre_signature_part( response.id, self.sec.my_cert, 1) sign_class = [(class_name(response), response.id)] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) if encrypted_advice_attributes and response.assertion.advice is not None \ and len(response.assertion.advice.assertion) == 1: tmp_assertion = response.assertion.advice.assertion[0] response.assertion.advice.encrypted_assertion = [] response.assertion.advice.encrypted_assertion.append( EncryptedAssertion()) if isinstance(tmp_assertion, list): response.assertion.advice.encrypted_assertion[ 0].add_extension_elements(tmp_assertion) else: response.assertion.advice.encrypted_assertion[ 0].add_extension_element(tmp_assertion) response.assertion.advice.assertion = [] if encrypt_assertion_self_contained: advice_tag = response.assertion.advice._to_element_tree( ).tag assertion_tag = tmp_assertion._to_element_tree().tag response = response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion( assertion_tag, advice_tag) node_xpath = ''.join([ "/*[local-name()=\"%s\"]" % v for v in [ "Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion" ] ]) elif encrypt_assertion_self_contained: assertion_tag = response.assertion._to_element_tree().tag response = pre_encrypt_assertion(response) response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion( assertion_tag) else: response = pre_encrypt_assertion(response) if to_sign: response = signed_instance_factory(response, self.sec, to_sign) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part(), node_xpath=node_xpath) # template(response.assertion.id)) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def logout_response(self, request, bindings, status=None, sign=False, issuer=None): """ Create a LogoutResponse. What is returned depends on which binding is used. :param request: The request this is a response to :param bindings: Which bindings that can be used to send the response :param status: The return status of the response operation :param issuer: The issuer of the message :return: A 3-tuple consisting of HTTP return code, HTTP headers and possibly a message. """ sp_entity_id = request.issuer.text.strip() binding = None destinations = [] for binding in bindings: destinations = self.conf.single_logout_services(sp_entity_id, binding) if destinations: break if not destinations: logger.error("Not way to return a response !!!") return ("412 Precondition Failed", [("Content-type", "text/html")], ["No return way defined"]) # Pick the first destination = destinations[0] logger.info("Logout Destination: %s, binding: %s" % (destination, binding)) if not status: status = success_status_factory() mid = sid() rcode = "200 OK" # response and packaging differs depending on binding if binding == BINDING_SOAP: response = logoutresponse_factory( sign=sign, id = mid, in_response_to = request.id, status = status, ) if sign: to_sign = [(class_name(response), mid)] response = signed_instance_factory(response, self.sec, to_sign) (headers, message) = http_soap_message(response) else: _issuer = self.issuer(issuer) response = logoutresponse_factory( sign=sign, id = mid, in_response_to = request.id, status = status, issuer = _issuer, destination = destination, sp_entity_id = sp_entity_id, instant=instant(), ) if sign: to_sign = [(class_name(response), mid)] response = signed_instance_factory(response, self.sec, to_sign) logger.info("Response: %s" % (response,)) if binding == BINDING_HTTP_REDIRECT: (headers, message) = http_redirect_message(response, destination, typ="SAMLResponse") rcode = "302 Found" else: (headers, message) = http_post_message(response, destination, typ="SAMLResponse") return rcode, headers, message
def authn_request(self, query_id, destination, service_url, spentityid, my_name="", vorg="", scoping=None, log=None, sign=None, binding=BINDING_HTTP_POST, nameid_format=saml.NAMEID_FORMAT_TRANSIENT, **kwargs): """ Creates an authentication request. :param query_id: The identifier for this request :param destination: Where the request should be sent. :param service_url: Where the reply should be sent. :param spentityid: The entity identifier for this service. :param my_name: The name of this service. :param vorg: The vitual organization the service belongs to. :param scoping: The scope of the request :param log: A service to which logs should be written :param sign: Whether the request should be signed or not. :param binding: The protocol to use for the Response !! :return: <samlp:AuthnRequest> instance added: we want additional kw arguments, namely is_passive """ request = samlp.AuthnRequest( id=query_id, version=VERSION, issue_instant=instant(), assertion_consumer_service_url=service_url, protocol_binding=binding, **kwargs) if destination: request.destination = destination if my_name: request.provider_name = my_name if scoping: request.scoping = scoping # Profile stuff, should be configurable if nameid_format == saml.NAMEID_FORMAT_TRANSIENT: name_id_policy = samlp.NameIDPolicy(allow_create="true", format=nameid_format) else: name_id_policy = samlp.NameIDPolicy(format=nameid_format) if vorg: try: name_id_policy.sp_name_qualifier = vorg name_id_policy.format = saml.NAMEID_FORMAT_PERSISTENT except KeyError: pass if sign is None: sign = self.authn_requests_signed if sign: request.signature = pre_signature_part(request.id, self.sec.my_cert, 1) to_sign = [(class_name(request), request.id)] else: to_sign = [] request.name_id_policy = name_id_policy request.issuer = self._issuer(spentityid) logger.info("REQUEST: %s" % request) return signed_instance_factory(request, self.sec, to_sign)
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
class Server(object): """ A class that does things that IdPs or AAs do """ def __init__(self, config_file="", config=None, _cache="", stype="idp"): self.ident = None if config_file: self.load_config(config_file, stype) elif config: self.conf = config else: raise Exception("Missing configuration") self.conf.setup_logger() self.metadata = self.conf.metadata self.sec = security_context(self.conf) self._cache = _cache # if cache: # if isinstance(cache, basestring): # self.cache = Cache(cache) # else: # self.cache = cache # else: # self.cache = Cache() def load_config(self, config_file, stype="idp"): """ Load the server configuration :param config_file: The name of the configuration file :param stype: The type of Server ("idp"/"aa") """ self.conf = config_factory(stype, config_file) if stype == "aa": return try: # subject information is stored in a database # default database is a shelve database which is OK in some setups dbspec = self.conf.subject_data idb = None if isinstance(dbspec, basestring): idb = shelve.open(dbspec, writeback=True) else: # database spec is a a 2-tuple (type, address) print >> sys.stderr, "DBSPEC: %s" % dbspec (typ, addr) = dbspec if typ == "shelve": idb = shelve.open(addr, writeback=True) elif typ == "memcached": idb = memcache.Client(addr) elif typ == "dict": # in-memory dictionary idb = addr if idb is not None: self.ident = Identifier(idb, self.conf.virtual_organization) else: raise Exception("Couldn't open identity database: %s" % (dbspec,)) except AttributeError: self.ident = None def issuer(self, entityid=None): """ Return an Issuer precursor """ if entityid: return saml.Issuer(text=entityid, format=saml.NAMEID_FORMAT_ENTITY) else: return saml.Issuer(text=self.conf.entityid, format=saml.NAMEID_FORMAT_ENTITY) def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT): """Parse a Authentication Request :param enc_request: The request in its transport format :param binding: Which binding that was used to transport the message to this entity. :return: A dictionary with keys: consumer_url - as gotten from the SPs entity_id and the metadata id - the id of the request sp_entity_id - the entity id of the SP request - The verified request """ response = {} _log_info = logger.info _log_debug = logger.debug # The addresses I should receive messages like this on receiver_addresses = self.conf.endpoint("single_sign_on_service", binding) _log_info("receiver addresses: %s" % receiver_addresses) _log_info("Binding: %s" % binding) try: timeslack = self.conf.accepted_time_diff if not timeslack: timeslack = 0 except AttributeError: timeslack = 0 authn_request = AuthnRequest(self.sec, self.conf.attribute_converters, receiver_addresses, timeslack=timeslack) if binding == BINDING_SOAP or binding == BINDING_PAOS: # not base64 decoding and unzipping authn_request.debug=True _log_info("Don't decode") authn_request = authn_request.loads(enc_request, decode=False) else: authn_request = authn_request.loads(enc_request) _log_debug("Loaded authn_request") if authn_request: authn_request = authn_request.verify() _log_debug("Verified authn_request") if not authn_request: return None response["id"] = authn_request.message.id # put in in_reply_to sp_entity_id = authn_request.message.issuer.text # try to find return address in metadata try: # What's the binding ? ProtocolBinding _binding = authn_request.message.protocol_binding consumer_url = self.metadata.consumer_url(sp_entity_id, binding=_binding) except KeyError: _log_info("Failed to find consumer URL for %s" % sp_entity_id) _log_info("entities: %s" % self.metadata.entity.keys()) raise UnknownPrincipal(sp_entity_id) if not consumer_url: # what to do ? _log_info("Couldn't find a consumer URL binding=%s" % _binding) raise UnsupportedBinding(sp_entity_id) response["sp_entity_id"] = sp_entity_id if authn_request.message.assertion_consumer_service_url: return_destination = \ authn_request.message.assertion_consumer_service_url if consumer_url != return_destination: # serious error on someones behalf _log_info("%s != %s" % (consumer_url, return_destination)) raise OtherError("ConsumerURL and return destination mismatch") response["consumer_url"] = consumer_url response["request"] = authn_request.message return response def wants(self, sp_entity_id): """ Returns what attributes the SP requiers and which are optional if any such demands are registered in the Metadata. :param sp_entity_id: The entity id of the SP :return: 2-tuple, list of required and list of optional attributes """ return self.metadata.requests(sp_entity_id) def parse_attribute_query(self, xml_string, decode=True): """ Parse an attribute query :param xml_string: The Attribute Query as an XML string :param decode: Whether the xmlstring is base64encoded and zipped :return: 3-Tuple containing: subject - identifier of the subject attribute - which attributes that the requestor wants back query - the whole query """ receiver_addresses = self.conf.endpoint("attribute_service") attribute_query = AttributeQuery( self.sec, receiver_addresses) attribute_query = attribute_query.loads(xml_string, decode=decode) attribute_query = attribute_query.verify() logger.info("KEYS: %s" % attribute_query.message.keys()) # Subject is described in the a saml.Subject instance subject = attribute_query.subject_id() attribute = attribute_query.attribute() return subject, attribute, attribute_query.message # ------------------------------------------------------------------------ def _response(self, in_response_to, consumer_url=None, sp_entity_id=None, identity=None, name_id=None, status=None, sign=False, policy=Policy(), authn=None, authn_decl=None, issuer=None): """ Create a Response that adhers to the ??? profile. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param sp_entity_id: The entity identifier of the SP :param identity: A dictionary with attributes and values that are expected to be the bases for the assertion in the response. :param name_id: The identifier of the subject :param status: The status of the response :param sign: Whether the assertion should be signed or not :param policy: The attribute release policy for this instance :param authn: A 2-tuple denoting the authn class and the authn authority :param authn_decl: :param issuer: The issuer of the response :return: A Response instance """ to_sign = [] if not status: status = success_status_factory() _issuer = self.issuer(issuer) response = response_factory( issuer=_issuer, in_response_to = in_response_to, status = status, ) if consumer_url: response.destination = consumer_url if identity: ast = Assertion(identity) try: ast.apply_policy(sp_entity_id, policy, self.metadata) except MissingValue, exc: return self.error_response(in_response_to, consumer_url, sp_entity_id, exc, name_id) if authn: # expected to be a 2-tuple class+authority (authn_class, authn_authn) = authn assertion = ast.construct(sp_entity_id, in_response_to, consumer_url, name_id, self.conf.attribute_converters, policy, issuer=_issuer, authn_class=authn_class, authn_auth=authn_authn) elif authn_decl: assertion = ast.construct(sp_entity_id, in_response_to, consumer_url, name_id, self.conf.attribute_converters, policy, issuer=_issuer, authn_decl=authn_decl) else: assertion = ast.construct(sp_entity_id, in_response_to, consumer_url, name_id, self.conf.attribute_converters, policy, issuer=_issuer) if sign: assertion.signature = pre_signature_part(assertion.id, self.sec.my_cert, 1) # Just the assertion or the response and the assertion ? to_sign = [(class_name(assertion), assertion.id)] # Store which assertion that has been sent to which SP about which # subject. # self.cache.set(assertion.subject.name_id.text, # sp_entity_id, {"ava": identity, "authn": authn}, # assertion.conditions.not_on_or_after) response.assertion = assertion return signed_instance_factory(response, self.sec, to_sign)