feat: link local library

This commit is contained in:
gabdevele
2026-06-17 20:43:56 +02:00 Unverified
parent 9a770a2af0
commit db59882f33
327 changed files with 22274 additions and 1 deletions
@@ -0,0 +1,165 @@
from django.test import Client
from django.urls import reverse
from spid_cie_oidc.authority.models import FederationDescendant
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.entity.jwtse import unpad_jwt_payload, create_jws
from spid_cie_oidc.entity.exceptions import HttpError
from spid_cie_oidc.entity.tests.settings import ta_conf_data
from .settings import rp_onboarding_data, intermediary_conf, rp_conf
import copy
import logging
logger = logging.getLogger(__name__)
class DummyContent:
def __init__(self, content):
self.content = content.encode()
self.status_code = 200
class EntityResponse:
def __init__(self):
self.status_code = 200
self.req_counter = 0
self.client = Client()
self.result = None
def result_as_it_is(self):
logger.info(f"Response #{self.req_counter}: {self.result.content.decode()}")
self.req_counter += 1
return self.result.content
def result_as_jws(self):
logger.info(
f"Response #{self.req_counter}: "
f"{unpad_jwt_payload(self.result.content.decode())}"
)
self.req_counter += 1
return self.result.content
def trust_anchor_ec(self):
url = reverse("entity_configuration")
res = self.client.get(url, data={"sub": ta_conf_data["sub"]})
return res
def rp_ec(self):
rp = FederationEntityConfiguration.objects.get(sub=rp_onboarding_data["sub"])
res = DummyContent(rp.entity_configuration_as_jws)
return res
def fetch_rp_from_ta(self):
url = reverse("oidcfed_fetch")
res = self.client.get(url, data={"sub": rp_onboarding_data["sub"]})
return res
class EntityResponseNoIntermediate(EntityResponse):
@property
def content(self):
if self.req_counter == 0:
self.result = self.trust_anchor_ec()
elif self.req_counter == 1:
self.result = self.rp_ec()
elif self.req_counter == 2:
self.result = self.fetch_rp_from_ta()
elif self.req_counter > 2:
raise NotImplementedError(
"The mocked resposes seems to be not aligned with the correct flow"
)
return self.result_as_jws()
class EntityResponseNoIntermediateSignedJwksUri(EntityResponse):
@property
def content(self):
if self.req_counter == 0:
self.result = self.trust_anchor_ec()
elif self.req_counter == 1:
self.result = self.rp_ec()
elif self.req_counter == 2:
self.result = self.fetch_rp_from_ta()
elif self.req_counter == 3:
metadata = copy.deepcopy(rp_conf['metadata']['openid_relying_party'])
_jwks = metadata.pop('jwks')
fed_jwks = rp_conf['jwks_fed'][0]
self.result = create_jws(_jwks, fed_jwks)
return self.result.encode()
elif self.req_counter > 3:
raise NotImplementedError(
"The mocked resposes seems to be not aligned with the correct flow"
)
return self.result_as_jws()
class EntityResponseWithIntermediate(EntityResponse):
@property
def content(self):
if self.req_counter == 0:
self.result = self.trust_anchor_ec()
elif self.req_counter == 1:
self.result = self.rp_ec()
elif self.req_counter == 2:
sa = FederationEntityConfiguration.objects.get(sub=intermediary_conf["sub"])
self.result = DummyContent(sa.entity_configuration_as_jws)
elif self.req_counter == 3:
# Subordinate statement: intermediate -> RP (not TA -> RP)
rp_desc = FederationDescendant.objects.get(sub=rp_onboarding_data["sub"])
stmt_jws = rp_desc.entity_statement_as_jws(intermediary_conf["sub"])
self.result = DummyContent(stmt_jws)
elif self.req_counter == 4:
url = reverse("oidcfed_fetch")
self.result = self.client.get(url, data={"sub": intermediary_conf["sub"]})
elif self.req_counter == 5:
url = reverse("entity_configuration")
self.result = self.client.get(url, data={"sub": ta_conf_data["sub"]})
elif self.req_counter > 5:
raise NotImplementedError(
"The mocked resposes seems to be not aligned with the correct flow"
)
if self.result.status_code != 200:
raise HttpError(f"Something went wrong with Http Request: {self.result.__dict__}")
logger.info("-------------------------------------------------")
logger.info("")
return self.result_as_jws()
class EntityResponseWithIntermediateManyHints(EntityResponse):
@property
def content(self):
if self.req_counter == 0:
self.result = self.trust_anchor_ec()
elif self.req_counter == 1:
self.result = self.rp_ec()
elif self.req_counter == 2:
sa = FederationEntityConfiguration.objects.get(sub=intermediary_conf["sub"])
self.result = DummyContent(sa.entity_configuration_as_jws)
elif self.req_counter == 3:
# Second authority hint (that-faulty): invalid response
self.result = DummyContent("crap")
elif self.req_counter == 4:
# Subordinate statement: intermediate -> RP
rp_desc = FederationDescendant.objects.get(sub=rp_onboarding_data["sub"])
stmt_jws = rp_desc.entity_statement_as_jws(intermediary_conf["sub"])
self.result = DummyContent(stmt_jws)
elif self.req_counter == 5:
# TA -> intermediate subordinate statement
url = reverse("oidcfed_fetch")
self.result = self.client.get(url, data={"sub": intermediary_conf["sub"]})
elif self.req_counter > 5:
raise NotImplementedError(
"The mocked resposes seems to be not aligned with the correct flow"
)
if self.result.status_code != 200:
raise HttpError(f"Something went wrong with Http Request: {self.result.__dict__}")
try:
return self.result_as_jws()
except:
return self.result_as_it_is()
@@ -0,0 +1,149 @@
from copy import deepcopy
from spid_cie_oidc.entity.jwks import serialize_key, new_rsa_key
INTERMEDIARY_RSA = new_rsa_key()
INTERMEDIARY_JWK1 = serialize_key(INTERMEDIARY_RSA.priv_key, kind='private')
INTERMEDIARY_JWK1_pub = serialize_key(INTERMEDIARY_RSA.pub_key)
RP_METADATA_JWK1 = {'use': 'sig', 'kty': 'RSA', 'n': 'w8H80eT2zrs2XQ-SApZG9TkuXDuIxANfCVHt4fFqNnOEZaCNWqlTQIo0JiSBE-QmzZ09TYP1BJpESuQf_PUeLRVPfYHsBVk5OYvhT27_nYlV7_1LsFGLxxsIa-hswMMzvW-1_huKLy6Fp0WP0ouUJAHsF_eYVtO1ApRhvlIVd5azM4k7t8Lh8lkCSdF1SfGHfXnXJRb-XensZ0cFSfe2Koq9mD7jpGLXlPpXxj8Ow0g7KYT5kVtWE5ULmNmO7BIN1Hx4HpggbbEGgC9FyjKw4GfFb-csnB-icBPf_60HomjrkFFt6vTjrcqQaHOj-sEjP36N8rMSBiMmiMSPnsHhMQ', 'e': 'AQAB', 'd': 'jEDxjcTZXBbgBV8Bgt7-qfW1FJoHDEFKFxhfMpHQQoETa-jTPhCxOD2MzYM8A-9kKc8tu9r-crTAl1PI42kPnMd283phixd5G5Tv8gSaGdnq-45ka0iRuC7TItUdDiMNb_2YzB4ZLGLNmaIKQJSGqCHEcQuRVyxJtTZwrXaMMOhDqJaWUvUQWF5C7g5O5mOVTkNKw6ujzhqcWa4N3NE-HwcbVW_9st4s1c_ng-DlwLTptaeM5j-LOeZMX1zcVlwYMi5ZkYYY6FHHjYI4nBWDtqhvf-64QaTv8exIjk8PcxHOwhfLTWiHPLk14af7U_pCzkP87WQCBgNfvt3WILQ5DQ', 'p': '75eNHkWaYQMgzVfFwif5uftSxqOhFU6VkxNKdqoRuFxJuVTO-M-vbQc3BwPxms2xrpizU6zGcoPGPvccDi0G040wZh34pWDVABMgGMKXKmeTwj8FuM1DzOVq8DKHmdrhk1gaQbPAP8JVOVYK7uh_lG5wmz3X-En1McMk-E8g8Ic', 'q': '0Sny6DLNtDP1_B9qiyCaMtRqPSAUZ1ohCZRlBT6-IGRR31Kt5S2JcVNDnF5w4dunlDY4nhIBZ0v0VyzWKgDXj6qrFY1pm1iE29gW227YsVRWQU8xWGpBwEu8nxNMr0u0zfe0QEGWU4RvNAsZPRa31HU87Vm7I3NSZ34DZsCZJoc', 'kid': 'HIvo33-Km7n03ZqKDJfWVnlFudsW28YhQZx5eaXtAKA'}
RP_METADATA_JWK1_pub = {'use': 'sig', 'kty': 'RSA', 'n': 'w8H80eT2zrs2XQ-SApZG9TkuXDuIxANfCVHt4fFqNnOEZaCNWqlTQIo0JiSBE-QmzZ09TYP1BJpESuQf_PUeLRVPfYHsBVk5OYvhT27_nYlV7_1LsFGLxxsIa-hswMMzvW-1_huKLy6Fp0WP0ouUJAHsF_eYVtO1ApRhvlIVd5azM4k7t8Lh8lkCSdF1SfGHfXnXJRb-XensZ0cFSfe2Koq9mD7jpGLXlPpXxj8Ow0g7KYT5kVtWE5ULmNmO7BIN1Hx4HpggbbEGgC9FyjKw4GfFb-csnB-icBPf_60HomjrkFFt6vTjrcqQaHOj-sEjP36N8rMSBiMmiMSPnsHhMQ', 'e': 'AQAB', 'kid': 'HIvo33-Km7n03ZqKDJfWVnlFudsW28YhQZx5eaXtAKA'}
RP_METADATA_JWK2 = {'alg': 'RSA-OAEP', 'use': 'enc', 'kty': 'RSA', 'n': 'w8H80eT2zrs2XQ-SApZG9TkuXDuIxANfCVHt4fFqNnOEZaCNWqlTQIo0JiSBE-QmzZ09TYP1BJpESuQf_PUeLRVPfYHsBVk5OYvhT27_nYlV7_1LsFGLxxsIa-hswMMzvW-1_huKLy6Fp0WP0ouUJAHsF_eYVtO1ApRhvlIVd5azM4k7t8Lh8lkCSdF1SfGHfXnXJRb-XensZ0cFSfe2Koq9mD7jpGLXlPpXxj8Ow0g7KYT5kVtWE5ULmNmO7BIN1Hx4HpggbbEGgC9FyjKw4GfFb-csnB-icBPf_60HomjrkFFt6vTjrcqQaHOj-sEjP36N8rMSBiMmiMSPnsHhMQ', 'e': 'AQAB', 'd': 'jEDxjcTZXBbgBV8Bgt7-qfW1FJoHDEFKFxhfMpHQQoETa-jTPhCxOD2MzYM8A-9kKc8tu9r-crTAl1PI42kPnMd283phixd5G5Tv8gSaGdnq-45ka0iRuC7TItUdDiMNb_2YzB4ZLGLNmaIKQJSGqCHEcQuRVyxJtTZwrXaMMOhDqJaWUvUQWF5C7g5O5mOVTkNKw6ujzhqcWa4N3NE-HwcbVW_9st4s1c_ng-DlwLTptaeM5j-LOeZMX1zcVlwYMi5ZkYYY6FHHjYI4nBWDtqhvf-64QaTv8exIjk8PcxHOwhfLTWiHPLk14af7U_pCzkP87WQCBgNfvt3WILQ5DQ', 'p': '75eNHkWaYQMgzVfFwif5uftSxqOhFU6VkxNKdqoRuFxJuVTO-M-vbQc3BwPxms2xrpizU6zGcoPGPvccDi0G040wZh34pWDVABMgGMKXKmeTwj8FuM1DzOVq8DKHmdrhk1gaQbPAP8JVOVYK7uh_lG5wmz3X-En1McMk-E8g8Ic', 'q': '0Sny6DLNtDP1_B9qiyCaMtRqPSAUZ1ohCZRlBT6-IGRR31Kt5S2JcVNDnF5w4dunlDY4nhIBZ0v0VyzWKgDXj6qrFY1pm1iE29gW227YsVRWQU8xWGpBwEu8nxNMr0u0zfe0QEGWU4RvNAsZPRa31HU87Vm7I3NSZ34DZsCZJoc', 'kid': 'ENC-Km7n03ZqKDJfWVnlFudsW28YhQZx5eaXtAKA'}
RP_METADATA_JWK2_pub = {'alg': 'RSA-OAEP', 'use': 'enc', 'kty': 'RSA', 'n': 'w8H80eT2zrs2XQ-SApZG9TkuXDuIxANfCVHt4fFqNnOEZaCNWqlTQIo0JiSBE-QmzZ09TYP1BJpESuQf_PUeLRVPfYHsBVk5OYvhT27_nYlV7_1LsFGLxxsIa-hswMMzvW-1_huKLy6Fp0WP0ouUJAHsF_eYVtO1ApRhvlIVd5azM4k7t8Lh8lkCSdF1SfGHfXnXJRb-XensZ0cFSfe2Koq9mD7jpGLXlPpXxj8Ow0g7KYT5kVtWE5ULmNmO7BIN1Hx4HpggbbEGgC9FyjKw4GfFb-csnB-icBPf_60HomjrkFFt6vTjrcqQaHOj-sEjP36N8rMSBiMmiMSPnsHhMQ', 'e': 'AQAB', 'kid': 'ENC-Km7n03ZqKDJfWVnlFudsW28YhQZx5eaXtAKA'}
rp_onboarding_data = dict(
name="RP Test",
sub="http://rp-test.it/oidc/rp/",
type="openid_relying_party",
metadata_policy={"openid_relying_party": {"scope": {"value": ["openid"]}}},
is_active=True,
jwks = [RP_METADATA_JWK1_pub]
)
rp_conf = {
"sub": rp_onboarding_data["sub"],
"jwks_fed" : [RP_METADATA_JWK1],
"jwks_core" : [RP_METADATA_JWK1, RP_METADATA_JWK2],
"metadata": {
"openid_relying_party": {
"application_type": "web",
"client_registration_types": ["automatic"],
"client_name": "Name of this service called http://rp-test.it/oidc/rp/",
"contacts": ["[email protected]"],
"grant_types": ["refresh_token", "authorization_code"],
"redirect_uris": ["http://rp-test.it/oidc/rp/callback/"],
"response_types": ["code"],
"subject_type": "pairwise",
"client_id": "http://rp-test.it/oidc/rp/",
"jwks": {"keys": [RP_METADATA_JWK1_pub, RP_METADATA_JWK2_pub]},
}
},
"authority_hints": ["http://testserver/"],
"is_active": True,
}
RP_CONF_AS_JSON = {
"iss": rp_conf["sub"],
"sub": rp_conf["sub"],
"jwks": {
"keys": [RP_METADATA_JWK1_pub, RP_METADATA_JWK2_pub]
},
"metadata": rp_conf["metadata"],
"authority_hints":rp_conf["authority_hints"]
}
intermediary_conf = {
"sub": "http://intermediary-test",
"metadata": {
"federation_entity": {
"contacts": ["ops@localhost"],
"federation_fetch_endpoint": "http://intermediary-test/fetch",
"homepage_uri": "http://intermediary-test",
"name": "example Intermediate",
}
},
"trust_marks": [],
"authority_hints": ["http://testserver/"],
"is_active": True,
"jwks_core": [INTERMEDIARY_JWK1],
"jwks_fed": [INTERMEDIARY_JWK1]
}
intermediary_onboarding_data = dict(
name="intermediary-test",
sub="http://intermediary-test",
type="federation_entity",
# metadata_policy = {"openid_relying_party": {"scope": {"value": ["openid"]}}},
is_active=True,
jwks = [INTERMEDIARY_JWK1_pub]
)
TRUST_MARK_PAYLOAD = {
"iss": "$.issuer_sub",
"sub": "$.sub",
"iat": 1579621160,
"trust_mark_id": "https://www.spid.gov.it/certification/rp",
"logo_uri": "https://www.agid.gov.it/themes/custom/agid/logo.svg",
"ref": "https://docs.italia.it/italia/spid/spid-regole-tecniche-oidc/it/stabile/index.html",
}
RP_PROFILE = {
"name": "SPID Public SP",
"profile_category": "openid_relying_party",
"profile_id": "https://www.spid.gov.it/certification/rp",
"trust_mark_template": TRUST_MARK_PAYLOAD,
}
RP_METADATA = {
"openid_relying_party": {
"application_type": "web",
"client_registration_types": ["automatic"],
"client_name": f"Name of this service called {rp_onboarding_data['sub']}",
"contacts": ["[email protected]"],
"grant_types": ["refresh_token", "authorization_code"],
"jwks": {"keys": [RP_METADATA_JWK1_pub]},
"redirect_uris": [f"{rp_onboarding_data['sub']}/callback/"],
"response_types": ["code"],
"subject_type": "pairwise",
}
}
TA_SUB = "http://testserver.it/"
FETCH_REQUEST = {
"sub": rp_conf["sub"],
}
LIST_REQUEST = {
"entity_type": "openid_relying_party",
}
TRUST_MARK = "eyJhbGciOiJSUzI1NiIsImtpZCI6IkZpZll4MDNibm9zRDhtNmdZUUlmTkhOUDljTV9TYW05VGM1bkxsb0lJcmMiLCJ0eXAiOiJ0cnVzdC1tYXJrK2p3dCJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvIiwic3ViIjoiaHR0cDovLzEyNy4wLjAuMTo4MDAyL29pZGMvb3AvIiwiaWF0IjoxNjQ4NTcxMTk4LCJpZCI6Imh0dHBzOi8vd3d3LnNwaWQuZ292Lml0L2NlcnRpZmljYXRpb24vb3AiLCJtYXJrIjoiaHR0cHM6Ly93d3cuYWdpZC5nb3YuaXQvdGhlbWVzL2N1c3RvbS9hZ2lkL2xvZ28uc3ZnIiwicmVmIjoiaHR0cHM6Ly9kb2NzLml0YWxpYS5pdC9pdGFsaWEvc3BpZC9zcGlkLXJlZ29sZS10ZWNuaWNoZS1vaWRjL2l0L3N0YWJpbGUvaW5kZXguaHRtbCJ9.mN-AMOmcxOKh9w8SIw9T0N4_wsmjrnMsPp_M2k3irErz0ikrRNtOQ0iPXhJ33szXUxZQJc0jlxu3glCliViv34u6FyAUK-WyVHWf2QKHL5oPbZj45Lx6u1ZMTgU9gKEAUl6MdkGtIEU6F0-q87-BKIKTvJmErH3pdrQG7ed5fR2lvFCN7QQ4rHnVYHVMEnlZxe2oMPtRILsSHaa30yPGCYfp36BpZx9rdmrV0bb8sIsRYFin7GIhkzl4FxqEQ0J6i1A4elgP9tVQsOu1Yd4E54t62KdVtTgfI4qfmP15NoPiNZFHLIFIMsUmqHMh5nuq9KWBb2IC1dfHM6AoxCpqtQ"
TRUST_MARK_REQUEST = {
"sub": rp_conf["sub"],
"trust_mark_id" : rp_conf["sub"],
"trust_mark" : TRUST_MARK
}
TRUST_MARK_REQUEST_NO_SUB_ID = deepcopy(TRUST_MARK_REQUEST)
TRUST_MARK_REQUEST_NO_SUB_ID.pop("sub")
TRUST_MARK_REQUEST_NO_SUB_ID.pop("trust_mark_id")
TRUST_MARK_REQUEST_NO_TRUST_MARK = deepcopy(TRUST_MARK_REQUEST)
TRUST_MARK_REQUEST_NO_TRUST_MARK.pop("trust_mark")
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB = deepcopy(TRUST_MARK_REQUEST)
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB.pop("sub")
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB_NO_TRUST_MARK = deepcopy(TRUST_MARK_REQUEST)
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB_NO_TRUST_MARK.pop("sub")
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB_NO_TRUST_MARK.pop("trust_mark")
TRUST_MARK_REQUEST_TRUST_MARK_NO_ID_NO_TRUST_MARK = deepcopy(TRUST_MARK_REQUEST)
TRUST_MARK_REQUEST_TRUST_MARK_NO_ID_NO_TRUST_MARK.pop("trust_mark_id")
TRUST_MARK_REQUEST_TRUST_MARK_NO_ID_NO_TRUST_MARK.pop("trust_mark")
ADVANCED_LIST_REQUEST = {
"page": 1,
}
@@ -0,0 +1,385 @@
from django.test import TestCase, Client, override_settings
from django.urls import reverse
from spid_cie_oidc.entity.exceptions import InvalidRequiredTrustMark
from spid_cie_oidc.entity.jwtse import verify_jws, unpad_jwt_head, unpad_jwt_payload
from spid_cie_oidc.entity.models import *
from spid_cie_oidc.entity.trust_chain_operations import (
dumps_statements_from_trust_chain_to_db,
get_or_create_trust_chain,
trust_chain_builder,
)
from spid_cie_oidc.entity.statements import (
EntityConfiguration,
get_entity_configurations,
TrustMark,
)
from spid_cie_oidc.entity.utils import get_jwks
from spid_cie_oidc.entity.tests.settings import *
from spid_cie_oidc.authority.models import *
from spid_cie_oidc.authority.tests.mocked_responses import *
from spid_cie_oidc.authority.tests.settings import *
from unittest.mock import patch
import copy
import datetime
class TrustChainTest(TestCase):
def setUp(self):
self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
self.rp_conf = FederationEntityConfiguration.objects.create(**rp_conf)
self.rp_profile = FederationEntityProfile.objects.create(**RP_PROFILE)
self.rp = FederationDescendant.objects.create(**rp_onboarding_data)
self.rp_assigned_profile = FederationEntityAssignedProfile.objects.create(
descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
)
def test_fetch_endpoint(self):
self.rp_profile.__str__()
self.rp_profile.trust_mark_template_as_json
url = reverse("oidcfed_fetch")
c = Client()
res = c.get(url, data={"sub": self.rp.sub})
data = verify_jws(res.content.decode(), self.ta_conf.jwks_fed[0])
self.assertEqual(data['source_endpoint'], 'http://testserver//fetch')
self.assertTrue(data["jwks"])
def test_list_endpoint(self):
url = reverse("oidcfed_list")
c = Client()
res = c.get(url, data={})
self.assertTrue(res.json())
self.assertEqual(res.status_code, 200)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseNoIntermediate())
def test_trust_chain_valid_no_intermediaries(self, mocked):
self.ta_conf.constraints = {"max_path_length": 0}
self.ta_conf.save()
jwt = get_entity_configurations(self.ta_conf.sub)
trust_anchor_ec = EntityConfiguration(jwt[0])
trust_chain = trust_chain_builder(
subject=rp_onboarding_data["sub"],
trust_anchor=trust_anchor_ec
)
self.assertTrue(trust_chain)
self.assertTrue(trust_chain.final_metadata)
for i in trust_chain.tree_of_trust.values():
for ec in i:
self.assertTrue(ec.is_valid)
for ec in trust_chain.trust_path:
self.assertTrue(ec.is_valid)
self.assertTrue(len(trust_chain.trust_path) == 2)
self.assertTrue((len(trust_chain.trust_path) - 2) == trust_chain.max_path_len)
self.assertTrue(isinstance(trust_chain.exp, int))
self.assertTrue(isinstance(trust_chain.exp_datetime, datetime.datetime))
def _create_federation_with_intermediary(self) -> EntityConfiguration:
jwt = get_entity_configurations(self.ta_conf.sub)
trust_anchor_ec = EntityConfiguration(jwt[0])
self.intermediate = FederationEntityConfiguration.objects.create(
**intermediary_conf
)
self.intermediate_desc = FederationDescendant.objects.create(
**intermediary_onboarding_data
)
FederationEntityAssignedProfile.objects.create(
descendant=self.rp, profile=self.rp_profile, issuer=self.intermediate
)
self.rp_conf.authority_hints = [intermediary_conf["sub"]]
self.rp_conf.save()
return trust_anchor_ec
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseWithIntermediate())
def test_trust_chain_valid_with_intermediary(self, mocked):
trust_anchor_ec = self._create_federation_with_intermediary()
trust_chain = trust_chain_builder(
subject=self.rp.sub,
trust_anchor=trust_anchor_ec
)
self.assertTrue(trust_chain)
self.assertTrue(trust_chain.final_metadata)
for i in trust_chain.tree_of_trust.values():
for ec in i:
self.assertTrue(ec.is_valid)
for ec in trust_chain.trust_path:
self.assertTrue(ec.is_valid)
self.assertTrue(len(trust_chain.trust_path) == 3)
self.assertTrue((len(trust_chain.trust_path) - 2) == trust_chain.max_path_len)
tc_ser = trust_chain.serialize()
_p0 = unpad_jwt_payload(tc_ser[0])
_p1 = unpad_jwt_payload(tc_ser[1])
_p2 = unpad_jwt_payload(tc_ser[2])
_p3 = unpad_jwt_payload(tc_ser[3])
# Entity configurations
self.assertEqual(_p0['iss'], _p0['sub'])
self.assertEqual(_p3['iss'], _p3['sub'])
# Entity statements
self.assertNotEqual(_p1['iss'], _p1['sub'])
self.assertNotEqual(_p2['iss'], _p2['sub'])
# Chain consistency (positive)
self.assertEqual(_p0['iss'], _p1['sub'])
self.assertEqual(_p1['iss'], _p2['sub'])
self.assertEqual(_p2['iss'], _p3['sub'])
# Chain consistency (negative)
self.assertNotEqual(_p1['iss'], _p0['sub'])
self.assertNotEqual(_p2['iss'], _p1['sub'])
self.assertNotEqual(_p3['iss'], _p2['sub'])
dumps = dumps_statements_from_trust_chain_to_db(trust_chain)
self.assertTrue(isinstance(dumps, list) and len(dumps) == 5)
self.assertTrue("sub" in dumps[0].get_entity_configuration_as_obj().payload)
stored_trust_chain = TrustChain.objects.create(
sub=trust_chain.subject,
exp=trust_chain.exp_datetime,
chain=trust_chain.serialize(),
jwks = trust_chain.subject_configuration.jwks,
metadata=trust_chain.final_metadata,
parties_involved=[i.sub for i in trust_chain.trust_path],
status="valid",
trust_anchor=FetchedEntityStatement.objects.get(
sub=trust_anchor_ec.sub, iss=trust_anchor_ec.sub
),
is_active=True,
)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseWithIntermediateManyHints())
def test_trust_chain_valid_with_intermediaries_many_authhints(self, mocked):
trust_anchor_ec = self._create_federation_with_intermediary()
self.rp_conf.authority_hints = [
"http://intermediary-test",
"http://that-faulty",
]
self.rp_conf.save()
trust_chain = trust_chain_builder(
subject=self.rp.sub,
trust_anchor=trust_anchor_ec
)
for ec in trust_chain.trust_path:
self.assertTrue(ec.is_valid)
self.assertTrue(len(trust_chain.trust_path) == 3)
self.assertTrue((len(trust_chain.trust_path) - 2) == trust_chain.max_path_len)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseWithIntermediate())
def test_trust_chain_valid_with_intermediaries_and_trust_mark_filter(self, mocked):
trust_anchor_ec = self._create_federation_with_intermediary()
# the RP exposes a trust marks in its entity configuration
self.rp_conf.trust_marks = [
FederationEntityAssignedProfile.objects.filter(descendant=self.rp)
.first()
.trust_mark
]
self.rp_conf.save()
trust_chain = trust_chain_builder(
subject=self.rp.sub,
trust_anchor=trust_anchor_ec,
required_trust_marks=["https://www.spid.gov.it/certification/rp"],
)
for ec in trust_chain.trust_path:
self.assertTrue(ec.is_valid)
self.assertTrue(trust_chain.verified_trust_marks)
self.assertTrue(isinstance(trust_chain.verified_trust_marks[0], TrustMark))
self.assertTrue(len(trust_chain.trust_path) == 3)
self.assertTrue((len(trust_chain.trust_path) - 2) == trust_chain.max_path_len)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseWithIntermediate())
def test_trust_chain_with_missing_trust_mark(self, mocked):
trust_anchor_ec = self._create_federation_with_intermediary()
with self.assertRaises(InvalidRequiredTrustMark):
trust_chain = trust_chain_builder(
subject=self.rp.sub,
trust_anchor=trust_anchor_ec,
required_trust_marks=["https://www.spid.gov.it/certification/rp"],
)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseNoIntermediate())
def test_trust_chain_valid_helpers(self, mocked):
gctc = get_or_create_trust_chain(
subject=rp_conf["sub"],
trust_anchor=self.ta_conf.sub,
# TODO
# required_trust_marks: list = []
)
self.assertFalse(gctc.is_expired)
self.assertTrue(gctc.is_valid)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseNoIntermediate())
def test_resolve_endpoint(self, mocked):
gctc = get_or_create_trust_chain(
subject=rp_conf["sub"],
trust_anchor=self.ta_conf.sub,
# TODO
# required_trust_marks: list = []
)
url = reverse("oidcfed_resolve")
c = Client()
res = c.get(url, data={
"sub": self.rp.sub,
"trust_anchor": self.ta_conf.sub
}
)
self.assertTrue(res.status_code == 200)
verify_jws(res.content.decode(), self.ta_conf.jwks_fed[0])
def test_trust_mark_status_endpoint(self):
url = reverse("oidcfed_trust_mark_status")
c = Client()
# Legacy: sub + trust_mark_id -> 200 + JWT (application/trust-mark-status-response+jwt)
res = c.post(
url,
data={
"trust_mark_id": self.rp_assigned_profile.profile.profile_id,
"sub": self.rp_assigned_profile.descendant.sub,
},
)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.get("Content-Type"), "application/trust-mark-status-response+jwt")
payload = unpad_jwt_payload(res.content.decode())
self.assertEqual(payload.get("status"), "active")
self.assertIn("trust_mark", payload)
self.assertIn("iss", payload)
self.assertIn("iat", payload)
# Draft 48: trust_mark JWT only -> 200 + JWT
res = c.post(
url,
data={"trust_mark": self.rp_assigned_profile.trust_mark["trust_mark"]},
)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.get("Content-Type"), "application/trust-mark-status-response+jwt")
payload = unpad_jwt_payload(res.content.decode())
self.assertEqual(payload.get("status"), "active")
# Legacy: GET with sub + trust_mark_id -> 200 + JWT
res = c.get(
url,
data={
"trust_mark_id": self.rp_assigned_profile.profile.profile_id,
"sub": self.rp_assigned_profile.descendant.sub,
},
)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.get("Content-Type"), "application/trust-mark-status-response+jwt")
# Empty request -> 400 invalid_request
res = c.get(url, data={})
self.assertEqual(res.status_code, 400)
self.assertEqual(res.json().get("error"), "invalid_request")
# Invalid trust_mark JWT -> 400
res = c.get(
url,
data={"trust_mark": self.rp_assigned_profile.trust_mark["trust_mark"][1:]},
)
self.assertEqual(res.status_code, 400)
# trust_mark_id without sub -> 400
res = c.get(
url,
data={"trust_mark_id": self.rp_assigned_profile.profile.profile_id},
)
self.assertEqual(res.status_code, 400)
# Unknown trust mark (wrong sub) -> 404 not_found
res = c.get(
url,
data={
"trust_mark_id": self.rp_assigned_profile.profile.profile_id,
"sub": "https://nonexistent.example/",
},
)
self.assertEqual(res.status_code, 404)
self.assertEqual(res.json().get("error"), "not_found")
class TrustChainWithSignedJwksUriTest(TestCase):
def setUp(self):
self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
_rp_conf = copy.deepcopy(rp_conf)
_rp_conf['metadata']['openid_relying_party'].pop('jwks')
_rp_conf['metadata']['openid_relying_party']['signed_jwks_uri'] = f"{_rp_conf['sub']}signed_jwks.jose"
self.rp_conf = FederationEntityConfiguration.objects.create(**_rp_conf)
self.rp_profile = FederationEntityProfile.objects.create(**RP_PROFILE)
self.rp = FederationDescendant.objects.create(**rp_onboarding_data)
self.rp_assigned_profile = FederationEntityAssignedProfile.objects.create(
descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
)
@override_settings(HTTP_CLIENT_SYNC=True)
@patch("requests.get", return_value=EntityResponseNoIntermediateSignedJwksUri())
def test_signed_jwks_uri(self, mocked):
self.ta_conf.constraints = {"max_path_length": 0}
self.ta_conf.save()
jwt = get_entity_configurations(self.ta_conf.sub)
trust_anchor_ec = EntityConfiguration(jwt[0])
trust_chain = trust_chain_builder(
subject=rp_onboarding_data["sub"],
trust_anchor=trust_anchor_ec
)
_jwks = get_jwks(
trust_chain.final_metadata['openid_relying_party'],
trust_chain.subject_configuration.jwks
)
self.assertTrue(
verify_jws(_jwks, self.rp_conf.jwks_fed[0])
)
@@ -0,0 +1,48 @@
from copy import deepcopy
from unittest.mock import patch
from django.test import TestCase
from spid_cie_oidc.authority.tests.settings import (
RP_CONF_AS_JSON,
RP_METADATA_JWK1
)
from spid_cie_oidc.authority.utils import random_token
from spid_cie_oidc.authority.validators import validate_entity_configuration
from spid_cie_oidc.entity.exceptions import MissingAuthorityHintsClaim, NotDescendant
from spid_cie_oidc.entity.jwtse import create_jws
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.entity.tests.settings import ta_conf_data
JWS = create_jws(RP_CONF_AS_JSON, RP_METADATA_JWK1, typ="entity-statement+jwt")
RP_CONF_AS_JSON_NO_HINTS = deepcopy(RP_CONF_AS_JSON)
RP_CONF_AS_JSON_NO_HINTS.pop("authority_hints")
JWS_NO_HINTS = create_jws(RP_CONF_AS_JSON_NO_HINTS, RP_METADATA_JWK1, typ="entity-statement+jwt")
RP_CONF_AS_JSON_WRONG_HINTS = deepcopy(RP_CONF_AS_JSON)
RP_CONF_AS_JSON_WRONG_HINTS["authority_hints"] = ["http://wrong.url.it"]
JWS_WRONG_HINTS = create_jws(RP_CONF_AS_JSON_WRONG_HINTS, RP_METADATA_JWK1, typ="entity-statement+jwt")
class ValidatorTest(TestCase):
def setUp(self):
self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
@patch("spid_cie_oidc.authority.validators.get_entity_configurations", return_value = [JWS])
def test_validator(self, mocked):
validate_entity_configuration(["url_entity"])
@patch("spid_cie_oidc.authority.validators.get_entity_configurations", return_value = [JWS_NO_HINTS])
def test_validator_no_hints(self, mocked):
with self.assertRaises(MissingAuthorityHintsClaim):
validate_entity_configuration(["url_entity"])
@patch("spid_cie_oidc.authority.validators.get_entity_configurations", return_value = [JWS_WRONG_HINTS])
def test_validator_no_descendant(self, mocked):
with self.assertRaises(NotDescendant):
validate_entity_configuration(["url_entity"])
class UtilityTests(TestCase):
def test_random_token(self):
self.assertTrue(random_token())
@@ -0,0 +1,49 @@
from copy import deepcopy
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from spid_cie_oidc.authority.models import FederationDescendant
from spid_cie_oidc.authority.tests.settings import rp_onboarding_data
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.entity.tests.settings import ta_conf_data
class AdvanceEntityListing(TestCase):
def setUp(self):
FederationDescendant.objects.create(**rp_onboarding_data)
rp_onboarding_data_local = deepcopy(rp_onboarding_data)
rp_onboarding_data_local["sub"] = "http://rp-test.it/oidc/rplocal/"
FederationDescendant.objects.create(**rp_onboarding_data_local)
self.ta = FederationEntityConfiguration.objects.create(**ta_conf_data)
@override_settings(MAX_ENTRIES_PAGE=1)
def test_advanced_entity_listing_1(self):
url = reverse("oidcfed_advanced_entity_listing")
data = {"page":2}
res = Client().get(url, data)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json().get("iss"), "http://testserver/")
self.assertEqual(len(res.json().get("entities")), 2)
self.assertEqual(res.json().get("page"), 2)
url_prev_page = reverse("oidcfed_advanced_entity_listing")
self.assertEqual(res.json().get("prev_page_path"), f"{url_prev_page}?page=1")
@override_settings(MAX_ENTRIES_PAGE=1)
def test_advanced_entity_listing_no_page(self):
url = reverse("oidcfed_advanced_entity_listing")
res = Client().get(url)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json().get("iss"), "http://testserver/")
self.assertEqual(len(res.json().get("entities")), 2)
self.assertEqual(res.json().get("page"), 1)
url_next_page = reverse("oidcfed_advanced_entity_listing")
self.assertEqual(res.json().get("next_page_path"), f"{url_next_page}?page=2")
def test_advanced_entity_listing_missing_trust_anchor(self):
FederationEntityConfiguration.objects.all().delete()
c = Client()
url = reverse("oidcfed_advanced_entity_listing")
res = c.get(url)
self.assertEqual(res.status_code, 404)
self.assertEqual(res.json().get("error"), "Missing trust anchor")
@@ -0,0 +1,51 @@
from django.test import TestCase
from spid_cie_oidc.authority.schemas.advanced_entity_list_endpoint import AdvancedEntityListRequest
from spid_cie_oidc.authority.schemas.fetch_endpoint_request import FetchRequest
from spid_cie_oidc.authority.schemas.list_endpoint import ListRequest
from spid_cie_oidc.authority.schemas.trust_mark_status_endpoint import TrustMarkRequest
from spid_cie_oidc.authority.tests.settings import (
FETCH_REQUEST,
LIST_REQUEST,
TRUST_MARK_REQUEST,
ADVANCED_LIST_REQUEST,
TRUST_MARK_REQUEST_NO_SUB_ID,
TRUST_MARK_REQUEST_NO_TRUST_MARK,
TRUST_MARK_REQUEST_TRUST_MARK_NO_ID_NO_TRUST_MARK,
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB,
TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB_NO_TRUST_MARK,
)
class SchemaTest(TestCase):
def setUp(self) -> None:
return super().setUp()
def test_fetch_request(self):
FetchRequest(**FETCH_REQUEST)
def test_list_request(self):
ListRequest(**LIST_REQUEST)
def test_trust_mark_request(self):
TrustMarkRequest(**TRUST_MARK_REQUEST)
def test_trust_mark_request_no_sub_id(self):
TrustMarkRequest(**TRUST_MARK_REQUEST_NO_SUB_ID)
def test_trust_mark_request_no_rust_mark(self):
TrustMarkRequest(**TRUST_MARK_REQUEST_NO_TRUST_MARK)
def test_trust_mark_request_no_sub(self):
TrustMarkRequest(**TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB)
def test_trust_mark_request_no_id_no_tust_mark(self):
# Schema allows optional fields; view returns 400 if neither (sub+id) nor trust_mark provided
TrustMarkRequest(**TRUST_MARK_REQUEST_TRUST_MARK_NO_ID_NO_TRUST_MARK)
def test_trust_mark_request_no_sub_no_tust_mark(self):
# Schema allows optional fields; view returns 400 if neither (sub+id) nor trust_mark provided
TrustMarkRequest(**TRUST_MARK_REQUEST_TRUST_MARK_NO_SUB_NO_TRUST_MARK)
def test_advanced_list_request(self):
AdvancedEntityListRequest(**ADVANCED_LIST_REQUEST)