Exemplo n.º 1
0
class Conversation(object):
    """
    :param response: The received HTTP messages
    :param protocol_response: List of the received protocol messages
    """

    def __init__(self, client, config, trace, interaction,
                 check_factory=None, msg_factory=None,
                 features=None, verbose=False, expect_exception=None,
                 **extra_args):
        self.entity = client
        self.entity_config = config
        self.trace = trace
        self.features = features
        self.verbose = verbose
        self.check_factory = check_factory
        self.msg_factory = msg_factory
        self.expect_exception = expect_exception
        self.extra_args = extra_args

        self.cjar = {"browser": http.cookiejar.MozillaCookieJar(),
                     "rp": http.cookiejar.MozillaCookieJar(),
                     "service": http.cookiejar.MozillaCookieJar()}

        self.events = Events()
        self.interaction = Interaction(self.entity, interaction)
        self.exception = None
        self.provider_info = self.entity.provider_info or {}
        self.interact_done = []
        self.ignore_check = []
        self.login_page = ""
        self.sequence = {}
        self.flow_index = 0
        self.request_args = {}
        self.args = {}
        self.creq = None
        self.cresp = None
        self.req = None
        self.request_spec = None
        self.last_url = ""
        self.state = rndstr()

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

            if not stat["mti"]:
                raise Break(stat["message"])
            else:
                raise FatalError(stat["message"])

    def do_check(self, test, **kwargs):
        if isinstance(test, str):
            chk = self.check_factory(test)(**kwargs)
        else:
            chk = test(**kwargs)

        if chk.__class__.__name__ not in self.ignore_check:
            stat = chk(self, self.events.last('condition').data)
            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.events.last('condition').data)
        if bryt:
            e = FatalError("%s" % err)
            e.trace = "".join(traceback.format_exception(*sys.exc_info()))
            raise e

    def test_sequence(self, sequence):
        if isinstance(sequence, dict):
            for test, kwargs in list(sequence.items()):
                self.do_check(test, **kwargs)
        else:
            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):
        return []

    def for_me(self, response="", url=""):
        if not response:
            response = self.events.last('response').data
        if not url:
            url = response.headers["location"]
        for redirect_uri in self.my_endpoints():
            if url.startswith(redirect_uri):
                return True
        return False

    def intermit(self):
        _response = self.events.last('response').data
        if _response.status_code >= 400:
            done = True
        else:
            done = False

        rdseq = []
        while not done:
            url = _response.url
            content = _response.text

            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)

                self.trace.reply("REDIRECT TO: %s" % url)

                # If back to me
                if self.for_me(_response):
                    self.entity.cookiejar = self.cjar["rp"]
                    done = True
                    break
                else:
                    try:
                        _response = self.entity.send(
                            url, "GET", headers={"Referer": self.last_url})
                    except Exception as err:
                        raise FatalError("%s" % err)

                    content = _response.text
                    self.trace.reply("CONTENT: %s" % content)
                    self.events.store('position', url)
                    self.events.store('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)
                # if _spec in self.interact_done:
                #    self.trace.error("Same interaction a second time")
                #    raise InteractionNeeded("Same interaction twice")
                # self.interact_done.append(_spec)
            except InteractionNeeded:
                if self.extra_args["break"]:
                    self.dump_state(self.extra_args["break"])
                    exit(2)

                self.position = url
                self.trace.error("Page Content: %s" % content)
                raise
            except KeyError:
                self.position = url
                self.trace.error("Page Content: %s" % content)
                self.err_check("interaction-needed")

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

            _op = Action(_spec["control"])

            try:
                _response = _op(self.entity, self, self.trace, url,
                                _response, content, self.features)
                if isinstance(_response, dict):
                    self.events.store(EV_RESPONSE, _response,
                                      sender=self.__class__)
                    return _response
                self.events.store('position', url)
                self.events.store(EV_HTTP_RESPONSE, _response)
                self.events.store('received', _response.text)

                if _response.status_code >= 400:
                    break

            except (FatalError, InteractionNeeded):
                raise
            except Exception as err:
                self.err_check("exception", err, False)
                self.events.store('condition',
                                  TestResult(test_id="Communication error",
                                             status=3,
                                             message="{}".format(err)))
                raise FatalError

        self.events.store(EV_HTTP_RESPONSE, _response, sender=self.__class__)
        try:
            self.events.store('content', _response.text)
        except AttributeError:
            self.events.store('content', None)

    def init(self, phase):
        self.creq, self.cresp = phase

    def setup_request(self):
        self.request_spec = req = self.creq(conv=self)

        if isinstance(req, Operation):
            for intact in self.interaction.interactions:
                try:
                    if req.__class__.__name__ == intact["matches"]["class"]:
                        req.args = intact["args"]
                        break
                except KeyError:
                    pass
        else:
            try:
                self.request_args = req.request_args
            except KeyError:
                pass
            try:
                self.args = req.kw_args
            except KeyError:
                pass

        # The authorization dance is all done through the browser
        if req.request == "AuthorizationRequest":
            self.entity.cookiejar = self.cjar["browser"]
        # everything else by someone else, assuming the RP
        else:
            self.entity.cookiejar = self.cjar["rp"]

        self.req = req

    def send(self):
        pass

    def handle_result(self):
        pass

    def do_query(self):
        self.setup_request()
        self.send()
        last_response = self.events.last('response').data
        if last_response.status_code in [301, 302, 303] and \
                not self.for_me():
            self.intermit()
        if not self.handle_result():
            self.intermit()
            self.handle_result()

    def do_sequence(self, oper):
        self.sequence = oper
        try:
            self.test_sequence(oper["tests"]["pre"])
        except KeyError:
            pass

        for i in range(self.flow_index, len(oper["sequence"])):
            phase = oper["sequence"][i]
            flow = oper["flow"][i]
            self.flow_index = i

            self.trace.info(flow)
            if not isinstance(phase, tuple):
                _proc = phase()
                _proc(self)
                continue

            self.init(phase)

            try:
                _cimp = self.extra_args["cookie_imp"]
            except KeyError:
                pass
            else:
                if self.creq.request == "AuthorizationRequest" and _cimp:
                    try:
                        self.cjar['browser'].load(_cimp)
                    except Exception:
                        self.trace.error("Could not import cookies from file")

            try:
                _kaka = self.extra_args["login_cookies"]
            except KeyError:
                pass
            else:
                self.entity.cookiejar = self.cjar["browser"]
                self.entity.load_cookies_from_file(_kaka.name)

            try:
                self.do_query()
            except InteractionNeeded:
                self.events.store(EV_CONDITION,
                                  TestResult(status=INTERACTION,
                                             message=self.events.last_item(
                                                 'received'),
                                             test_id="exception",
                                             name="interaction needed",
                                             url=self.position),
                                  sender=self.__class__)
                break
            except FatalError:
                raise
            except PyoidcError as err:
                if err.message:
                    self.trace.info("Protocol message: %s" % err.message)
                raise FatalError
            except Exception as err:
                # self.err_check("exception", err)
                raise
            else:
                if self.extra_args["cookie_exp"]:
                    if self.request_spec.request == "AuthorizationRequest":
                        self.cjar["browser"].save(
                            self.extra_args["cookie_exp"], ignore_discard=True)

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

    def dump_state(self, filename):
        state = {
            "client": {
                "behaviour": self.entity.behaviour,
                "keyjar": self.entity.keyjar.dump(),
                "provider_info": self.entity.provider_info.to_json(),
                "client_id": self.entity.client_id,
                "client_secret": self.entity.client_secret,
            },
            "trace_log": {"start": self.trace.start, "trace": self.trace.trace},
            "sequence": self.sequence["flow"],
            "flow_index": self.flow_index,
            "entity_config": self.entity_config,
            "condition checks": self.events.get('condition')
        }

        try:
            state["client"][
                "registration_resp"] = \
                self.entity.registration_response.to_json()
        except AttributeError:
            pass

        txt = json.dumps(state)
        _fh = open(filename, "w")
        _fh.write(txt)
        _fh.close()

    # def restore_state(self, filename):
    #     txt = open(filename).read()
    #     state = json.loads(txt)
    #     self.trace.start = state["trace_log"]["start"]
    #     self.trace.trace = state["trace_log"]["trace"]
    #     self.flow_index = state["flow_index"]
    #     self.entity_config = state["entity_config"]
    #     self.condition_checks = state["condition checks"]
    #
    #     self.entity.behaviour = state["client"]["behaviour"]
    #     self.entity.keyjar.restore(state["client"]["keyjar"])
    #     pcr = ProviderConfigurationResponse().from_json(
    #         state["client"]["provider_info"])
    #     self.entity.provider_info = pcr
    #     self.entity.client_id = state["client"]["client_id"]
    #     self.entity.client_secret = state["client"]["client_secret"]
    #
    #     for key, val in list(pcr.items()):
    #         if key.endswith("_endpoint"):
    #             setattr(self.entity, key, val)
    #
    #     try:
    #         self.entity.registration_response = RegistrationResponse(
    # ).from_json(
    #             state["client"]["registration_resp"])
    #     except KeyError:
    #         pass

    def restart(self, state):
        pass
Exemplo n.º 2
0
class Conversation(object):
    def __init__(self, flow, entity, msg_factory, check_factory=None,
                 features=None, trace_cls=Trace, **extra_args):
        self.flow = flow
        self.entity = entity
        self.msg_factory = msg_factory
        self.trace = trace_cls(True)
        self.test_id = ""
        self.info = {}
        self.index = 0
        self.comhandler = None
        self.check_factory = check_factory
        self.features = features
        self.extra_args = extra_args
        self.exception = None
        self.events = Events()
        self.sequence = []

        try:
            self.callback_uris = extra_args["callback_uris"]
        except KeyError:
            pass

        self.trace.info('Conversation initiated')

    def for_me(self, url):
        for cb in self.callback_uris:
            if url.startswith(cb):
                return True
        return False

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

    def my_endpoints(self):
        return self.entity.redirect_uris

    def dump_state(self, filename):
        state = {
            "client": {
                "behaviour": self.entity.behaviour,
                "keyjar": self.entity.keyjar.dump(),
                "provider_info": self.entity.provider_info.to_json(),
                "client_id": self.entity.client_id,
                "client_secret": self.entity.client_secret,
            },
            "trace_log": {"start": self.trace.start, "trace": self.trace.trace},
            "sequence": self.flow,
            "flow_index": self.index,
            "client_config": self.entity.conf,
            "condition": self.events.get('condition')
        }

        try:
            state["client"][
                "registration_resp"] = \
                self.entity.registration_response.to_json()
        except AttributeError:
            pass

        txt = json.dumps(state)
        _fh = open(filename, "w")
        _fh.write(txt)
        _fh.close()

    def do_interaction(self, url, content, response):
        _base = url.split("?")[0]

        try:
            _spec = self.interaction.pick_interaction(_base, content)
        except InteractionNeeded:
            if self.extra_args["break"]:
                self.dump_state(self.extra_args["break"])
                exit(2)

            self.events.store('position', url)
            self.trace.error("Page Content: %s" % content)
            raise
        except KeyError:
            self.events.store('position', url)
            self.trace.error("Page Content: %s" % content)
            self.err_check("interaction-needed")
            raise

        if _spec is None:
            return response

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

        _op = Action(_spec["control"])

        try:
            _response = _op(self.entity, self, self.trace, url,
                            response, content, self.features)
            if isinstance(_response, dict):
                self.events.store('response', _response)
                # self.events.store('last_content', _response)
                return _response

            content = _response.text
            self.events.store('position', url)
            self.events.store('content', content)
            self.events.store('response', _response)
            return _response

        except (FatalError, InteractionNeeded):
            raise
        except Exception as err:
            self.err_check("exception", err, False)
            self.events.store('condition',
                              State(status=3, test_id="Communication error",
                                    message="{}".format(err)))
            raise FatalError

    def intermit(self, response):
        if response.status_code >= 400:
            done = True
        else:
            done = False

        content = response.text
        rdseq = []
        while not done:
            url = response.url

            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)

                self.trace.reply("REDIRECT TO: %s" % url)

                # If back to me
                if self.for_me(url):
                    done = True
                    break
                else:
                    try:
                        response = self.entity.send(
                            url, "GET",
                            headers={"Referer": self.events.last('position')})
                    except Exception as err:
                        raise FatalError("%s" % err)

                    content = response.text
                    self.trace.reply("CONTENT: %s" % content)
                    self.events.store('response', response)

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

            if done or url is None:
                break

            response = self.do_interaction(url, content, response)

            if response.status_code < 300 or response.status_code >= 400:
                break

        return response

    def parse_request_response(self, reqresp, response, body_type, state="",
                               **kwargs):

        text = reqresp.text
        if reqresp.status_code in SUCCESSFUL:
            body_type = verify_header(reqresp, body_type)
        elif reqresp.status_code == 302:  # redirect
            text = reqresp.headers["location"]
        elif reqresp.status_code == 500:
            logger.error("(%d) %s" % (reqresp.status_code, reqresp.text))
            raise ParseError("ERROR: Something went wrong: %s" % reqresp.text)
        elif reqresp.status_code in [400, 401]:
            # expecting an error response
            if issubclass(response, ErrorResponse):
                pass
        else:
            logger.error("(%d) %s" % (reqresp.status_code, reqresp.text))
            raise HttpError("HTTP ERROR: %s [%s] on %s" % (
                reqresp.text, reqresp.status_code, reqresp.url))

        if body_type:
            if response:
                return self.entity.parse_response(response, text,
                                                  body_type, state, **kwargs)
            else:
                raise OtherError("Didn't expect a response body")
        else:
            return reqresp
