def test_collect_intermediate(self): _collector = self.endpoint.server_get( "endpoint_context").federation_entity.collector subject = 'https://op.ntnu.no' intermediate = 'https://ntnu.no' fedop1 = 'https://feide.no' fedop2 = 'https://swamid.se' # self-signed from subject es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(subject)) subj_sesi = es_api.create_entity_statement(get_netloc(subject)) # self-signed from intermediate es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(intermediate)) inter_sesi = es_api.create_entity_statement(get_netloc(intermediate)) # self-signed from fedop es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop1)) fedop_sesi_1 = es_api.create_entity_statement(get_netloc(fedop1)) es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop2)) fedop_sesi_2 = es_api.create_entity_statement(get_netloc(fedop2)) # intermediate on subject es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(intermediate)) inter_on_sub = es_api.create_entity_statement(get_netloc(subject)) # fedop on intermediate es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop1)) fedop_on_inter_1 = es_api.create_entity_statement( get_netloc(intermediate)) es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop2)) fedop_on_inter_2 = es_api.create_entity_statement( get_netloc(intermediate)) sleep(1) with responses.RequestsMock() as rsps: _url = "{}/.well-known/openid-federation".format(intermediate) rsps.add("GET", _url, body=inter_sesi, status=200) _url = "{}/.well-known/openid-federation".format(fedop1) rsps.add("GET", _url, body=fedop_sesi_1, status=200) _url = "{}/.well-known/openid-federation".format(fedop2) rsps.add("GET", _url, body=fedop_sesi_2, status=200) _url = 'https://ntnu.no/api?iss=https%3A%2F%2Fntnu.no&sub=https%3A%2F%2Fop.ntnu.no' rsps.add("GET", _url, body=inter_on_sub, status=200) _url = 'https://feide.no/api?iss=https%3A%2F%2Ffeide.no&sub=https%3A%2F%2Fntnu.no' rsps.add("GET", _url, body=fedop_on_inter_1, status=200) _url = 'https://swamid.se/api?iss=https%3A%2F%2Fswamid.se&sub=https%3A%2F%2Fntnu.no' rsps.add("GET", _url, body=fedop_on_inter_2, status=200) tree = _collector.collect_intermediate(subject, 'https://ntnu.no') assert tree assert len(_collector.config_cache) == 3 assert set(_collector.config_cache.keys()) == { 'https://ntnu.no', 'https://feide.no', 'https://swamid.se' } # The unpacked fedop1's self signed entity statement _info = _collector.config_cache['https://feide.no'] assert _info['sub'] == fedop1 assert _info['iss'] == fedop1 assert _info['metadata']['federation_entity'][ 'federation_api_endpoint'] == 'https://feide.no/api' # For each entity statement there is also the expiration time assert len(_collector.entity_statement_cache) == 6 assert set(_collector.entity_statement_cache.keys()) == { 'https://feide.no!!https://ntnu.no', 'https://feide.no!exp!https://ntnu.no', 'https://ntnu.no!!https://op.ntnu.no', 'https://ntnu.no!exp!https://op.ntnu.no', 'https://swamid.se!!https://ntnu.no', 'https://swamid.se!exp!https://ntnu.no' } # have a look at the payload _info = unverified_entity_statement( _collector. entity_statement_cache['https://swamid.se!!https://ntnu.no']) assert _info['sub'] == intermediate assert _info['iss'] == fedop2 assert _info['authority_hints'] == [fedop2] _collector_dump = _collector.dump() _c2 = Collector() _c2.load(_collector_dump) assert len(_c2.config_cache) == 3 assert set(_c2.config_cache.keys()) == { 'https://ntnu.no', 'https://feide.no', 'https://swamid.se' } # The unpacked fedop1's self signed entity statement _info = _c2.config_cache['https://feide.no'] assert _info['sub'] == fedop1 assert _info['iss'] == fedop1 assert _info['metadata']['federation_entity'][ 'federation_api_endpoint'] == 'https://feide.no/api' # For each entity statement there is also the expiration time assert len(_c2.entity_statement_cache) == 6 assert set(_c2.entity_statement_cache.keys()) == { 'https://feide.no!!https://ntnu.no', 'https://feide.no!exp!https://ntnu.no', 'https://ntnu.no!!https://op.ntnu.no', 'https://ntnu.no!exp!https://op.ntnu.no', 'https://swamid.se!!https://ntnu.no', 'https://swamid.se!exp!https://ntnu.no' } # have a look at the payload _info = unverified_entity_statement( _c2.entity_statement_cache['https://swamid.se!!https://ntnu.no']) assert _info['sub'] == intermediate assert _info['iss'] == fedop2 assert _info['authority_hints'] == [fedop2]
class FederationEntity(object): name = "federation_entity" def __init__(self, entity_id: str = "", config: Optional[Union[dict, Configuration]] = None, httpc: Optional[Any] = None, cwd: Optional[str] = '', httpc_params: Optional[dict] = None): if config is None: config = {} if httpc is None: httpc = request if httpc_params is None: httpc_params = config.get("httpc_params", {}) if not entity_id: entity_id = config.get("entity_id") self.context = FederationContext(config=config, entity_id=entity_id, server_get=self.server_get) self.collector = Collector(trust_anchors=self.context.trusted_roots, http_cli=httpc, cwd=cwd, httpc_params=httpc_params) if config.get("entity_id") is None: config['entity_id'] = entity_id if 'endpoint' in config: self.endpoint = do_endpoints(config, self.server_get) else: self.endpoint = {} def collect_statement_chains(self, entity_id, statement): return self.collector.collect_superiors(entity_id, statement) def eval_chains(self, chains, entity_type='', apply_policies=True): """ :param chains: A list of lists of signed JWT :param entity_type: The leafs entity type :param apply_policies: Apply metadata policies from the list on the the metadata of the leaf entity :return: List of TrustChain instances """ _context = self.context if not entity_type: entity_type = _context.opponent_entity_type return [ eval_chain(c, _context.keyjar, entity_type, apply_policies) for c in chains ] def get_configuration_information(self, subject_id): return self.collector.get_configuration_information(subject_id) def pick_trust_chain(self, trust_chains): """ Pick one trust chain out of the list of possible trust chains :param trust_chains: A list of :py:class:`fedservice.entity_statement.statement.TrustChain instances :return: A :py:class:`fedservice.entity_statement.statement.TrustChain instance """ if len(trust_chains) == 1: # If there is only one, then use it return trust_chains[0] elif self.context.tr_priority: # Go by priority for fid in self.context.tr_priority: for trust_chain in trust_chains: if trust_chain.anchor == fid: return trust_chain # Can only arrive here if the federations I got back and trust are not # in the priority list. So, just pick one return trust_chains[0] def get_payload(self, self_signed_statement): _jws = as_unicode(self_signed_statement) _jwt = factory(_jws) return _jwt.jwt.payload() def collect_trust_chains(self, self_signed_statement, metadata_type): """ :param self_signed_statement: A Self signed Entity Statement :param metadata_type: One of the metadata types defined in the specification :return: """ payload = self.get_payload(self_signed_statement) # collect trust chains _tree = self.collect_statement_chains(payload['iss'], payload) _node = {payload['iss']: (self_signed_statement, _tree)} _chains = branch2lists(_node) logger.debug("%s chains", len(_chains)) # verify the trust paths and apply policies return [ eval_chain(c, self.context.keyjar, metadata_type) for c in _chains ] def server_get(self, what, *arg): _func = getattr(self, "get_{}".format(what), None) if _func: return _func(*arg) return None def get_context(self, *arg): return self.context def get_endpoint_context(self, *arg): return self.context def federation_endpoint_metadata(self): _config = self.context.config metadata = {} # collect endpoints endpoints = {} for key, item in self.endpoint.items(): if key in ["fetch", "list", "status", "evaluate"]: endpoints[f"federation_{key}_endpoint"] = item.full_path for attr in message.FederationEntity.c_param.keys(): if attr in _config: metadata[attr] = _config[attr] elif attr in endpoints: metadata[attr] = endpoints[attr] return {"federation_entity": metadata} def get_metadata(self): _config = self.context.config return self.federation_endpoint_metadata() def get_endpoints(self, *arg): return self.endpoint def get_endpoint(self, endpoint_name, *arg): try: return self.endpoint[endpoint_name] except KeyError: return None def get_entity(self): return self def dump(self): return { "context": self.context.dump(), "collector": self.collector.dump() } def load(self, dump): self.collector.load(dump.get("collector", {})) self.context.load(dump.get("context", {})) def get_client_id(self): return self.context.entity_id