Example #1
0
    def active(self, subject_id, entity_id):
        """ Returns the status of assertions from a specific entity_id.

        :param subject_id: The ID of the subject
        :param entity_id: The entity ID of the entity_id of the assertion
        :return: True or False depending on if the assertion is still
            valid or not.
        """

        item = self._cache.find_one({"subject_id": subject_id,
                                     "entity_id": entity_id})
        try:
            return time_util.not_on_or_after(item["timestamp"])
        except ToOld:
            return False
Example #2
0
    def _get_info(self, item, check_not_on_or_after=True):
        """ Get session information about a subject gotten from a
        specified IdP/AA.

        :param item: Information stored
        :return: The session information as a dictionary
        """
        timestamp = item["timestamp"]

        if check_not_on_or_after and not time_util.not_on_or_after(timestamp):
            raise ToOld()

        try:
            return item["info"]
        except KeyError:
            return None
Example #3
0
    def get_info(self, item, check_not_on_or_after=True):
        """ Get session information about a subject gotten from a
        specified IdP/AA.

        :param item: Information stored
        :return: The session information as a dictionary
        """
        try:
            (timestamp, info) = item
        except ValueError:
            raise ToOld()
            
        if check_not_on_or_after and not time_util.not_on_or_after(timestamp):
            raise ToOld()

        return info or None
Example #4
0
    def active(self, subject_id, entity_id):
        """ Returns the status of assertions from a specific entity_id.
        
        :param subject_id: The ID of the subject
        :param entity_id: The entity ID of the entity_id of the assertion
        :return: True or False depending on if the assertion is still
            valid or not.
        """
        try:
            (timestamp, info) = self._db[subject_id][entity_id]
        except KeyError:
            return False

        if not info:
            return False
        else:
            return time_util.not_on_or_after(timestamp)
Example #5
0
    def active(self, name_id, entity_id):
        """ Returns the status of assertions from a specific entity_id.

        :param name_id: The ID of the subject
        :param entity_id: The entity ID of the entity_id of the assertion
        :return: True or False depending on if the assertion is still
            valid or not.
        """
        try:
            cni = code(name_id)
            (timestamp, info) = self._db[cni][entity_id]
        except KeyError:
            return False

        if not info:
            return False
        else:
            return time_util.not_on_or_after(timestamp)
Example #6
0
    def active(self, subject_id, entity_id):
        """ Returns the status of assertions from a specific entity_id.

        :param subject_id: The ID of the subject
        :param entity_id: The entity ID of the entity_id of the assertion
        :return: True or False depending on if the assertion is still
            valid or not.
        """
        try:
            (timestamp, info) = self._cache.get(_key(subject_id, entity_id))
        except ValueError:
            return False
        except TypeError:
            return False

        # if not info:
        #     return False

        try:
            return time_util.not_on_or_after(timestamp)
        except TooOld:
            return False
Example #7
0
 def active(self, subject_id, entity_id):
     """ Returns the status of assertions from a specific entity_id.
     
     :param subject_id: The ID of the subject
     :param entity_id: The entity ID of the entity_id of the assertion
     :return: True or False depending on if the assertion is still
         valid or not.
     """
     try:
         (timestamp, info) = self._cache.get(_key(subject_id, entity_id))
     except ValueError:
         return False
     except TypeError:
         return False
         
     # if not info:
     #     return False
         
     try:
         return time_util.not_on_or_after(timestamp)
     except ToOld:
         return False