Exemplo n.º 3
0
class TestEvents():
    @pytest.fixture(autouse=True)
    def setup_consumer(self):
        self.events = Events()

    def test_store_event(self):
        i = self.events.store('foo', 'bar')
        assert i

    def test_by_index(self):
        i = self.events.store('foo', 'bar')
        ev = self.events.by_index(i)
        assert ev.typ == 'foo'
        assert ev.data == 'bar'

    def test_by_ref(self):
        i = self.events.store('foo', 'bar')
        self.events.store('foo', 'bav', i)
        evl = self.events.by_ref(i)
        assert len(evl) == 1

    def test_get(self):
        self.events.store('foo', 'bar')
        evl = self.events.get('foo')
        assert len(evl) == 1
        assert evl[0].data == 'bar'

        self.events.store('foo', 'bav')
        evl = self.events.get('foo')
        assert len(evl) == 2

    def test_get_data(self):
        self.events.store('foo', 'bar')
        self.events.store('foo', 'bav')
        dl = self.events.get_data('foo')
        assert _eq(dl, ['bar', 'bav'])

    def test_get_messages(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', Action(None))
        self.events.store('response', HandlerResponse(True, user_action='OK'))

        mesg = self.events.get_messages('response', HandlerResponse)
        assert len(mesg) == 2

    def test_last(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', Action(None))
        self.events.store('response', HandlerResponse(True, user_action='OK'))

        ev = self.events.last('response')
        assert isinstance(ev, Event)
        assert isinstance(ev.data, HandlerResponse)
        assert ev.data.content_processed == True
        assert ev.data.user_action == 'OK'

    def test_get_message(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', HandlerResponse(True, user_action='OK'))
        self.events.store('response', Action(None))

        hr = self.events.get_message('response', HandlerResponse)
        assert isinstance(hr, HandlerResponse)
        assert hr.content_processed == True
        assert hr.user_action == 'OK'

    def test_last_item(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)

        i = self.events.last_item('index')
        assert i == 2

    def test_len(self):
        self.events.store('index', 0)
        assert len(self.events) == 1
        self.events.store('index', 1)
        assert len(self.events) == 2
        self.events.store('index', 2)
        assert len(self.events) == 3

    def test_iter(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)
        evl = [l for l in self.events]
        assert len(evl) == 3

    def test_getitem(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)

        dl = self.events['index']
        assert len(dl) == 3
        assert _eq(dl, [0,1,2])

    def test_setitem(self):
        self.events['index'] = 0
        self.events['index'] = 1
        self.events['index'] = 2

        dl = self.events['index']
        assert len(dl) == 3
        assert _eq(dl, [0,1,2])

    def test_append(self):
        ev = Event(typ='doo', data='doo')
        self.events.append(ev)
        assert len(self.events) == 1

    def test_extend(self):
        evl = [
            Event(typ='doo', data='doo'),
            Event(typ='once', data='more'),
            Event(typ='that', data='thing'),
        ]
        self.events.extend(evl)
        assert len(self.events) == 3

    def test_last_of(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', HandlerResponse(True, user_action='OK'))
        self.events.store('response', Action(None))
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('song', 'doremi')

        data = self.events.last_of(['response'])
        assert isinstance(data, Action)

        data = self.events.last_of(['response', 'song'])
        assert data == 'doremi'
Exemplo n.º 4
0
class TestEvents():
    @pytest.fixture(autouse=True)
    def setup_consumer(self):
        self.events = Events()

    def test_store_event(self):
        i = self.events.store('foo', 'bar')
        assert i

    def test_by_index(self):
        i = self.events.store('foo', 'bar')
        ev = self.events.by_index(i)
        assert ev.typ == 'foo'
        assert ev.data == 'bar'

    def test_by_ref(self):
        i = self.events.store('foo', 'bar')
        self.events.store('foo', 'bav', i)
        evl = self.events.by_ref(i)
        assert len(evl) == 1

    def test_get(self):
        self.events.store('foo', 'bar')
        evl = self.events.get('foo')
        assert len(evl) == 1
        assert evl[0].data == 'bar'

        self.events.store('foo', 'bav')
        evl = self.events.get('foo')
        assert len(evl) == 2

    def test_get_data(self):
        self.events.store('foo', 'bar')
        self.events.store('foo', 'bav')
        dl = self.events.get_data('foo')
        assert _eq(dl, ['bar', 'bav'])

    def test_get_messages(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', Action(None))
        self.events.store('response', HandlerResponse(True))

        mesg = self.events.get_messages('response', HandlerResponse)
        assert len(mesg) == 2

    def test_last(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', Action(None))
        self.events.store('response', HandlerResponse(True))

        ev = self.events.last('response')
        assert isinstance(ev, Event)
        assert isinstance(ev.data, HandlerResponse)
        assert ev.data.content_processed == True

    def test_get_message(self):
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', HandlerResponse(True))
        self.events.store('response', Action(None))

        hr = self.events.get_message('response', HandlerResponse)
        assert isinstance(hr, HandlerResponse)
        assert hr.content_processed == True

    def test_last_item(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)

        i = self.events.last_item('index')
        assert i == 2

    def test_len(self):
        self.events.store('index', 0)
        assert len(self.events) == 1
        self.events.store('index', 1)
        assert len(self.events) == 2
        self.events.store('index', 2)
        assert len(self.events) == 3

    def test_iter(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)
        evl = [l for l in self.events]
        assert len(evl) == 3

    def test_getitem(self):
        self.events.store('index', 0)
        self.events.store('index', 1)
        self.events.store('index', 2)

        dl = self.events['index']
        assert len(dl) == 3
        assert _eq(dl, [0,1,2])

    def test_setitem(self):
        self.events['index'] = 0
        self.events['index'] = 1
        self.events['index'] = 2

        dl = self.events['index']
        assert len(dl) == 3
        assert _eq(dl, [0,1,2])

    def test_append(self):
        ev = Event(typ='doo', data='doo')
        self.events.append(ev)
        assert len(self.events) == 1

    def test_extend(self):
        evl = [
            Event(typ='doo', data='doo'),
            Event(typ='once', data='more'),
            Event(typ='that', data='thing'),
        ]
        self.events.extend(evl)
        assert len(self.events) == 3

    def test_last_of(self):
        for ev in EVENT_SEQUENCE:
            self.events.store(*ev)

        data = self.events.last_of(['response'])
        assert isinstance(data, Action)

        data = self.events.last_of(['response', 'song'])
        assert data == 'doremi'

    def test_contains(self):
        for ev in EVENT_SEQUENCE:
            self.events.store(*ev)

        ev = self.events.events[0]

        assert ev in self.events

        ev = Event(typ='foo', data='bar')

        assert ev not in self.events

    def test_sort(self):
        for ev in EVENT_SEQUENCE:
            self.events.store(*ev)

        ev = Event(100, typ='foo', data='bar')

        self.events.append(ev)

        # should be last

        assert ev == self.events.events[len(self.events)-1]

        self.events.sort()

        # should now be first

        assert ev == self.events.events[0]

    def test_print(self):
        for ev in EVENT_SEQUENCE:
            self.events.store(*ev)

        s = '{}'.format(self.events)
        assert s