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()
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