Example #8
0
    def do_logout(
        self,
        name_id,
        entity_ids,
        reason,
        expire,
        sign=None,
        expected_binding=None,
        sign_alg=None,
        digest_alg=None,
        **kwargs,
    ):
        """

        :param name_id: Identifier of the Subject (a NameID instance)
        :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
        :param expected_binding: Specify the expected binding then not try it
            all
        :param kwargs: Extra key word arguments.
        :return:
        """
        # check time
        if not not_on_or_after(expire):  # I've run out of time
            # Do the local logout anyway
            self.local_logout(name_id)
            return 0, "504 Gateway Timeout", [], []

        not_done = entity_ids[:]
        responses = {}

        bindings_slo_preferred = self.config.preferred_binding[
            "single_logout_service"]

        for entity_id in entity_ids:
            logger.debug("Logout from '%s'", entity_id)

            bindings_slo_supported = self.metadata.single_logout_service(
                entity_id=entity_id, typ="idpsso")
            bindings_slo_preferred_and_supported = (
                binding for binding in bindings_slo_preferred
                if binding in bindings_slo_supported)
            bindings_slo_choices = filter(lambda x: x, (
                expected_binding,
                *bindings_slo_preferred_and_supported,
                *bindings_slo_supported,
            ))
            binding = next(bindings_slo_choices, None)
            if not binding:
                logger.info({
                    "message": "Entity does not support SLO",
                    "entity": entity_id,
                })
                continue

            service_info = bindings_slo_supported[binding]
            service_location = next(locations(service_info), None)
            if not service_location:
                logger.info({
                    "message": "Entity SLO service does not have a location",
                    "entity": entity_id,
                    "service_location": service_location,
                })
                continue

            session_info = self.users.get_info_from(name_id, entity_id, False)
            session_index = session_info.get('session_index')
            session_indexes = [session_index] if session_index else None

            sign = sign if sign is not None else self.logout_requests_signed
            sign_post = sign and (binding == BINDING_HTTP_POST
                                  or binding == BINDING_SOAP)
            sign_redirect = sign and binding == BINDING_HTTP_REDIRECT

            log_report = {
                "message": "Invoking SLO on entity",
                "entity": entity_id,
                "binding": binding,
                "location": service_location,
                "session_indexes": session_indexes,
                "sign": sign,
            }
            logger.info(log_report)

            req_id, request = self.create_logout_request(
                service_location,
                entity_id,
                name_id=name_id,
                reason=reason,
                expire=expire,
                session_indexes=session_indexes,
                sign=sign_post,
                sign_alg=sign_alg,
                digest_alg=digest_alg,
            )
            relay_state = self._relay_state(req_id)
            http_info = self.apply_binding(
                binding,
                str(request),
                service_location,
                relay_state,
                sign=sign_redirect,
                sigalg=sign_alg,
            )

            if binding == BINDING_SOAP:
                response = self.send(**http_info)
                if response and response.status_code == 200:
                    not_done.remove(entity_id)
                    response_text = response.text
                    log_report_response = {
                        **log_report,
                        "message": "Response from SLO service",
                        "response_text": response_text,
                    }
                    logger.debug(log_report_response)
                    res = self.parse_logout_request_response(
                        response_text, binding)
                    responses[entity_id] = res
                else:
                    log_report_response = {
                        **log_report,
                        "message": "Bad status_code response from SLO service",
                        "status_code": (response and response.status_code),
                    }
                    logger.info(log_report_response)
            else:
                self.state[req_id] = {
                    "entity_id": entity_id,
                    "operation": "SLO",
                    "entity_ids": entity_ids,
                    "name_id": code(name_id),
                    "reason": reason,
                    "not_on_or_after": expire,
                    "sign": sign,
                }
                responses[entity_id] = (binding, http_info)
                not_done.remove(entity_id)

        if not_done:
            # upstream should try later
            raise LogoutError("%s" % (entity_ids, ))

        return responses
def test_not_on_or_after():
    current_year = datetime.datetime.today().year
    assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year + 1)) == True
    assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year - 1)) == False
Example #10
0
    def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
                  expected_binding=None, **kwargs):
        """

        :param name_id: Identifier of the Subject (a NameID instance)
        :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
        :param expected_binding: Specify the expected binding then not try it
            all
        :param kwargs: Extra key word arguments.
        :return:
        """
        # check time
        if not not_on_or_after(expire):  # I've run out of time
            # Do the local logout anyway
            self.local_logout(name_id)
            return 0, "504 Gateway Timeout", [], []

        not_done = entity_ids[:]
        responses = {}

        for entity_id in entity_ids:
            logger.debug("Logout from '%s'", entity_id)
            # for all where I can use the SOAP binding, do those first
            for binding in [BINDING_SOAP, BINDING_HTTP_POST,
                            BINDING_HTTP_REDIRECT]:
                if expected_binding and binding != expected_binding:
                    continue
                try:
                    srvs = self.metadata.single_logout_service(entity_id,
                                                               binding,
                                                               "idpsso")
                except:
                    srvs = None

                if not srvs:
                    logger.debug("No SLO '%s' service", binding)
                    continue

                destination = destinations(srvs)[0]
                logger.info("destination to provider: %s", destination)
                try:
                    session_info = self.users.get_info_from(name_id,
                                                            entity_id,
                                                            False)
                    session_indexes = [session_info['session_index']]
                except KeyError:
                    session_indexes = None
                req_id, request = self.create_logout_request(
                    destination, entity_id, name_id=name_id, reason=reason,
                    expire=expire, session_indexes=session_indexes)

                # to_sign = []
                if binding.startswith("http://"):
                    sign = True

                if sign is None:
                    sign = self.logout_requests_signed

                sigalg = None
                key = None
                if sign:
                    if binding == BINDING_HTTP_REDIRECT:
                        sigalg = kwargs.get("sigalg", ds.sig_default)
                        key = kwargs.get("key", self.signkey)
                        srequest = str(request)
                    else:
                        srequest = self.sign(request)
                else:
                    srequest = str(request)

                relay_state = self._relay_state(req_id)

                http_info = self.apply_binding(binding, srequest, destination,
                                               relay_state, sigalg=sigalg,
                                               key=key)

                if binding == BINDING_SOAP:
                    response = self.send(**http_info)

                    if response and response.status_code == 200:
                        not_done.remove(entity_id)
                        response = response.text
                        logger.info("Response: %s", response)
                        res = self.parse_logout_request_response(response,
                                                                 binding)
                        responses[entity_id] = res
                    else:
                        logger.info("NOT OK response from %s", destination)

                else:
                    self.state[req_id] = {"entity_id": entity_id,
                                          "operation": "SLO",
                                          "entity_ids": entity_ids,
                                          "name_id": code(name_id),
                                          "reason": reason,
                                          "not_on_of_after": expire,
                                          "sign": sign}

                    responses[entity_id] = (binding, http_info)
                    not_done.remove(entity_id)

                # only try one binding
                break

        if not_done:
            # upstream should try later
            raise LogoutError("%s" % (entity_ids,))

        return responses
