Exemple #1
0
    def __init__(self,
                 client,
                 config,
                 interaction,
                 check_factory=None,
                 msg_factory=None,
                 features=None,
                 verbose=False,
                 expect_exception=None):
        self.client = client
        self.client_config = config
        self.test_output = []
        self.features = features
        self.verbose = verbose
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception

        self.cjar = {
            "browser": cookielib.CookieJar(),
            "rp": cookielib.CookieJar(),
            "service": cookielib.CookieJar()
        }

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.client, interaction)
        self.exception = None
Exemple #2
0
    def __init__(self, instance, config, interaction, json_config,
                 check_factory, entity_id, msg_factory=None,
                 features=None, constraints=None,  # verbose=False,
                 expect_exception=None, commandlineargs=None):
        self.instance = instance
        self._config = config
        self.test_output = []
        self.features = features
        #self.verbose = verbose  # removed (not used)
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception
        self.commandlineargs = commandlineargs

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.instance, interaction)
        self.exception = None

        self.entity_id = entity_id
        self.cjar = {"rp": cookielib.CookieJar()}
        self.args = {}
        self.qargs = {}
        self.response_args = {}
        self.saml_response = []
        self.destination = ""
        self.request = None
        self.position = ""
        self.response = None
        self.oper = None
        self.msg_constraints = constraints
        self.json_config = json_config
        self.start_page = json_config["start_page"]
Exemple #3
0
    def __init__(self, client, config, interaction,
                 check_factory=None, msg_factory=None,
                 features=None, verbose=False, expect_exception=None):
        self.client = client
        self.client_config = config
        self.test_output = []
        self.features = features
        self.verbose = verbose
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.client, interaction)
        self.exception = None
Exemple #4
0
    def __init__(self, instance, config, interaction, json_config,
                 check_factory, entity_id, msg_factory=None,
                 features=None, constraints=None,  # verbose=False,
                 expect_exception=None, commandlineargs=None):
        self.instance = instance
        self._config = config
        self.test_output = []
        self.features = features
        #self.verbose = verbose  # removed (not used)
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception
        self.commandlineargs = commandlineargs

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.instance, interaction)
        self.exception = None

        self.entity_id = entity_id
        self.cjar = {"rp": cookielib.CookieJar()}
        self.args = {}
        self.qargs = {}
        self.response_args = {}
        self.saml_response = []
        self.destination = ""
        self.request = None
        self.position = ""
        self.response = None
        self.oper = None
        self.msg_constraints = constraints
        self.json_config = json_config
        self.start_page = json_config["start_page"]
