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
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
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'
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