Example #11
0
    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
Example #12
0
    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
Example #13
0
    def do_logout(self,
                  name_id,
                  entity_ids,
                  reason,
                  expire,
                  sign=None,
                  expected_binding=None):
        """

        :param name_id: Identifier of the Subject (a NameID instance)
        :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
        :param expected_binding: Specify the expected binding then not try it
            all
        :return:
        """
        # check time
        if not not_on_or_after(expire):  # I've run out of time
            # Do the local logout anyway
            self.local_logout(name_id)
            return 0, "504 Gateway Timeout", [], []

        not_done = entity_ids[:]
        responses = {}

        for entity_id in entity_ids:
            logger.debug("Logout from '%s'" % entity_id)
            # for all where I can use the SOAP binding, do those first
            for binding in [
                    BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
            ]:
                if expected_binding and binding != expected_binding:
                    continue
                try:
                    srvs = self.metadata.single_logout_service(
                        entity_id, binding, "idpsso")
                except:
                    srvs = None

                if not srvs:
                    logger.debug("No SLO '%s' service" % binding)
                    continue

                destination = destinations(srvs)[0]
                logger.info("destination to provider: %s" % destination)
                req_id, request = self.create_logout_request(destination,
                                                             entity_id,
                                                             name_id=name_id,
                                                             reason=reason,
                                                             expire=expire)

                #to_sign = []
                if binding.startswith("http://"):
                    sign = True

                if sign is None:
                    sign = self.logout_requests_signed

                if sign:
                    srequest = self.sign(request)
                else:
                    srequest = "%s" % request

                relay_state = self._relay_state(req_id)

                http_info = self.apply_binding(binding, srequest, destination,
                                               relay_state)

                if binding == BINDING_SOAP:
                    response = self.send(**http_info)

                    if response and response.status_code == 200:
                        not_done.remove(entity_id)
                        response = response.text
                        logger.info("Response: %s" % response)
                        res = self.parse_logout_request_response(response)
                        responses[entity_id] = res
                    else:
                        logger.info("NOT OK response from %s" % destination)

                else:
                    self.state[req_id] = {
                        "entity_id": entity_id,
                        "operation": "SLO",
                        "entity_ids": entity_ids,
                        "name_id": code(name_id),
                        "reason": reason,
                        "not_on_of_after": expire,
                        "sign": sign
                    }

                    responses[entity_id] = (binding, http_info)
                    not_done.remove(entity_id)

                # only try one binding
                break

        if not_done:
            # upstream should try later
            raise LogoutError("%s" % (entity_ids, ))

        return responses