Exemple #5
0
class Conversation(object):
    """
    :ivar response: The received HTTP messages
    :ivar protocol_response: List of the received protocol messages
    """
    def __init__(self,
                 client,
                 config,
                 interaction,
                 check_factory=None,
                 msg_factory=None,
                 features=None,
                 verbose=False,
                 expect_exception=None):
        self.client = client
        self.client_config = config
        self.test_output = []
        self.features = features
        self.verbose = verbose
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception

        self.cjar = {
            "browser": cookielib.CookieJar(),
            "rp": cookielib.CookieJar(),
            "service": cookielib.CookieJar()
        }

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.client, interaction)
        self.exception = None

    def check_severity(self, stat):
        if stat["status"] >= 4:
            logger.error("WHERE: %s" % stat["id"])
            logger.error("STATUS:%s" % STATUSCODE[stat["status"]])
            try:
                logger.error("HTTP STATUS: %s" % stat["http_status"])
            except KeyError:
                pass
            try:
                logger.error("INFO: %s" % stat["message"])
            except KeyError:
                pass

            raise CheckError

    def do_check(self, test, **kwargs):
        if isinstance(test, basestring):
            chk = self.check_factory(test)(**kwargs)
        else:
            chk = test(**kwargs)
        stat = chk(self, self.test_output)
        self.check_severity(stat)

    def err_check(self, test, err=None, bryt=True):
        if err:
            self.exception = err
        chk = self.check_factory(test)()
        chk(self, self.test_output)
        if bryt:
            e = FatalError("%s" % err)
            e.trace = "".join(traceback.format_exception(*sys.exc_info()))
            raise e

    def test_sequence(self, sequence):
        for test in sequence:
            if isinstance(test, tuple):
                test, kwargs = test
            else:
                kwargs = {}
            self.do_check(test, **kwargs)
            if test == ExpectedError:
                return False
        return True

    def my_endpoints(self):
        pass

    def intermit(self):
        _response = self.last_response
        _last_action = None
        _same_actions = 0
        if _response.status_code >= 400:
            done = True
        else:
            done = False

        url = _response.url
        content = _response.text
        while not done:
            rdseq = []
            while _response.status_code in [302, 301, 303]:
                url = _response.headers["location"]
                if url in rdseq:
                    raise FatalError("Loop detected in redirects")
                else:
                    rdseq.append(url)
                    if len(rdseq) > 8:
                        raise FatalError("Too long sequence of redirects: %s" %
                                         rdseq)

                logger.info("HTTP %d Location: %s" %
                            (_response.status_code, url))
                # If back to me
                for_me = False
                for redirect_uri in self.my_endpoints():
                    if url.startswith(redirect_uri):
                        # Back at the RP
                        self.client.cookiejar = self.cjar["rp"]
                        for_me = True
                        try:
                            base, query = url.split("?")
                        except ValueError:
                            pass
                        else:
                            _response = parse_qs(query)
                            self.last_response = _response
                            self.last_content = _response
                            return _response

                if for_me:
                    done = True
                    break
                else:
                    try:
                        logger.info("GET %s" % url)
                        _response = self.client.send(url, "GET")
                    except Exception, err:
                        raise FatalError("%s" % err)

                    content = _response.text
                    logger.info("<-- CONTENT: %s" % content)
                    self.position = url
                    self.last_content = content
                    self.response = _response

                    if _response.status_code >= 400:
                        done = True
                        break

            if done or url is None:
                break

            _base = url.split("?")[0]

            try:
                _spec = self.interaction.pick_interaction(_base, content)
            except InteractionNeeded:
                self.position = url
                cnt = content.replace("\n", '').replace("\t",
                                                        '').replace("\r", '')
                logger.error("URL: %s" % url)
                logger.error("Page Content: %s" % cnt)
                raise
            except KeyError:
                self.position = url
                cnt = content.replace("\n", '').replace("\t",
                                                        '').replace("\r", '')
                logger.error("URL: %s" % url)
                logger.error("Page Content: %s" % cnt)
                self.err_check("interaction-needed")

            if _spec == _last_action:
                _same_actions += 1
                if _same_actions >= 3:
                    self.test_output.append({
                        "status": ERROR,
                        "message": "Interaction loop detection",
                        #"id": "exception",
                        #"name": "interaction needed",
                        "url": self.position
                    })
                    raise OperationError()
            else:
                _last_action = _spec

            if len(_spec) > 2:
                logger.info(">> %s <<" % _spec["page-type"])
                if _spec["page-type"] == "login":
                    self.login_page = content

            _op = Action(_spec["control"])

            try:
                _response = _op(self.client, self, url, _response, content,
                                self.features)
                if isinstance(_response, dict):
                    self.last_response = _response
                    self.last_content = _response
                    return _response
                content = _response.text
                self.position = url
                self.last_content = content
                self.response = _response

                if _response.status_code >= 400:
                    txt = "Got status code '%s', error: %s" % (
                        _response.status_code, content)
                    logger.error(txt)
                    self.test_output.append({
                        "status": ERROR,
                        "message": txt,
                        #"id": "exception",
                        #"name": "interaction needed",
                        "url": self.position
                    })
                    raise OperationError()
            except (FatalError, InteractionNeeded, OperationError):
                raise
            except Exception, err:
                self.err_check("exception", err, False)
