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]
Exemple #2
0
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