Example #14
0
    def do_logout(self, name_id, entity_ids, reason, expire, sign=None):
        """

        :param name_id: Identifier of the Subject a NameID instance
        :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(name_id)
            return 0, "504 Gateway Timeout", [], []
            
        not_done = entity_ids[:]
        responses = {}

        for entity_id in entity_ids:
            logger.debug("Logout from '%s'" % entity_id)
            # for all where I can use the SOAP binding, do those first
            for binding in [BINDING_SOAP, BINDING_HTTP_POST,
                            BINDING_HTTP_REDIRECT]:
                try:
                    srvs = self.metadata.single_logout_service(entity_id,
                                                               binding,
                                                               "idpsso")
                except:
                    srvs = None

                if not srvs:
                    logger.debug("No SLO '%s' service" % binding)
                    continue

                destination = destinations(srvs)[0]
                logger.info("destination to provider: %s" % destination)
                request = self.create_logout_request(destination, entity_id,
                                                     name_id=name_id,
                                                     reason=reason,
                                                     expire=expire)
                
                #to_sign = []
                if binding.startswith("http://"):
                    sign = True

                if sign is None:
                    sign = self.logout_requests_signed

                if sign:
                    srequest = self.sign(request)
                else:
                    srequest = "%s" % request

                relay_state = self._relay_state(request.id)

                http_info = self.apply_binding(binding, srequest, destination,
                                               relay_state)

                if binding == BINDING_SOAP:
                    response = self.send(**http_info)

                    if response and response.status_code == 200:
                        not_done.remove(entity_id)
                        response = response.text
                        logger.info("Response: %s" % response)
                        res = self.parse_logout_request_response(response)
                        responses[entity_id] = res
                    else:
                        logger.info("NOT OK response from %s" % destination)

                else:
                    self.state[request.id] = {"entity_id": entity_id,
                                              "operation": "SLO",
                                              "entity_ids": entity_ids,
                                              "name_id": name_id,
                                              "reason": reason,
                                              "not_on_of_after": expire,
                                              "sign": sign}

                    responses[entity_id] = (binding, http_info)
                    not_done.remove(entity_id)

                # only try one binding
                break

        if not_done:
            # upstream should try later
            raise LogoutError("%s" % (entity_ids,))
        
        return responses
Example #15
0
def test_not_on_or_after():
    current_year = datetime.datetime.today().year
    assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year + 1)) == True
    assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year - 1)) == False
Example #16
0
    def do_logout(
        self,
        name_id,
        entity_ids,
        reason,
        expire,
        sign=None,
        expected_binding=None,
        sign_alg=None,
        digest_alg=None,
        **kwargs,
    ):
        """

        :param name_id: Identifier of the Subject (a NameID instance)
        :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
        :param expected_binding: Specify the expected binding then not try it
            all
        :param kwargs: Extra key word arguments.
        :return:
        """
        # check time
        if not not_on_or_after(expire):  # I've run out of time
            # Do the local logout anyway
            self.local_logout(name_id)
            return 0, "504 Gateway Timeout", [], []

        not_done = entity_ids[:]
        responses = {}

        for entity_id in entity_ids:
            logger.debug("Logout from '%s'", entity_id)
            # for all where I can use the SOAP binding, do those first
            for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]:
                if expected_binding and binding != expected_binding:
                    continue

                try:
                    srvs = self.metadata.single_logout_service(
                        entity_id, binding, "idpsso"
                    )
                except:
                    srvs = None

                if not srvs:
                    logger.debug("No SLO '%s' service", binding)
                    continue

                destination = next(locations(srvs), None)
                logger.info("destination to provider: %s", destination)

                try:
                    session_info = self.users.get_info_from(
                        name_id, entity_id, False
                    )
                    session_indexes = [session_info['session_index']]
                except KeyError:
                    session_indexes = None

                sign_post = False if binding == BINDING_HTTP_REDIRECT else sign
                sign_redirect = False if binding == BINDING_HTTP_POST and sign else sign

                req_id, request = self.create_logout_request(
                    destination,
                    entity_id,
                    name_id=name_id,
                    reason=reason,
                    expire=expire,
                    session_indexes=session_indexes,
                    sign=sign_post,
                    sign_alg=sign_alg,
                    digest_alg=digest_alg,
                )

                relay_state = self._relay_state(req_id)
                http_info = self.apply_binding(
                    binding,
                    str(request),
                    destination,
                    relay_state,
                    sign=sign_redirect,
                    sigalg=sign_alg,
                )

                if binding == BINDING_SOAP:
                    response = self.send(**http_info)
                    if response and response.status_code == 200:
                        not_done.remove(entity_id)
                        response = response.text
                        logger.info("Response: %s", response)
                        res = self.parse_logout_request_response(response, binding)
                        responses[entity_id] = res
                    else:
                        logger.info("NOT OK response from %s", destination)
                else:
                    self.state[req_id] = {
                        "entity_id": entity_id,
                        "operation": "SLO",
                        "entity_ids": entity_ids,
                        "name_id": code(name_id),
                        "reason": reason,
                        "not_on_or_after": expire,
                        "sign": sign,
                    }
                    responses[entity_id] = (binding, http_info)
                    not_done.remove(entity_id)

                # only try one binding
                break

        if not_done:
            # upstream should try later
            raise LogoutError("%s" % (entity_ids,))

        return responses
Example #17
0
    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