Exemple #6
0
class Conversation(object):
    """
    :ivar response: The received HTTP messages
    :ivar protocol_response: List of the received protocol messages
    """

    def __init__(self, client, config, interaction,
                 check_factory=None, msg_factory=None,
                 features=None, verbose=False, expect_exception=None):
        self.client = client
        self.client_config = config
        self.test_output = []
        self.features = features
        self.verbose = verbose
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.client, interaction)
        self.exception = None

    def check_severity(self, stat):
        if stat["status"] >= 4:
            logger.error("WHERE: %s" % stat["id"])
            logger.error("STATUS:%s" % STATUSCODE[stat["status"]])
            try:
                logger.error("HTTP STATUS: %s" % stat["http_status"])
            except KeyError:
                pass
            try:
                logger.error("INFO: %s" % stat["message"])
            except KeyError:
                pass

            raise CheckError

    def do_check(self, test, **kwargs):
        if isinstance(test, basestring):
            chk = self.check_factory(test)(**kwargs)
        else:
            chk = test(**kwargs)
        stat = chk(self, self.test_output)
        self.check_severity(stat)

    def err_check(self, test, err=None, bryt=True):
        if err:
            self.exception = err
        chk = self.check_factory(test)()
        chk(self, self.test_output)
        if bryt:
            e = FatalError("%s" % err)
            e.trace = "".join(traceback.format_exception(*sys.exc_info()))
            raise e

    def test_sequence(self, sequence):
        for test in sequence:
            if isinstance(test, tuple):
                test, kwargs = test
            else:
                kwargs = {}
            self.do_check(test, **kwargs)
            if test == ExpectedError:
                return False
        return True

    def my_endpoints(self):
        pass

    def intermit(self):
        _response = self.last_response
        _last_action = None
        _same_actions = 0
        if _response.status_code >= 400:
            done = True
        else:
            done = False

        url = _response.url
        content = _response.text
        while not done:
            rdseq = []
            while _response.status_code in [302, 301, 303]:
                url = _response.headers["location"]
                if url in rdseq:
                    raise FatalError("Loop detected in redirects")
                else:
                    rdseq.append(url)
                    if len(rdseq) > 8:
                        raise FatalError(
                            "Too long sequence of redirects: %s" % rdseq)

                logger.info("HTTP %d Location: %s" % (_response.status_code,
                                                      url))
                # If back to me
                for_me = False
                for redirect_uri in self.my_endpoints():
                    if url.startswith(redirect_uri):
                        # Back at the RP
                        self.client.cookiejar = self.cjar["rp"]
                        for_me = True
                        try:
                            base, query = url.split("?")
                        except ValueError:
                            pass
                        else:
                            _response = parse_qs(query)
                            self.last_response = _response
                            self.last_content = _response
                            return _response

                if for_me:
                    done = True
                    break
                else:
                    try:
                        logger.info("GET %s" % url)
                        _response = self.client.send(url, "GET")
                    except Exception, err:
                        raise FatalError("%s" % err)

                    content = _response.text
                    logger.info("<-- CONTENT: %s" % content)
                    self.position = url
                    self.last_content = content
                    self.response = _response

                    if _response.status_code >= 400:
                        done = True
                        break

            if done or url is None:
                break

            _base = url.split("?")[0]

            try:
                _spec = self.interaction.pick_interaction(_base, content)
            except InteractionNeeded:
                self.position = url
                cnt = content.replace("\n", '').replace("\t", '').replace("\r",
                                                                          '')
                logger.error("URL: %s" % url)
                logger.error("Page Content: %s" % cnt)
                raise
            except KeyError:
                self.position = url
                cnt = content.replace("\n", '').replace("\t", '').replace("\r",
                                                                          '')
                logger.error("URL: %s" % url)
                logger.error("Page Content: %s" % cnt)
                self.err_check("interaction-needed")

            if _spec == _last_action:
                _same_actions += 1
                if _same_actions >= 3:
                    self.test_output.append(
                        {"status": ERROR,
                         "message": "Interaction loop detection",
                         #"id": "exception",
                         #"name": "interaction needed",
                         "url": self.position})
                    raise OperationError()
            else:
                _last_action = _spec

            if len(_spec) > 2:
                logger.info(">> %s <<" % _spec["page-type"])
                if _spec["page-type"] == "login":
                    self.login_page = content

            _op = Action(_spec["control"])

            try:
                _response = _op(self.client, self, url, _response, content,
                                self.features)
                if isinstance(_response, dict):
                    self.last_response = _response
                    self.last_content = _response
                    return _response
                content = _response.text
                self.position = url
                self.last_content = content
                self.response = _response

                if _response.status_code >= 400:
                    txt = "Got status code '%s', error: %s" % (
                        _response.status_code, content)
                    logger.error(txt)
                    self.test_output.append(
                        {"status": ERROR,
                         "message": txt,
                         #"id": "exception",
                         #"name": "interaction needed",
                         "url": self.position})
                    raise OperationError()
            except (FatalError, InteractionNeeded, OperationError):
                raise
            except Exception, err:
                self.err_check("exception", err, False)
