def test_collect_superiors(): # entity_id = 'https://feide.no' entity_id = 'https://foodle.uninett.no' target = 'https://foodle.uninett.no' collector = DummyCollector(trusted_roots=ANCHOR, httpd=Publisher( os.path.join(BASE_PATH, 'base_data')), root_dir=os.path.join(BASE_PATH, 'base_data')) entity_statement = collector.get_entity_statement( api_endpoint='https://foodle.uninett.no/fed_api', issuer=entity_id, subject=entity_id) _config = verify_self_signed_signature(entity_statement) assert _config tree = collector.collect_superiors(_config['iss'], entity_statement) node = {entity_id: (entity_statement, tree)} chains = branch2lists(node) assert len(chains) == 1 # only one chain assert len(chains[0]) == 4 # And that chain contains 4 statements _jws00 = factory(chains[0][0]) payload = _jws00.jwt.payload() # The Federation Entity Statement will be first in line assert payload["iss"] == 'https://feide.no'
def parse_federation_response(self, response, **kwargs): """ Takes a provider info response and parses it. If according to the info the OP has more then one federation in common with the client then the decision has to be handled higher up. For each Metadata statement that appears in the response, and was possible to parse, one :py:class:`fedservice.entity_statement.statement.Statement` instance is stored in the response by federation operator ID under the key 'fos'. :param response: A self-signed JWT containing an entity statement :returns: A list of lists of Statement instances. The innermost lists represents trust chains """ entity_statement = verify_self_signed_signature(response) entity_id = entity_statement['iss'] _fe = self.client_get("service_context").federation_entity _tree = _fe.collect_statement_chains(entity_id, entity_statement) _node = {entity_id: (response, _tree)} logger.debug("Translate tree to chains") _chains = branch2lists(_node) logger.debug("%s chains", len(_chains)) for c in _chains: c.append(response) return [eval_chain(c, _fe.keyjar, 'openid_provider') for c in _chains]
def test_eval_chains(): target = 'https://foodle.uninett.no' collector = DummyCollector(trusted_roots=ANCHOR, httpd=Publisher( os.path.join(BASE_PATH, 'base_data')), root_dir=os.path.join(BASE_PATH, 'base_data')) entity_statement = collector.get_entity_statement(target, issuer=target, subject=target) _config = verify_self_signed_signature(entity_statement) assert _config tree = collector.collect_superiors(_config['iss'], entity_statement) _node = {target: (entity_statement, tree)} chains = branch2lists(_node) key_jar = KeyJar() key_jar.import_jwks_as_json(jwks, 'https://feide.no') statements = [ eval_chain(c, key_jar, 'openid_relying_party') for c in chains ] assert len(statements) == 1 statement = statements[0] assert statement.fo == "https://feide.no" assert set(statement.metadata.keys()) == { 'response_types', 'claims', 'contacts', 'application_type', 'redirect_uris', 'id_token_signing_alg_values_supported', 'jwks_uri' }
def test_eval_path(self): leaf_entity_id = 'https://foodle.uninett.no' _jws = self.fedent.collector.get_entity_statement( '', leaf_entity_id, leaf_entity_id) tree = self.fedent.collect_statement_chains(leaf_entity_id, _jws) _node = {leaf_entity_id: (_jws, tree)} chains = branch2lists(_node) statements = [ eval_chain(c, self.fedent.keyjar, 'openid_relying_party') for c in chains ] assert len(statements) == 1 statement = statements[0] assert set(statement.metadata.keys()) == { 'application_type', 'claims', 'id_token_signing_alg_values_supported', 'redirect_uris', 'contacts', 'response_types', 'jwks_uri' } statement = self.fedent.pick_metadata(statements) assert statement.fo == 'https://feide.no' assert set(statement.metadata.keys()) == { 'application_type', 'claims', 'id_token_signing_alg_values_supported', 'redirect_uris', 'contacts', 'response_types', 'jwks_uri' }
def main(fedent, entity_id, entity_type): _jws = fedent.get_configuration_information(entity_id) _jwt = factory(_jws) msg = _jwt.jwt.payload() tree = fedent.collect_statement_chains(entity_id, msg) chains = branch2lists((_jws, tree)) statements = [eval_chain(c, fedent.keyjar, entity_type) for c in chains] return statements
def test_parse_registration_response(self): # construct the entity statement the OP should return es_api = FSEntityStatementAPI(os.path.join(BASE_PATH, 'base_data'), iss="op.ntnu.no") jws = es_api.create_entity_statement("op.ntnu.no") # parse the response and collect the trust chains res = self.service['discovery'].parse_response(jws) self.service['discovery'].update_service_context(res) _sc = self.service['registration'].service_context self.service['registration'].endpoint = _sc.get('provider_info')[ 'federation_registration_endpoint'] # construct the client registration request req_args = {'entity_id': self.federation_entity.entity_id} jws = self.service['registration'].construct(request_args=req_args) assert jws # construct the information needed to send the request _info = self.service['registration'].get_request_parameters( request_body_type="jose", method="POST") # create the request _req_jwt = factory(_info['body']) payload = _req_jwt.jwt.payload() # The OP as federation entity _fe = _sc.federation_entity del _fe.keyjar["https://op.ntnu.no"] # make sure I have the private keys _fe.keyjar.import_jwks( es_api.keyjar.export_jwks(True, "https://op.ntnu.no"), "https://op.ntnu.no" ) tree = _fe.collect_statement_chains(payload['iss'], _info['body']) _node = {payload['iss']: (_info['body'], tree)} chains = branch2lists(_node) statements = [eval_chain(c, _fe.keyjar, 'openid_relying_party') for c in chains] metadata_policy = { "client_id": {"value": "aaaaaaaaa"}, "client_secret": {"value": "bbbbbbbbbb"} } # This is the registration response from the OP _jwt = _fe.create_entity_statement( 'https://op.ntnu.no', 'https://foodle.uninett.no', metadata_policy={_fe.entity_type: metadata_policy}, metadata={"federation_entity": {"trust_anchor_id": statements[0].fo}}, authority_hints=['https://feide.no']) claims = self.service['registration'].parse_response(_jwt, request_body=_info['body']) assert set(claims.keys()) == { 'id_token_signed_response_alg', 'application_type', 'client_secret', 'client_id', 'response_types', 'token_endpoint_auth_method', 'grant_types', "contacts", 'federation_type'}
def test_collect_entity_statement(self): leaf_entity_id = 'https://foodle.uninett.no' entity_statement = self.fedent.collector.get_entity_statement('', leaf_entity_id, leaf_entity_id) tree = self.fedent.collect_statement_chains(leaf_entity_id, entity_statement) assert tree _node = {leaf_entity_id: (entity_statement, tree)} chains = branch2lists(_node) assert len(chains) == 1 assert len(chains[0]) == 4
def parse_federation_registration_response(self, resp, **kwargs): """ Receives a dynamic client registration response, :param resp: An entity statement instance :return: A set of metadata claims """ _sc = self.service_context _fe = _sc.federation_entity # Can not collect trust chain. Have to verify the signed JWT with keys I have kj = self.service_context.federation_entity.keyjar _jwt = factory(resp) entity_statement = _jwt.verify_compact(resp, keys=kj.get_jwt_verify_keys( _jwt.jwt)) _trust_anchor_id = self.get_trust_anchor_id(entity_statement) chosen = None for op_statement in _fe.op_statements: if op_statement.fo == _trust_anchor_id: chosen = op_statement break if not chosen: raise ValueError('No matching federation operator') # based on the Federation ID, conclude which OP config to use op_claims = chosen.metadata # _sc.trust_path = (chosen.fo, _fe.op_paths[statement.fo][0]) _sc.provider_info = self.response_cls(**op_claims) # To create RPs metadata collect the trust chains tree = {} for ah in _fe.authority_hints: tree[ah] = _fe.collector.collect_intermediate(_fe.entity_id, ah) _node = {_fe.entity_id: (resp, tree)} chains = branch2lists(_node) # Get the policies policy_chains_tup = [ eval_policy_chain(c, _fe.keyjar, _fe.entity_type) for c in chains ] _policy = combine_policy( policy_chains_tup[0][1], entity_statement['metadata_policy'][_fe.entity_type]) logger.debug("Combined policy: {}".format(_policy)) _uev = unverified_entity_statement(kwargs["request_body"]) logger.debug("Registration request: {}".format(_uev)) _query = _uev["metadata"][_fe.entity_type] _sc.registration_response = apply_policy(_query, _policy) return _sc.registration_response
def test_branch2lists_1(): tree = { "https://example.com/rp": ('statement1', { "https://example.com/intermediate1": ('statement2', { "https://example.com/anchor": ("statement3", {}) }) }) } chains = branch2lists(tree) assert len(chains) == 1 assert len(chains[0]) == 3 assert chains[0] == ["statement3", "statement2", "statement1"]
def test_branch2lists_3(): tree = { "https://example.com/rp": ('statement1', { "https://example.com/intermediate1": ('statement2', { "https://example.com/anchor1": ("statement3", {}) }), "https://example.com/intermediate2": ('statement5', { "https://example.com/anchor2": ("statement4", {}) }) }) } chains = branch2lists(tree) assert len(chains) == 2 assert chains[0] == ["statement3", "statement2", "statement1"] assert chains[1] == ["statement4", "statement5", "statement1"]
def collect_metadata_statements(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) # verify the trust paths and apply policies return [eval_chain(c, self.keyjar, metadata_type) for c in _chains]
def parse_federation_registration_response(self, resp, **kwargs): """ Receives a dynamic client registration response, :param resp: An entity statement instance :return: A set of metadata claims """ _context = self.client_get("service_context") _fe = _context.federation_entity _fe_ctx = _fe.context # Can not collect trust chain. Have to verify the signed JWT with keys I have kj = _fe_ctx.keyjar _jwt = factory(resp) entity_statement = _jwt.verify_compact(resp, keys=kj.get_jwt_verify_keys(_jwt.jwt)) _trust_anchor_id = self.get_trust_anchor_id(entity_statement) logger.debug("trust_anchor_id: {}".format(_trust_anchor_id)) chosen = None for op_statement in _fe_ctx.op_statements: if op_statement.anchor == _trust_anchor_id: chosen = op_statement break if not chosen: raise ValueError('No matching federation operator') # based on the Federation ID, conclude which OP config to use op_claims = chosen.metadata logger.debug("OP claims: {}".format(op_claims)) # _sc.trust_path = (chosen.anchor, _fe.op_paths[statement.anchor][0]) _context.provider_info = ProviderConfigurationResponse(**op_claims) # To create RPs metadata collect the trust chains tree = {} for ah in _fe_ctx.authority_hints: tree[ah] = _fe.collector.collect_intermediate(_fe_ctx.entity_id, ah) _node = {_fe_ctx.entity_id: (resp, tree)} chains = branch2lists(_node) logger.debug("%d chains", len(chains)) logger.debug("Evaluate policy chains") # Get the policies policy_chains_tup = [eval_policy_chain(c, _fe_ctx.keyjar, _fe_ctx.entity_type) for c in chains] # Weed out unusable chains policy_chains_tup = [pct for pct in policy_chains_tup if pct is not None] # Should leave me with one. The one ending in the chosen trust anchor. policy_chains_tup = [pct for pct in policy_chains_tup if pct[0] == _trust_anchor_id] if policy_chains_tup == []: logger.warning("No chain that ends in chosen trust anchor (%s)", _trust_anchor_id) raise ValueError("No trust chain that ends in chosen trust anchor (%s)", _trust_anchor_id) _policy = combine_policy(policy_chains_tup[0][1], entity_statement['metadata_policy'][_fe_ctx.entity_type]) logger.debug("Effective policy: {}".format(_policy)) _req = kwargs.get("request") if _req is None: _req = kwargs.get("request_body") _uev = unverified_entity_statement(_req) logger.debug("Registration request: {}".format(_uev)) _query = _uev["metadata"][_fe_ctx.entity_type] _resp = apply_policy(_query, _policy) _context.set("registration_response", _resp) return _resp