Exemple #7
0
class Conversation():
    def __init__(self, instance, config, interaction, json_config,
                 check_factory, entity_id, msg_factory=None,
                 features=None, constraints=None,  # verbose=False,
                 expect_exception=None, commandlineargs=None):
        self.instance = instance
        self._config = config
        self.test_output = []
        self.features = features
        #self.verbose = verbose  # removed (not used)
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception
        self.commandlineargs = commandlineargs

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.instance, interaction)
        self.exception = None

        self.entity_id = entity_id
        self.cjar = {"rp": cookielib.CookieJar()}
        self.args = {}
        self.qargs = {}
        self.response_args = {}
        self.saml_response = []
        self.destination = ""
        self.request = None
        self.position = ""
        self.response = None
        self.oper = None
        self.msg_constraints = constraints
        self.json_config = json_config
        self.start_page = json_config["start_page"]

    def check_severity(self, stat):
        if stat["status"] >= 3:
            logger.error("WHERE: %s" % stat["id"])
            logger.error("STATUS:%s" % STATUSCODE[stat["status"]])
            try:
                logger.error("HTTP STATUS: %s" % stat["http_status"])
            except KeyError:
                pass
            try:
                logger.error("INFO: %s" % stat["message"])
            except KeyError:
                pass

            raise CheckError

    def do_check(self, test, **kwargs):
        if isinstance(test, basestring):
            chk = self.check_factory(test)(**kwargs)
        else:
            chk = test(**kwargs)
        if not chk.call_on_redirect() and \
           300 < self.last_response.status_code <= 303:
            pass
        else:
            stat = chk(self, self.test_output)
            self.check_severity(stat)

    def err_check(self, test, err=None, bryt=True):
        if err:
            self.exception = err
        chk = self.check_factory(test)()
        chk(self, self.test_output)
        if bryt:
            e = FatalError("%s" % err)
            e.trace = "".join(traceback.format_exception(*sys.exc_info()))
            raise e

    def test_sequence(self, sequence):
        if sequence is None:
            return True

        for test in sequence:
            if isinstance(test, tuple):
                test, kwargs = test
            else:
                kwargs = {}
            self.do_check(test, **kwargs)
            if test == ExpectedError:
                return False  # TODO: return value is unused
        return True

    def my_endpoints(self):
        for serv in ["aa", "aq", "idp"]:
            endpoints = self._config.getattr("endpoints", serv)
            if endpoints:
                for typ, spec in endpoints.items():
                    for url, binding in spec:
                        yield url

    def which_endpoint(self, url):
        for serv in ["aa", "aq", "idp"]:
            endpoints = self._config.getattr("endpoints", serv)
            if endpoints:
                for typ, spec in endpoints.items():
                    for endp, binding in spec:
                        if url.startswith(endp):
                            return typ, binding
        return None

    def _log_response(self, response):
        """Depending on -k argument write content to either logger or extra file

        Create the <operation> directory; delete all possibly existing files
        Write response content into response_x.<ext> (with x incrementing from 0)
        """
        logger.info("<-- Status: %s" % response.status_code)
        if response.status_code in [302, 301, 303]:
            logger.info("<-- location: %s" %
                        response.headers._store['location'][1])
        else:
            if self.commandlineargs.content_log:
                self._content_log_fileno = getattr(self, '_content_log_fileno', 0) + 1
                if not getattr(self, 'logcontentpath', None):
                    try:
                        content_type_hdr = response.headers._store['content-type'][1]
                        l = content_type_hdr.split(';') + ['charset=ISO-8859-1',]
                        content_type = l[0]
                        encoding = l[1].split("=")
                        ext = "." + FILE_EXT[content_type]
                    except Exception as e:
                        ext = ""
                    self._logcontentpath = os.path.join(
                        self.commandlineargs.logpath,
                        self.commandlineargs.oper)
                    if not os.path.exists(self._logcontentpath):
                        os.makedirs(self._logcontentpath)
                    for fn in os.listdir(self._logcontentpath):
                        old_file = os.path.join(self._logcontentpath, fn)
                        if os.path.isfile(old_file):
                            os.unlink(old_file)
                fn = os.path.join(self._logcontentpath, "response_%d%s"
                                  % (self._content_log_fileno, ext ))
                f = open(fn, "w")
                f.write(response.content)
                f.close()
                logger.info("<-- Response content (encoding=%s) in file %s" %
                            (encoding, fn))
                pass
            else:
                logger.info("<-- Content: %s" % response.content)

    def wb_send_GET_startpage(self):
        """
        The action that starts the whole sequence, a HTTP GET on a web page
        """
        self.last_response = self.instance.send(self.start_page)
        self._log_response(self.last_response)

    def handle_result(self, check_response=None):
        if check_response:
            if isinstance(check_response(), Check):
                if 300 < self.last_response.status_code <= 303:
                    self._redirect(self.last_response)
                self.do_check(check_response)
            else:
                # A HTTP redirect or HTTP Post
                if 300 < self.last_response.status_code <= 303:
                    self._redirect(self.last_response)

                if self.last_response.status_code >= 400:
                    raise FatalError(self.last_response.reason)

                _txt = self.last_response.content
                assert _txt.startswith("<h2>")
        else:
            if 300 < self.last_response.status_code <= 303:
                self._redirect(self.last_response)

            _txt = self.last_response.content
            if self.last_response.status_code >= 400:
                raise FatalError("Did not expected error")

    def parse_saml_message(self):
        try:
            url, query = self.last_response.headers["location"].split("?")
        except KeyError:
            return

        _dict = parse_qs(query)
        try:
            self.relay_state = _dict["RelayState"][0]
        except KeyError:
            self.relay_state = ""
        _str = _dict["SAMLRequest"][0]
        self.saml_request = self.instance._parse_request(
            _str, SERVICE2REQUEST[self._endpoint], self._endpoint,
            self._binding)
        if self._binding == BINDING_HTTP_REDIRECT:
            self.http_parameters = HttpParameters(_dict)

    def _redirect(self, _response):
        rdseq = []
        url = None
        while _response.status_code in [302, 301, 303]:
            url = _response.headers["location"]
            if url in rdseq:
                raise FatalError("Loop detected in redirects")
            else:
                rdseq.append(url)
                if len(rdseq) > 8:
                    raise FatalError(
                        "Too long sequence of redirects: %s" % rdseq)

            logger.info("--> REDIRECT TO: %s" % url)
            # If back to me
            for_me = False
            try:
                self._endpoint, self._binding = self.which_endpoint(url)
                for_me = True
            except TypeError:
                pass

            if for_me:
                break
            else:
                try:
                    _response = self.instance.send(url, "GET")
                except Exception as err:
                    raise FatalError("%s" % err)

                self._log_response(_response)
                self.last_response = _response
                if _response.status_code >= 400:
                    break
        return url

    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 do_flow(self, flow, mid_tests):
        """
        Solicited or 'un-solicited' flows.

        Solicited always starts with the Web client accessing a page.
        Un-solicited starts with the IDP sending a SAMl Response.
        """
        if len(flow) >= 3:
            self.wb_send_GET_startpage()
            self.intermit(flow[0]._interaction)
            self.parse_saml_message()
        # make sure I got the request I expected
        assert isinstance(self.saml_request.message, flow[1]._class)
        try:
            self.test_sequence(mid_tests)
        except KeyError:
            pass
        self.send_idp_response(flow[1], flow[2])
        if len(flow) == 4:
            self.handle_result(flow[3])
        else:
            self.handle_result()

    def do_sequence_and_tests(self, oper, tests=None):
        self.current_oper = oper
        try:
            self.test_sequence(tests["pre"])
        except KeyError:
            pass

        for flow in oper:
            try:
                self.do_flow(flow, tests["mid"])
            except InteractionNeeded:
                self.test_output.append({"status": INTERACTION,
                                         "message": "see detail log for response content",
                                         "id": "exception",
                                         "name": "interaction needed",
                                         "url": self.position})
                break
            except FatalError:
                raise
            except Exception as err:
                #self.err_check("exception", err)
                raise

        try:
            self.test_sequence(tests["post"])
        except KeyError:
            pass

    def intermit(self, page_types):
        """
        Currently handles only SP-issued redirects

        :param page_types: not used (could be used to implement wayf, disco)
        """
        _response = self.last_response
        _last_action = None
        _same_actions = 0
        if _response.status_code >= 400:
            try:
                self.last_content = _response.text
            except AttributeError:
                self.last_content = None
            raise FatalError(
                "HTTP response status code: %d" % _response.status_code)

        url = _response.url
        content = _response.text
        done = False
        while not done:
            rdseq = []
            while _response.status_code in [302, 301, 303]:
                url = _response.headers["location"]
                if url in rdseq:
                    raise FatalError("Loop detected in redirects")
                else:
                    rdseq.append(url)
                    if len(rdseq) > 8:
                        raise FatalError(
                            "Too long sequence of redirects: %s" % rdseq)

                # If back to me
                for_me = False
                try:
                    self._endpoint, self._binding = self.which_endpoint(url)
                    for_me = True
                except TypeError:
                    pass

                if for_me:
                    done = True
                    break
                else:
                    try:
                        _response = self.instance.send(url, "GET")
                    except Exception as err:
                        raise FatalError("%s" % err)

                    self._log_response(_response)
                    content = _response.text
                    self.position = url
                    self.last_content = content
                    self.response = _response

                    if _response.status_code >= 400:
                        done = True
                        break

            if done or url is None:
                break

            _base = url.split("?")[0]

            try:
                _spec = self.interaction.pick_interaction(_base, content)
            except InteractionNeeded:
                self.position = url
                logger.error("Page Content: %s" % content)
                raise
            except KeyError:
                self.position = url
                logger.error("Page Content: %s" % content)
                self.err_check("interaction-needed")

            if _spec == _last_action:
                _same_actions += 1
                if _same_actions >= 3:
                    raise InteractionNeeded("Interaction loop detection")
            else:
                _last_action = _spec

            if len(_spec) > 2:
                logger.info(">> %s <<" % _spec["page-type"])
                if _spec["page-type"] == "login":
                    self.login_page = content

            _op = Action(_spec["control"])

            try:
                _response = _op(self.instance, self, logger, url,
                                _response, content, self.features)
                if isinstance(_response, dict):
                    self.last_response = _response
                    self.last_content = _response
                    return _response
                content = _response.text
                self.position = url
                self.last_content = content
                self.response = _response

                if _response.status_code >= 400:
                    break
            except (FatalError, InteractionNeeded):
                raise
            except Exception as err:
                self.err_check("exception", err, False)

        self.last_response = _response
        try:
            self.last_content = _response.text
        except AttributeError:
            self.last_content = None
Exemple #8
0
    def _func(self, conv):
        interaction = Interaction(conv.instance, [conv.json_config["result"]])
        _int = interaction.pick_interaction(content=conv.last_response.content)

        return {}
Exemple #9
0
class Conversation():
    def __init__(self, instance, config, interaction, json_config,
                 check_factory, entity_id, msg_factory=None,
                 features=None, constraints=None,  # verbose=False,
                 expect_exception=None, commandlineargs=None):
        self.instance = instance
        self._config = config
        self.test_output = []
        self.features = features
        #self.verbose = verbose  # removed (not used)
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception
        self.commandlineargs = commandlineargs

        self.cjar = {"browser": cookielib.CookieJar(),
                     "rp": cookielib.CookieJar(),
                     "service": cookielib.CookieJar()}

        self.protocol_response = []
        self.last_response = None
        self.last_content = None
        self.response = None
        self.interaction = Interaction(self.instance, interaction)
        self.exception = None

        self.entity_id = entity_id
        self.cjar = {"rp": cookielib.CookieJar()}
        self.args = {}
        self.qargs = {}
        self.response_args = {}
        self.saml_response = []
        self.destination = ""
        self.request = None
        self.position = ""
        self.response = None
        self.oper = None
        self.msg_constraints = constraints
        self.json_config = json_config
        self.start_page = json_config["start_page"]

    def check_severity(self, stat):
        if stat["status"] >= 3:
            logger.error("WHERE: %s" % stat["id"])
            logger.error("STATUS:%s" % STATUSCODE[stat["status"]])
            try:
                logger.error("HTTP STATUS: %s" % stat["http_status"])
            except KeyError:
                pass
            try:
                logger.error("INFO: %s" % stat["message"])
            except KeyError:
                pass

            raise CheckError

    def do_check(self, test, **kwargs):
        if isinstance(test, six.string_types):
            chk = self.check_factory(test)(**kwargs)
        else:
            chk = test(**kwargs)
        if not chk.call_on_redirect() and \
           300 < self.last_response.status_code <= 303:
            pass
        else:
            stat = chk(self, self.test_output)
            self.check_severity(stat)

    def err_check(self, test, err=None, bryt=True):
        if err:
            self.exception = err
        chk = self.check_factory(test)()
        chk(self, self.test_output)
        if bryt:
            e = FatalError("%s" % err)
            e.trace = "".join(traceback.format_exception(*sys.exc_info()))
            raise e

    def test_sequence(self, sequence):
        if sequence is None:
            return True

        for test in sequence:
            if isinstance(test, tuple):
                test, kwargs = test
            else:
                kwargs = {}
            self.do_check(test, **kwargs)
            if test == ExpectedError:
                return False  # TODO: return value is unused
        return True

    def my_endpoints(self):
        for serv in ["aa", "aq", "idp"]:
            endpoints = self._config.getattr("endpoints", serv)
            if endpoints:
                for typ, spec in endpoints.items():
                    for url, binding in spec:
                        yield url

    def which_endpoint(self, url):
        for serv in ["aa", "aq", "idp"]:
            endpoints = self._config.getattr("endpoints", serv)
            if endpoints:
                for typ, spec in endpoints.items():
                    for endp, binding in spec:
                        if url.startswith(endp):
                            return typ, binding
        return None

    def _log_response(self, response):
        """Depending on -k argument write content to either logger or extra file

        Create the <operation> directory; delete all possibly existing files
        Write response content into response_x.<ext> (with x incrementing from 0)
        """
        logger.info("<-- Status: %s" % response.status_code)
        if response.status_code in [302, 301, 303]:
            logger.info("<-- location: %s" %
                        response.headers._store['location'][1])
        else:
            if self.commandlineargs.content_log:
                self._content_log_fileno = getattr(self, '_content_log_fileno', 0) + 1
                if not getattr(self, 'logcontentpath', None):
                    try:
                        content_type_hdr = response.headers._store['content-type'][1]
                        l = content_type_hdr.split(';') + ['charset=ISO-8859-1',]
                        content_type = l[0]
                        encoding = l[1].split("=")
                        ext = "." + FILE_EXT[content_type]
                    except Exception as e:
                        ext = ""
                    self._logcontentpath = os.path.join(
                        self.commandlineargs.logpath,
                        self.commandlineargs.oper)
                    if not os.path.exists(self._logcontentpath):
                        os.makedirs(self._logcontentpath)
                    for fn in os.listdir(self._logcontentpath):
                        old_file = os.path.join(self._logcontentpath, fn)
                        if os.path.isfile(old_file):
                            os.unlink(old_file)
                fn = os.path.join(self._logcontentpath, "response_%d%s"
                                  % (self._content_log_fileno, ext ))
                f = open(fn, "w")
                f.write(response.content)
                f.close()
                logger.info("<-- Response content (encoding=%s) in file %s" %
                            (encoding, fn))
                pass
            else:
                logger.info("<-- Content: %s" % response.content)

    def wb_send_GET_startpage(self):
        """
        The action that starts the whole sequence, a HTTP GET on a web page
        """
        self.last_response = self.instance.send(self.start_page)
        self._log_response(self.last_response)

    def handle_result(self, check_response=None):
        if check_response:
            if isinstance(check_response(), Check):
                if 300 < self.last_response.status_code <= 303:
                    self._redirect(self.last_response)
                self.do_check(check_response)
            else:
                # A HTTP redirect or HTTP Post
                if 300 < self.last_response.status_code <= 303:
                    self._redirect(self.last_response)

                if self.last_response.status_code >= 400:
                    raise FatalError(self.last_response.reason)

                _txt = self.last_response.content
                assert _txt.startswith("<h2>")
        else:
            if 300 < self.last_response.status_code <= 303:
                self._redirect(self.last_response)

            _txt = self.last_response.content
            if self.last_response.status_code >= 400:
                raise FatalError("Did not expected error")

    def parse_saml_message(self):
        try:
            url, query = self.last_response.headers["location"].split("?")
        except KeyError:
            return

        _dict = parse_qs(query)
        try:
            self.relay_state = _dict["RelayState"][0]
        except KeyError:
            self.relay_state = ""
        _str = _dict["SAMLRequest"][0]
        self.saml_request = self.instance._parse_request(
            _str, SERVICE2REQUEST[self._endpoint], self._endpoint,
            self._binding)
        if self._binding == BINDING_HTTP_REDIRECT:
            self.http_parameters = HttpParameters(_dict)

    def _redirect(self, _response):
        rdseq = []
        url = None
        while _response.status_code in [302, 301, 303]:
            url = _response.headers["location"]
            if url in rdseq:
                raise FatalError("Loop detected in redirects")
            else:
                rdseq.append(url)
                if len(rdseq) > 8:
                    raise FatalError(
                        "Too long sequence of redirects: %s" % rdseq)

            logger.info("--> REDIRECT TO: %s" % url)
            # If back to me
            for_me = False
            try:
                self._endpoint, self._binding = self.which_endpoint(url)
                for_me = True
            except TypeError:
                pass

            if for_me:
                break
            else:
                try:
                    _response = self.instance.send(url, "GET")
                except Exception as err:
                    raise FatalError("%s" % err)

                self._log_response(_response)
                self.last_response = _response
                if _response.status_code >= 400:
                    break
        return url

    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 do_flow(self, flow, mid_tests):
        """
        Solicited or 'un-solicited' flows.

        Solicited always starts with the Web client accessing a page.
        Un-solicited starts with the IDP sending a SAMl Response.
        """
        if len(flow) >= 3:
            self.wb_send_GET_startpage()
            self.intermit(flow[0]._interaction)
            self.parse_saml_message()
        # make sure I got the request I expected
        assert isinstance(self.saml_request.message, flow[1]._class)
        try:
            self.test_sequence(mid_tests)
        except KeyError:
            pass
        self.send_idp_response(flow[1], flow[2])
        if len(flow) == 4:
            self.handle_result(flow[3])
        else:
            self.handle_result()

    def do_sequence_and_tests(self, oper, tests=None):
        self.current_oper = oper
        try:
            self.test_sequence(tests["pre"])
        except KeyError:
            pass

        for flow in oper:
            try:
                self.do_flow(flow, tests["mid"])
            except InteractionNeeded:
                self.test_output.append({"status": INTERACTION,
                                         "message": "see detail log for response content",
                                         "id": "exception",
                                         "name": "interaction needed",
                                         "url": self.position})
                break
            except FatalError:
                raise
            except Exception as err:
                #self.err_check("exception", err)
                raise

        try:
            self.test_sequence(tests["post"])
        except KeyError:
            pass

    def intermit(self, page_types):
        """
        Currently handles only SP-issued redirects

        :param page_types: not used (could be used to implement wayf, disco)
        """
        _response = self.last_response
        _last_action = None
        _same_actions = 0
        if _response.status_code >= 400:
            try:
                self.last_content = _response.text
            except AttributeError:
                self.last_content = None
            raise FatalError(
                "HTTP response status code: %d" % _response.status_code)

        url = _response.url
        content = _response.text
        done = False
        while not done:
            rdseq = []
            while _response.status_code in [302, 301, 303]:
                url = _response.headers["location"]
                if url in rdseq:
                    raise FatalError("Loop detected in redirects")
                else:
                    rdseq.append(url)
                    if len(rdseq) > 8:
                        raise FatalError(
                            "Too long sequence of redirects: %s" % rdseq)

                # If back to me
                for_me = False
                try:
                    self._endpoint, self._binding = self.which_endpoint(url)
                    for_me = True
                except TypeError:
                    pass

                if for_me:
                    done = True
                    break
                else:
                    try:
                        _response = self.instance.send(url, "GET")
                    except Exception as err:
                        raise FatalError("%s" % err)

                    self._log_response(_response)
                    content = _response.text
                    self.position = url
                    self.last_content = content
                    self.response = _response

                    if _response.status_code >= 400:
                        done = True
                        break

            if done or url is None:
                break

            _base = url.split("?")[0]

            try:
                _spec = self.interaction.pick_interaction(_base, content)
            except InteractionNeeded:
                self.position = url
                logger.error("Page Content: %s" % content)
                raise
            except KeyError:
                self.position = url
                logger.error("Page Content: %s" % content)
                self.err_check("interaction-needed")

            if _spec == _last_action:
                _same_actions += 1
                if _same_actions >= 3:
                    raise InteractionNeeded("Interaction loop detection")
            else:
                _last_action = _spec

            if len(_spec) > 2:
                logger.info(">> %s <<" % _spec["page-type"])
                if _spec["page-type"] == "login":
                    self.login_page = content

            _op = Action(_spec["control"])

            try:
                _response = _op(self.instance, self, logger, url,
                                _response, content, self.features)
                if isinstance(_response, dict):
                    self.last_response = _response
                    self.last_content = _response
                    return _response
                content = _response.text
                self.position = url
                self.last_content = content
                self.response = _response

                if _response.status_code >= 400:
                    break
            except (FatalError, InteractionNeeded):
                raise
            except Exception as err:
                self.err_check("exception", err, False)

        self.last_response = _response
        try:
            self.last_content = _response.text
        except AttributeError:
            self.last_content = None