italia__spid-cie-oidc-django METASEP

examples/federation_authority/templates/admin/change_form.html METASEP
{% extends "admin/change_form.html" %}
{% block admin_change_form_document_ready %}
{{ block.super }}
<script>
Array.from(document.querySelectorAll('div.readonly')).forEach(div => {
    let data;
    try {
        data = JSON.parse(div.innerText);
    } catch {
        // Not valid JSON
        return;
    }
    div.style.whiteSpace = 'pre-wrap';
    div.style.fontFamily = 'courier';
    div.style.fontSize = '0.9em';
    div.innerText = JSON.stringify(data, null, 2);
});
</script>
{% endblock %}

examples/relying_party/dumps/example.json METASEP
[{"model": "spid_cie_oidc_entity.federationentityconfiguration", "pk": 1, "fields": {"created": "2022-02-06T17:25:43.158Z", "modified": "2022-02-06T17:30:34.676Z", "uuid": "10b07ffc-5d22-4625-b5b9-d710d0b9a1de", "sub": "http://127.0.0.1:8001/", "default_exp": 33, "default_signature_alg": "RS256", "authority_hints": ["http://127.0.0.1:8000/"], "jwks": [{"kty": "RSA", "kid": "2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk", "n": "1cE1PyQiBkmwO4TT30HGUwegdPZ9iKvuwQezUYOe8LqGol_6sUgxAf67_KbAeP1PMrmGH6d-AgNIT2Taa0OAtqyRUTLhG8rl7gT3_Jzwt2mOJu2JI4MfWcQxa-ZtzM8PPr7JUzWUzhHO7Nb1MfBm_fqB20cRcHOS_fvu3PqY-C-t33z3JYeDD_PsvSs2-WLlUiMDf9ILp0rVatF4GTPwvEp7VLCqCf1lLSLIxuuTVI0sc1j3xPbyv4MO_33fTmoAOVDmkUnhi2igLgw_tjc2-iu_4-r2qsKbotGiTu6y1RQrHc8xcrQmfqJ8FUwqAcpgQnlsekEHc6lWx9262Anobw", "e": "AQAB", "d": "hVFCz4XOvtM42nZm2skgU-re1fRiqVpqx_PwkHeiKwx7Kkzog6e-ATuKrfKm-w8qVT3G_Hkvqzu23uXNILdyUPziiZ1NL1NmAwUQUnrj29E2-uqN7Rs783XdSSnn86R1IrJFyOMTU09iGu-a8DG7Ws7iLQxvQQ6kcEqwWRznSWr3nsy5n1sYX6-JLL8QDkGxVMpOuJAccIWjRLNkNsOSbJ89t3G4dN5gBI5gbscwQsAKpDciDenzc1Qnlvj8ZCJzJWGY4Yab8dKeLyvEgpMiEVLBCrN-Ln9YSpjWyr46KQN0bYWbF_9VrZWJtC2dgYJH9Mne67jvqx9veLtmzwDhYQ", "p": "6pt7rTLJscDkbK6bpME4kZnDCv49y8g5mC2iCxnYZ-MLq3eKYClJlb6ysXD7qF8xRwRJAiKjnfiy6PvfzkSbHzrItfmffjTMW-U0uwj8F_lK1P0h2B_Z0bJ6PVELmvQWHSXveDYZ0q5UmhA3hhL9pIq4Qav3dGUiVknMhkDDAV8", "q": "6T70dA3H1XZq9BCJ134OQkS7HOhTGg7EDiujcsVgRkP2cN_9f5EU-zw3-yUc_HBX8rW0P65jVLGFeBI05WJsnFchRLImErF6ChcQWcd-cb2z6mj_CcZKsX0LiN5qV-KcVIvOQixioFd9WsdJs9PYWr9T3gFWhhxa-1sSBCgZIvE"}], "trust_marks": [], "trust_marks_issuers": {}, "metadata": {"application_type": "web", "client_registration_types": ["automatic"], "client_name": "Name of this service called https://rp.example.it/spid", "contacts": ["ops@rp.example.it"], "grant_types": ["refresh_token", "authorization_code"], "jwks": {"kty": "RSA", "n": "1cE1PyQiBkmwO4TT30HGUwegdPZ9iKvuwQezUYOe8LqGol_6sUgxAf67_KbAeP1PMrmGH6d-AgNIT2Taa0OAtqyRUTLhG8rl7gT3_Jzwt2mOJu2JI4MfWcQxa-ZtzM8PPr7JUzWUzhHO7Nb1MfBm_fqB20cRcHOS_fvu3PqY-C-t33z3JYeDD_PsvSs2-WLlUiMDf9ILp0rVatF4GTPwvEp7VLCqCf1lLSLIxuuTVI0sc1j3xPbyv4MO_33fTmoAOVDmkUnhi2igLgw_tjc2-iu_4-r2qsKbotGiTu6y1RQrHc8xcrQmfqJ8FUwqAcpgQnlsekEHc6lWx9262Anobw", "e": "AQAB", "kid": "2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk"}, "redirect_uris": ["https://rp.example.it/spid/callback"], "response_types": ["code"], "subject_type": "pairwise"}, "is_active": true}}]

examples/federation_authority/logs/README.md METASEP

examples/federation_authority/federation_authority/settingslocal.py.example METASEP
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# SECURITY WARNING: keep the secret key used in production secret!
# CHANGE THIS VALUE
SECRET_KEY = 'lrx(fg&+2e=$l=y8$!£$%$/£%412345234534543--345234-dfdfgsdf-myg%r3z!yjm(lg*l%-z'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# the path corresponding the admin backend, default if not defined: admin/
# CHANGE THIS VALUE
ADMIN_PATH = 'admin/'

# required for onboarding checks
FEDERATION_TRUST_ANCHOR = "http://localhost:8000"
FEDERATION_DEFAULT_METADATA_POLICY = {
    "openid_relying_party": {
      # TODO: to be customized for each entities, not somethinf to default!
      # "client_id": {"value":  "https://rp.example.it/spid"},
      # "redirect_uris": {
        # "subset_of": [
          # "https://rp.example.it/spid/cb1", 
          # "https://rp.example.it/spid/cb2"
        # ]
      # },
      # "organization_name": {"value": "Example RP organization name"},
      # "logo_uri": {
         # "one_of": [
            # "https://rp.example.it/logo_small.jpg",
            # "https://rp.example.it/logo_big.jpg"
         # ],
         # "default": "https://rp.example.it/logo_small.jpg"
      # },
       # "policy_uri": {
         # "value": "https://rp.example.it/policy.html"
       # },
       # "tos_uri": {
         # "value": "https://rp.example.it/tos.html"
      # },
      "grant_types": {
        "subset_of": [
          "authorization_code",
          "refresh_token"
         ]
      },
      "scopes": {
        "superset_of": ["openid"],
        "subset_of": ["openid", "offline_access"]
    },

    # TODO: to be customized for each actor
    # "constraints": {
      # "naming_constraints": {
        # "permitted": [
          # "https://rp.example.it"
      # ]
    # },

    # defines how many intermediaries are allowed to this trust anchor
    "max_path_length": 1
  },

  # TODO: TBD
  "openid_provider": {},
  "federation_entity": {},
  "oauth_resource": {}
  
}


ALLOWED_HOSTS = ['*']
AUTH_USER_MODEL = 'spid_cie_oidc_accounts.User'

INSTALLED_APPS = [
    'spid_cie_oidc.accounts',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'prettyjson',
    'spid_cie_oidc.entity',
    'spid_cie_oidc.onboarding',
    'spid_cie_oidc.relying_party'
]

DATABASES = {
    # if you need more power
    # 'default': {
    # 'ENGINE': 'django.db.backends.mysql',
    # 'NAME': 'that-username',
    # 'HOST': 'localhost',
    # 'USER': 'that-user',
    # 'PASSWORD': 'that-password',
    # 'PORT': '',
    # 'OPTIONS': {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}
    # },
    'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'it-it'
TIME_ZONE = 'Europe/Rome'
USE_I18N = True
USE_L10N = True
USE_TZ = True

DATA_UPLOAD_MAX_NUMBER_FIELDS = 65000

# email notification on errors
# DEFAULT_FROM_EMAIL = 'fedauth-noreply@DOMAIN'
# SERVER_EMAIL = DEFAULT_FROM_EMAIL
# EMAIL_HOST = 'smtpservice.yourdomain.it'
# EMAIL_HOST_USER = 'myemail@hotmail.com'
# EMAIL_HOST_PASSWORD = 'mypassword'
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True

ADMINS = [('name surname', 'user1@DOMAIN'),
          ('name surnale', 'user2@DOMAIN'),]

OIDCFED_FEDERATION_TRUST_ANCHORS = ["http://127.0.0.1:8000/"]
OIDCFED_FEDERATION_TRUST_MARKS_PROFILES = {
    "openid_relying_party__public": {},
    "openid_relying_party__private": {},

}

# LOGGING
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'default': {
            # exact format is not important, this is the minimum information
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
        },
        'detailed': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s [%(pathname)s %(funcName)s:%(lineno)s]'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'formatter': 'detailed',
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'console': {
            'formatter': 'default',
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
        "daily": {
          "class": "logging.handlers.TimedRotatingFileHandler",
          "level": "INFO",
          "formatter": "default",
          "filename": f"{BASE_DIR}/logs/trust_anchor.log",
          "when": "midnight",
          "backupCount": 96
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'spid_cie_oidc': {
            'handlers': ['console', 'mail_admins', "daily"],
            'level': 'INFO',
            'propagate': False,
        }
    }
}

examples/federation_authority/dumps/example.json METASEP
[{"model": "spid_cie_oidc_entity.federationentityconfiguration", "pk": 1, "fields": {"created": "2022-02-05T22:04:49.310Z", "modified": "2022-02-13T23:11:26.222Z", "uuid": "afd47731-7484-427f-a1db-4e470835fa86", "sub": "http://127.0.0.1:8000/", "default_exp": 33, "default_signature_alg": "RS256", "authority_hints": [], "jwks": [{"kty": "RSA", "kid": "FifYx03bnosD8m6gYQIfNHNP9cM_Sam9Tc5nLloIIrc", "e": "AQAB", "n": "3i5vV-_4nF_ES1BU86Zf2Bj6SiyGdGM3Izc2GrvtknQQCzpT3QlGv2d_wMrzVTS7PmZlvjyi2Qceq8EmEwbsIa5R8G57fxSpE0HL33giJfhpe8ublY4hGb6tEqSbHiFcgiF4T-Ft_98pz4nZtKTcesMZ8CcDUd9ibaLXGM4vaiUhSt76X1qOzqJHqAKMG-9VGm5DD2GSe7cu1yvaMCMPU6DGOqHYoBSkSbsnLelsRg6sINh6mZfb39odTJlOMFGhlg665702kc_iqqxd8jpyOh94vBagmJB4EQqI1qEte8sTMeBkVRpSLDoV5uNTlp2ZdINu1SakmaHB3WeStwC1lw", "d": "QvPRP7mjvFOrjlp9zxJyzWbxfYqfVdFUGzuXBUVeWQS6lPeVsAUMmb8xo0JFQ4bpaetne4VAOZBIsM86jv9GBvxF2uMgOfJa5N-t9QB5oeGSv-hiURYMaXqpIvYRfGnnO5ukasXu5O0150GOJj6L5j6GwXSwLmrXeVxZ3zK63QwVl71xU1LR-lO0wLbqQROIT37Jw72B__wBk3QC0HjbrPv1fUVxKB3RCDR43X7PQkMPOfRHxicyp2MA4mLhLvuoRTTI4dfnd8Ou-xX5ctVzYmL0EMxPCleDFDIn9gTxpgCH95sVi-Zg6Zw5k1J_cchoD4AgGSSt2dr9mbiTRjLlcQ", "p": "8BHX7hErQjESybgfzcX0hZmM-e1EWaM76uNJop9BiqRlBz9f-XxuC40A032AaZFDXqxVi3W0Hn1vJA6lSj9mGY5HEY-YVWAdOLLjM12oQ_cnH6czElExAoppUeMWsDEewDbZTn6rX5silcZ8Pu7Tsj-KSjPVzl9dr1w76EzsYj8", "q": "7Oy3PGm3MjVlgTlgHnRKC-IcoB50hCBiqwACVcnlIgpg9Kt_srV7NWdmo5DJFIdrrvkjmN4wi9IOknSymStU-sB8BepnnterjPyBOr9PbttUP13qcOjuvjzD7Tr0IGou3yhA-YOuO9hOluhqd4tJIkdxT_X9qxgFQx5NSnsBpqk"}], "trust_marks": [], "trust_marks_issuers": {"https://www.spid.gov.it/certification/rp/public": ["https://registry.spid.agid.gov.it", "https://public.intermediary.spid.it"], "https://www.spid.gov.it/certification/rp/private": ["https://registry.spid.agid.gov.it", "https://private.other.intermediary.it"], "https://sgd.aa.it/onboarding": ["https://sgd.aa.it"]}, "metadata": {"federation_entity": {"contacts": ["ops@localhost"], "federation_api_endpoint": "http://localhost:8000/fetch", "homepage_uri": "http://localhost:8000", "name": "example TA"}}, "constraints": {"max_path_length": 1}, "is_active": true}}, {"model": "spid_cie_oidc_entity.federationentityconfiguration", "pk": 2, "fields": {"created": "2022-02-14T23:44:47.487Z", "modified": "2022-02-14T23:49:12.069Z", "uuid": "780373bb-7b4e-4511-8fef-ca920d66addb", "sub": "http://127.0.0.1:8000/oidc/rp/", "default_exp": 2880, "default_signature_alg": "RS256", "authority_hints": ["http://127.0.0.1:8000/"], "jwks": [{"kty": "RSA", "kid": "2HnoFS3YnC9tjiCaivhWLVUJ3AxwGGz_98uRFaqMEEs", "n": "5s4qi1Ta-sEuKb5rJ8TzHmyGKaSu89pIXIi6w4Ekx6GL56mJDNE_MWJHsFjWXajfMdMQmZrSXAvLtXxmbhUui9Mq_IormhmEyyEJS0SyE9UKTxWzi0yd_n_C7OjFBhM-0ZyUlgl81E_sr-35P1A6b5WSYwMvRSR-P9yx_NI-XBQ48G_zdmk3CbuuzZsXZqqgj5U7OGWH-4Huosn9nH3FVkwX0OlWkgWM-J9DEWzGBjl9hfbbrMtM_obljHL2NfT6RJYER2IpdI8RCyQS3sMPt6ZHDskmuNlyMDNATCChXQJLnltwEjxcgvzjw_G9J25DwfdfVEhDF_0kCp44UMmS3Q", "e": "AQAB", "d": "kXg7xFmVMxpy2AiWTRiLCw_nd3O-eR-JIBllbTeGUPR202o9YQC5TYzeFj3HznxTQHoBKm80SqN8n0Rq4tMi5SoRG96SIKwY0FZgHzqK6okJ2FKbOR7vLaqk1uDW3T9gBokj9XTBYqeFTFU3FTqhuhaRjypArtmTYPjejbSNbUZ29r2UjlMY92y-w4-IVDD9cWlI5I75QA1iWrmPF2t80uk9qqZFde8ZwWsvqqJym-I-x7T34SfMVhJQPbts6VzsUFAUZbT6kVKuUzffSdnr-QeQgj2dR-ULjcN3Y_M-6oc_n25Cz_xFgv3_3hLveizP6inooBzyhgTD1nlR7cWNgQ", "p": "_XLjTNHt1OfGbaHae50Sm_C4dWp_fNNt1__cUNJ62bxJgFG3KlnwbRWtztXGe-BThMyMXK1HHjjUCf66FQGmfoDYTe7qz9j0OBctKMdkoAjTTYZdOYXu3G9U3HddnB-6bnd8fNnKidGAiseWi1eCoViEqeZ7cVULeOlI-ZOB500", "q": "6SDtrFt50EL37kgcaKVttxXjz7JbanH5q-mLgV_tzBcdjEeg1lSv9-7pRDPNedF7KD-FsaT-2YSrPrc2F8z6_aKE7M_TCUv-m2LdLbvvB0iqO_kOjkdd1v9I-3qOq3Yvvd_SYTb81uAFTEFeRXoE7sLINOCO8ClCWa95nEFOMdE"}], "trust_marks": [], "trust_marks_issuers": {}, "metadata": {"openid_relying_party": {"scopes": {"superset_of": ["openid"], "subset_of": ["openid", "offline_access", "profile", "email"]}, "contacts": {"add": ["ciao@email.it"]}}}, "constraints": {}, "is_active": true}}, {"model": "spid_cie_oidc_entity.publicjwk", "pk": 1, "fields": {"created": "2022-02-12T17:56:40.330Z", "modified": "2022-02-12T17:56:40.330Z", "kid": "2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk", "jwk": {"kty": "RSA", "n": "1cE1PyQiBkmwO4TT30HGUwegdPZ9iKvuwQezUYOe8LqGol_6sUgxAf67_KbAeP1PMrmGH6d-AgNIT2Taa0OAtqyRUTLhG8rl7gT3_Jzwt2mOJu2JI4MfWcQxa-ZtzM8PPr7JUzWUzhHO7Nb1MfBm_fqB20cRcHOS_fvu3PqY-C-t33z3JYeDD_PsvSs2-WLlUiMDf9ILp0rVatF4GTPwvEp7VLCqCf1lLSLIxuuTVI0sc1j3xPbyv4MO_33fTmoAOVDmkUnhi2igLgw_tjc2-iu_4-r2qsKbotGiTu6y1RQrHc8xcrQmfqJ8FUwqAcpgQnlsekEHc6lWx9262Anobw", "e": "AQAB", "kid": "2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk"}}}, {"model": "spid_cie_oidc_onboarding.federationentityprofile", "pk": 1, "fields": {"created": "2022-02-10T17:47:04.952Z", "modified": "2022-02-10T17:47:04.952Z", "name": "SPID Public SP", "profile_category": "openid_relying_party", "profile_id": "https://www.spid.gov.it/openid-federation/agreement/sp-public/", "trust_mark_template": {"iss": "$.issuer_sub", "sub": "$.sub", "iat": 1579621160, "id": "https://www.spid.gov.it/certification/rp", "mark": "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"}}}, {"model": "spid_cie_oidc_onboarding.federationdescendant", "pk": 1, "fields": {"created": "2022-02-06T18:01:01.998Z", "modified": "2022-02-18T00:48:45.258Z", "uid": "autouid-b0caa9d7-da0e-442f-a613-8623acf9d0c1", "name": "Test Relying Party", "sub": "http://127.0.0.1:8000/oidc/rp/", "type": "openid_relying_party", "metadata_policy": {"openid_relying_party": {"scopes": {"superset_of": ["openid"], "subset_of": ["openid", "offline_access", "profile", "email"]}}}, "constraints": {}, "extended_claims": {}, "status": "unknown", "status_log": {}, "is_active": true, "registrant": []}}, {"model": "spid_cie_oidc_onboarding.federationdescendantjwk", "pk": 1, "fields": {"created": "2022-02-12T17:56:45.228Z", "modified": "2022-02-12T17:56:45.228Z", "descendant": 1, "jwk": 1}}, {"model": "spid_cie_oidc_onboarding.federationentityassignedprofile", "pk": 1, "fields": {"created": "2022-02-10T17:56:54.482Z", "modified": "2022-02-10T17:56:54.482Z", "descendant": 1, "profile": 1, "issuer": 1}}, {"model": "spid_cie_oidc_onboarding.federationdescendantcontact", "pk": 1, "fields": {"created": "2022-02-13T09:15:26.335Z", "modified": "2022-02-13T09:15:26.335Z", "entity": 1, "contact": "ciao@email.it", "type": "email"}}]
examples/relying_party/relying_party/wsgi.py METASEP
"""
WSGI config for relying_party project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'relying_party.settings')

application = get_wsgi_application()

examples/relying_party/relying_party/urls.py METASEP
"""acsia_agent_proj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve

from spid_cie_oidc.entity.urls import urlpatterns as ta_urlpatterns
from spid_cie_oidc.relying_party.urls import urlpatterns as rp_urlpatterns

admin.site.site_header = "OIDC Federation Relying Party Administration"
admin.site.site_title = "OIDC Federation Relying Party"
admin.site.index_title = "Welcome to OIDC Federation Relying Party Admin backend"

ADMIN_PATH = getattr(settings, 'ADMIN_PATH', 'admin/')

urlpatterns = [
    path(f"{ADMIN_PATH}", admin.site.urls),
    re_path('^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}),
]

urlpatterns.extend(ta_urlpatterns)
urlpatterns.extend(rp_urlpatterns)

examples/relying_party/relying_party/settings.py METASEP
"""
Django settings for relying_party project.

Generated by 'django-admin startproject' using Django 4.0.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-i8roniee9u)x4l@mf)hxp+v5$$9(&l2-to($3i#*s$&vdm_t5w'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']
AUTH_USER_MODEL = 'spid_cie_oidc_accounts.User'

# Application definition

INSTALLED_APPS = [
    'spid_cie_oidc.accounts',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'spid_cie_oidc.entity',
    'spid_cie_oidc.relying_party'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'relying_party.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'relying_party.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'it-it'
TIME_ZONE = 'Europe/Rome'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

FEDERATION_TRUST_ANCHOR = "http://localhost:8000"

examples/relying_party/relying_party/asgi.py METASEP
"""
ASGI config for relying_party project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'relying_party.settings')

application = get_asgi_application()

examples/relying_party/relying_party/__init__.py METASEP

examples/provider/provider/wsgi.py METASEP
"""
WSGI config for provider project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'provider.settings')

application = get_wsgi_application()

examples/provider/provider/urls.py METASEP
"""provider URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

examples/provider/provider/settings.py METASEP
"""
Django settings for provider project.

Generated by 'django-admin startproject' using Django 4.0.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-m0r77dh0g^35=_c&_r$y9#=c@_*u4m($ir=8)(j)$9j!o=*1nj'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']
AUTH_USER_MODEL = 'spid_cie_oidc_accounts.User'

# Application definition

INSTALLED_APPS = [
    'spid_cie_oidc.accounts',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'spid_cie_oidc.entity',
    'spid_cie_oidc.provider'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'provider.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'provider.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'it-it'
TIME_ZONE = 'Europe/Rome'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

examples/provider/provider/asgi.py METASEP
"""
ASGI config for provider project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'provider.settings')

application = get_asgi_application()

examples/provider/provider/__init__.py METASEP

examples/federation_authority/federation_authority/wsgi.py METASEP
"""
WSGI config for example project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'federation_authority.settings')

application = get_wsgi_application()

examples/federation_authority/federation_authority/urls.py METASEP
"""acsia_agent_proj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve

from spid_cie_oidc.entity.urls import urlpatterns as entity_urlpatterns
from spid_cie_oidc.onboarding.urls import urlpatterns as ta_urlpatterns

admin.site.site_header = "OIDC Federation Entity Administration"
admin.site.site_title = "OIDC Federation"
admin.site.index_title = "Welcome to OIDC Federation Entity Admin backend"

ADMIN_PATH = getattr(settings, 'ADMIN_PATH', 'admin/')

urlpatterns = [
    path(f"{ADMIN_PATH}", admin.site.urls),
    re_path('^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}),
]

urlpatterns.extend(entity_urlpatterns)
urlpatterns.extend(ta_urlpatterns)

if 'spid_cie_oidc.relying_party' in settings.INSTALLED_APPS:
    from spid_cie_oidc.relying_party.urls import urlpatterns as rp_urlpatterns
    urlpatterns.extend(rp_urlpatterns)

    from spid_cie_oidc.entity.views import entity_configuration

    urlpatterns.extend(
        [
            path(
                f"oidc/rp/.well-known/openid-federation",
                entity_configuration,
                name="rp_entity_configuration",
            ),
        ]
    )
    

examples/federation_authority/federation_authority/settings.py METASEP
"""
Django settings for example project.

Generated by 'django-admin startproject' using Django 4.0.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

from pathlib import Path
from .settingslocal import *  # noqa: F401

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'federation_authority.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'federation_authority.wsgi.application'

# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

examples/federation_authority/federation_authority/asgi.py METASEP
"""
ASGI config for example project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'federation_authority.settings')

application = get_asgi_application()

examples/federation_authority/federation_authority/__init__.py METASEP

docs/technical_specifications/RELYING_PARTY.md METASEP
# Openid Connect Relying Party Relying Party

A SPID/CIE implementation of a OpenID Connect Relying Party fully compliant to
AgID SPID guidelines and CIE id guidelines.

## Endpoints

### entity configuration (.well-known/openid-federation)

As inherited from [__spid_cie_oidc.entity__](docs/tecnhical_specifications/ENTITY.md).

### authorization

WiP

### auth code redirect

WiP

### logout

WiP

## SPID/CIE QAD and compliances tests

WiP

docs/technical_specifications/PROVIDER.md METASEP
# __Openid Connect Provider__ Identity Provider with additional test suite.

A SPID/CIE implementation of a OpenID Connect Provider fully compliant to
AgID SPID guidelines and CIE id guidelines.

## Endpoints

the webpath where the provider serve its features are the followins.

### entity configuration (.well-known/openid-federation)

As inherited from [__spid_cie_oidc.entity__](docs/tecnhical_specifications/ENTITY.md).

### authorization

WiP

### token

WiP

### token introspection

WiP

### token revocation

WiP

### userinfo endpoint

WiP


## SPID/CIE QAD and compliances tests

WiP

docs/technical_specifications/ONBOARDING.md METASEP
# OIDC Federation 1.0 onboarding service DEMO

> ⚠️ This application is only intended for demostration purpose and not for production use.


## General OnBoarding registration flow

The actions to accredit an entity within the demo federation are described below.

### Entity Registration

1. the legal representative of an entity/org accesses via SPID or CIE
   - __demo__ web resource `/onboarding/landing/`
   - __warning__ this onboarding demo comes with an unprotected registration page.
   The SPID and CIE authentication won't be involved in this demo.

2. becomes recognizable as an org affiliate and is given privileges to operate on
behalf of that org for the registration of an entity, if not already present within the federation.


3. The user accesses the submission form of a federation entity, fill in the following REQUIRED fields:
    1. Organization Name (String)
    2. unique identifier of the entity (url) of the instance in production/stage (URL)
        1. validators:
            - download of the Entity Configuration, a self-signed statement
            - JWS signature validation
            - structure analysis of the JSON (json-schema)
            - presence and validation of the claim  __authority_hints__ that MUST matches to the onboarding system entity (trust anchor)
    3. IPA code or VAT number, that determines whether the participant is public or private (String)
        1. validators:
            - TODO: fetch and verify the IPA code from the national registry of public services
    4. public jwks (List[JSON])
        1. validators:
            - check if the jwks is public and not private (for sake!)
            - check of the presence of kid claim
            - check that the certificate is not expired
            - check that the kid is unique and of the lenght of a minimum JWK thumbprint length
    5. at least one administrative email contact (String)
        1. validators:
            - Email Field
    6. at least one administrative email contact (String)
        1. validators:
            - Email Field
    7. URL where the RP page shows the SPID and CIE authentication button
        1. validators:
            - Null, just for demo purpose
    8. SPID/CIE authentication request trigger url at the onboarding system testing OP
        1. validators:
            - Null, just for demo purpose

### SPID/CIE QaD tests

Once the submission passes the initial checks the request is saved and a batch will start the
Automatic checks on the latter new registered entity. These check covers

- entity configuration:
    - reachability
    - signature validation
    - best practices checks following AgID and IPZS OIDC Fed guidelines.

- RP authz check following AgID and IPZS OIDC Fed guidelines.
- trust marks forgery (if present for recurrent sample checks)

The tests produces a json report.
this report would be also available in HTML format.

### Entity OnBoarding

- all the required information are moved (copied) from the OnBoardin registration storage to the FederationDescendant storage
- trust marks is automatically generated for the available profile, public or private
- the onboarding staff in any time can add more profiles and trust marks  through the FederationDescendant back office panel
- an entity configuration with trust marks and metadata policy applied, signed by the Trust Anchor of the onboarding system, is automatically created and published to its __Resolve Entity Statement__ endpoint.
- an email is sent to the user to inform on the succesfull state of the onboarding and the final entity configuration


### Considerations

1. it would be necessary to "regulate" how the entities must expose a trigger url of the authn request.
This would allow us to batch automate testing and obtain asynchronous reports and request states.
Automatically a warning email would be sent to users.

2. It would be useful to standardize a "quota" to avoid that "the usual primers"
can make too many stupid requests all at once, that is, max N batch testing can be performed per day for a single applicant.

docs/technical_specifications/ENTITY.md METASEP
# OIDC Federation 1.0 Entity

A Federation Entity is a service that meets the minimum requirements to integrate into OIDC Federation 1.0. This can be of the following types:

- Trust Anchor or Intermediary
- Relying Party
- Provider


## Trust Anchor or Intermediary

Thins kind of Entity represents a trusted third party 
and below it must have other entities for which it provides an onboarding mechanism.

### Endpoints:

All the endpoints are configured in the `urls.py` file of the project folder.

#### entity configuration

It's an endpoint that by default corresponds to this webpath: __.well-known/openid-federation__.
This provides the Entity Configuration in signed JWT format.
This endpoint MUST be available for __trust anchors__, __providers__ and __relying parties__.

You can manage additional custom paths for your descendants if you publish the Entity Configuration on their behalf.

`?format=json` will release a json for debug purpose. A prefix can be configured in global settings file with parameter `OIDC_PREFIX`.

Examples of requests to this endpoint are:

- `http://127.0.0.1:8000/.well-known/openid-federation?format=json`
- `http://127.0.0.1:8000/.well-known/openid-federation`

#### fetch

All entities that are expected to publish entity statements about other entities MUST expose a Fetch endpoint.

Fetching entity statements is performed to collect entity statements one by one to gather trust chains.

To fetch an entity statement, an entity needs to know the identifier of the entity to ask (the issuer),
the fetch endpoint of that entity and the identifier of the entity that you want the statement to be about (the subject).

Example of FETCH request

- `http://127.0.0.1:8000/fetch/?sub=http://127.0.0.1:8000/oidc/rp/`
- `http://127.0.0.1:8000/fetch/?sub=http://127.0.0.1:8001/&format=json`

#### listing

As described in the official specification
[here](https://openid.net/specs/openid-connect-federation-1_0.html#rfc.section.7.3.1).
It should be available only for __trust anchors__ and __intermediates__.

Lists all the descendant entities.

 - `http://127.0.0.1:8000/list/`
 - `http://127.0.0.1:8000/list/?is_leaf=false`
 - `http://127.0.0.1:8000/list/?is_leaf=true`

#### resolve entity statement

WiP

#### trust mark status

WiP

examples/relying_party/manage.py METASEP
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'relying_party.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

examples/provider/manage.py METASEP
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'provider.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

examples/federation_authority/manage.py METASEP
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'federation_authority.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

spid_cie_oidc/relying_party/templates/echo_attributes.html METASEP
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  </head>
  <body>
    <h1>OIDC attributes</h1>
    <dl>
      {% for attribute, value in oidc_rp_user_attrs.items %}
      <dt>{{ attribute }}:</dt>
      <dd>{{ value }}</dd>
      {% endfor %}
    </dl>

    <p><a href="{% url 'spid_oidc_rp:spid_oidc_rpinitiated_logout' %}">Log out</a></p>
  </body>
</html>

docs/FEDERATION_METADATA_POLICY.md METASEP
# Federation Metadata Policy

We use [Roland's implementation](https://github.com/rohe/fedservice/blob/master/src/fedservice/entity_statement/policy.py) for appling metadata policy and retrieve a final metadata.

#### Example
Enter in your project shell
````
./manage.py shell
````

Then

````
from spid_cie_oidc.entity.policy import apply_policy

metadata = {
    "application_type": "web",
    "client_registration_types": ["automatic"],
    "client_name": "Name of this service called https://rp.example.it/spid",
    "contacts": ["ops@rp.example.it"],
    "grant_types": ["refresh_token", "authorization_code"],
    "jwks": {
        "kty": "RSA",
        "n": "1cE1PyQiBkmwO4TT30HGUwegdPZ9iKvuwQezUYOe8LqGol_6sUgxAf67_KbAeP1PMrmGH6d-AgNIT2Taa0OAtqyRUTLhG8rl7gT3_Jzwt2mOJu2JI4MfWcQxa-ZtzM8PPr7JUzWUzhHO7Nb1MfBm_fqB20cRcHOS_fvu3PqY-C-t33z3JYeDD_PsvSs2-WLlUiMDf9ILp0rVatF4GTPwvEp7VLCqCf1lLSLIxuuTVI0sc1j3xPbyv4MO_33fTmoAOVDmkUnhi2igLgw_tjc2-iu_4-r2qsKbotGiTu6y1RQrHc8xcrQmfqJ8FUwqAcpgQnlsekEHc6lWx9262Anobw",
        "e": "AQAB",
        "kid": "2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk",
    },
    "redirect_uris": ["https://rp.example.it/spid/callback"],
    "response_types": ["code"],
    "subject_type": "pairwise",
}

policy = {
    "scopes": {
        "default": ["openid"],
        "superset_of": ["openid"],
        "subset_of": ["openid", "offline_access", "profile", "email"],
    },
    "contacts": {"add": ["ciao@email.it"]},
}

apply_policy(metadata, policy)
````

`apply_policy()` returns the final metadata, as follows

````
{'application_type': 'web',
 'client_registration_types': ['automatic'],
 'client_name': 'Name of this service called https://rp.example.it/spid',
 'contacts': ['ops@rp.example.it', 'ciao@email.it'],
 'grant_types': ['refresh_token', 'authorization_code'],
 'jwks': {'kty': 'RSA',
  'n': '1cE1PyQiBkmwO4TT30HGUwegdPZ9iKvuwQezUYOe8LqGol_6sUgxAf67_KbAeP1PMrmGH6d-AgNIT2Taa0OAtqyRUTLhG8rl7gT3_Jzwt2mOJu2JI4MfWcQxa-ZtzM8PPr7JUzWUzhHO7Nb1MfBm_fqB20cRcHOS_fvu3PqY-C-t33z3JYeDD_PsvSs2-WLlUiMDf9ILp0rVatF4GTPwvEp7VLCqCf1lLSLIxuuTVI0sc1j3xPbyv4MO_33fTmoAOVDmkUnhi2igLgw_tjc2-iu_4-r2qsKbotGiTu6y1RQrHc8xcrQmfqJ8FUwqAcpgQnlsekEHc6lWx9262Anobw',
  'e': 'AQAB',
  'kid': '2C3zbeQjgx3jk-CHSqK3pLhdPeV9Fn5eSBPMNUp7vQk'},
 'redirect_uris': ['https://rp.example.it/spid/callback'],
 'response_types': ['code'],
 'subject_type': 'pairwise',
 'scopes': ['openid']}
````

Another example with OpenID Connect Provider
````
md = {
  "authorization_endpoint":"https://idp.it/openid/authorization",
  "token_endpoint": "https://idp.it/openid/token",
  "userinfo_endpoint": "https://idp.it/openid/userinfo",
  "introspection_endpoint": "https://idp.it/openid/introspection",
  "claims_parameter_supported": True,
  "contacts": ["ops@https://idp.it"],
  "client_registration_types_supported": ["automatic"],
  "request_authentication_methods_supported": {
     "ar": [
       "request_object"
  ]},
  "acr_values_supported": [
     "https://www.spid.gov.it/SpidL1",
     "https://www.spid.gov.it/SpidL2",
     "https://www.spid.gov.it/SpidL3"
   ],
  "claims_supported": [
      "https://attributes.spid.gov.it/spidCode",
      "https://attributes.spid.gov.it/name",
      "https://attributes.spid.gov.it/familyName",
      "https://attributes.spid.gov.it/placeOfBirth",
      "https://attributes.spid.gov.it/countyOfBirth",
      "https://attributes.spid.gov.it/dateOfBirth",
      "https://attributes.spid.gov.it/gender",
      "https://attributes.spid.gov.it/companyName",
      "https://attributes.spid.gov.it/registeredOffice",
      "https://attributes.spid.gov.it/fiscalNumber",
      "https://attributes.spid.gov.it/ivaCode",
      "https://attributes.spid.gov.it/idCard",
      "https://attributes.spid.gov.it/mobilePhone",
      "https://attributes.spid.gov.it/email",
      "https://attributes.spid.gov.it/address",
      "https://attributes.spid.gov.it/expirationDate",
      "https://attributes.spid.gov.it/digitalAddress"
   ],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256",
    "ES256"
  ],
  "issuer": "https://idp.it",
  "jwks_uri": "https://idp.it/openid/jwks.json",
 "scopes_supported": [
   "openid",
   "offline_access"
 ],
  "logo_uri":  "https://idp.it/statics/logo.svg",
  "organization_name": "SPID OIDC identity provider",
  "op_policy_uri":  "https://idp.it/en/website/legal-information/",
  "request_parameter_supported": True,
  "request_uri_parameter_supported": True,
  "require_request_uri_registration": True,
  "response_types_supported": ["code"],
  "subject_types_supported": ["pairwise", "public"],
  "token_endpoint_auth_methods_supported": ["private_key_jwt"],
          "token_endpoint_auth_signing_alg_values_supported": [
            "RS256",
            "RS384",
            "RS512",
            "ES256",
            "ES384",
            "ES512"
          ],
          "userinfo_encryption_alg_values_supported": [
            "RSA-OAEP",
            "RSA-OAEP-256",
            "ECDH-ES",
            "ECDH-ES+A128KW",
            "ECDH-ES+A192KW",
            "ECDH-ES+A256KW"
          ],
         "userinfo_encryption_enc_values_supported": [
            "A128CBC-HS256",
            "A192CBC-HS384",
            "A256CBC-HS512",
            "A128GCM",
            "A192GCM",
            "A256GCM"
          ],
         "userinfo_signing_alg_values_supported": [
            "RS256",
            "RS384",
            "RS512",
            "ES256",
            "ES384",
            "ES512"
         ],
"request_object_encryption_alg_values_supported": [
    "RSA-OAEP",
    "RSA-OAEP-256",
    "ECDH-ES",
    "ECDH-ES+A128KW",
    "ECDH-ES+A192KW",
    "ECDH-ES+A256KW"
   ],
 "request_object_encryption_enc_values_supported": [
    "A128CBC-HS256",
    "A192CBC-HS384",
    "A256CBC-HS512",
    "A128GCM",
    "A192GCM",
    "A256GCM"
  ],
  "request_object_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "ES256",
    "ES384",
    "ES512"
  ]
}

policy = {'contacts': {'add': ['ops@idp.it']},
   'op_name': 'SPID OIDC Identity Provider',
   'organization_name': {'value': 'SPID OIDC Identity Provider'},
   'subject_types_supported': {'subset_of': ['pairwise']}}

apply_policy(md, policy)

````

docs/FEDERATION_ENTITY_API.md METASEP
# Federation Entity API

Retrieve all the RPs
````
FederationDescendant.objects.filter(
    metadata__openid_relying_party__isnull=False
)
````

spid_cie_oidc/relying_party/oidc/__init__.py METASEP
import json
import logging
import requests


logger = logging.getLogger(__name__)


class OidcUserInfo(object):
    """
    https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
    """

    def get_userinfo(
        self, state: str, access_token: str, provider_conf: dict, verify: bool
    ):
        """
        User Info endpoint request with bearer access token
        """
        # userinfo
        headers = {"Authorization": f"Bearer {access_token}"}
        authz_userinfo = requests.get(
            provider_conf["userinfo_endpoint"], headers=headers, verify=verify
        )
        if authz_userinfo.status_code != 200:  # pragma: no cover
            logger.error(f"Something went wrong with {state}: {authz_userinfo.content}")
            return False
        else:
            try:
                authz_userinfo = json.loads(authz_userinfo.content.decode())
                logger.debug(f"Userinfo endpoint result: {authz_userinfo}")
                return authz_userinfo
            except Exception as e:  # pragma: no cover
                logger.error(f"Something went wrong with {state}: {e}")
                return False

spid_cie_oidc/relying_party/oauth2/__init__.py METASEP
import json
import logging
import requests

from requests.auth import HTTPBasicAuth


logger = logging.getLogger(__name__)


class OAuth2AuthorizationCodeGrant(object):
    """
    https://tools.ietf.org/html/rfc6749#section-1.3.1
    https://tools.ietf.org/html/rfc6749#section-4.1
    """

    def get_token_endpoint_auth_method(
        self, client_conf: dict, grant_data: dict = {}, method: str = None
    ):
        method = (
            method or client_conf["client_preferences"]["token_endpoint_auth_method"][0]
        )

        if all((method == "client_secret_basic", "client_secret" in client_conf)):
            auth = HTTPBasicAuth(client_conf["client_id"], client_conf["client_secret"])
            data = {"auth": auth, "data": grant_data}
            return data
        elif method == "client_secret_post":
            grant_data.update(
                {
                    "client_id": client_conf["client_id"],
                    "client_secret": client_conf["client_secret"],
                }
            )
            return grant_data

    def access_token_request(
        self,
        redirect_uri: str,
        client_id: str,
        state: str,
        code: str,
        issuer_id: str,
        client_conf: dict,
        token_endpoint_url: str,
        code_verifier: str = None,
    ):
        """
        Access Token Request
        https://tools.ietf.org/html/rfc6749#section-4.1.3
        """
        grant_data = dict(
            grant_type="authorization_code",
            redirect_uri=redirect_uri,
            client_id=client_id,
            state=state,
            code=code,
        )

        if code_verifier:
            grant_data.update({"code_verifier": code_verifier})

        issuer_id = issuer_id
        token_req_data = self.get_token_endpoint_auth_method(client_conf, grant_data)
        logger.debug(f"Access Token Request for {state}: {token_req_data} ")

        token_request = requests.post(
            token_endpoint_url,
            **token_req_data,
            verify=client_conf["httpc_params"]["verify"],
        )

        if token_request.status_code != 200:
            logger.error(f"Something went wrong with {state}: {token_request.content}")
        else:
            try:
                token_request = json.loads(token_request.content.decode())
                return token_request
            except Exception as e:  # pragma: no cover
                logger.error(f"Something went wrong with {state}: {e}")
        return token_request

spid_cie_oidc/relying_party/migrations/__init__.py METASEP

spid_cie_oidc/relying_party/migrations/0001_initial.py METASEP
# Generated by Django 4.0.2 on 2022-02-13 09:06

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name="OidcAuthentication",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("client_id", models.CharField(max_length=255)),
                (
                    "state",
                    models.CharField(
                        default="state-is-unique", max_length=255, unique=True
                    ),
                ),
                ("endpoint", models.URLField(blank=True, null=True)),
                ("issuer", models.CharField(blank=True, max_length=255, null=True)),
                ("issuer_id", models.CharField(blank=True, max_length=255, null=True)),
                ("provider_jwks", models.TextField(blank=True, null=True)),
                ("data", models.TextField(blank=True, null=True)),
                ("successful", models.BooleanField(default=False)),
                ("provider_configuration", models.TextField(blank=True, null=True)),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
            ],
            options={
                "verbose_name": "OIDC Authentication",
                "verbose_name_plural": "OIDC Authentications",
            },
        ),
        migrations.CreateModel(
            name="OidcAuthenticationToken",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("code", models.CharField(blank=True, max_length=255, null=True)),
                ("access_token", models.TextField(blank=True, null=True)),
                ("id_token", models.TextField(blank=True, null=True)),
                ("scope", models.CharField(blank=True, max_length=255, null=True)),
                ("token_type", models.CharField(blank=True, max_length=255, null=True)),
                ("expires_in", models.IntegerField(blank=True, null=True)),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                ("logged_out", models.DateTimeField(blank=True, null=True)),
                (
                    "authz_request",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_relying_party.oidcauthentication",
                    ),
                ),
                (
                    "user",
                    models.ForeignKey(
                        blank=True,
                        null=True,
                        on_delete=django.db.models.deletion.SET_NULL,
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
        ),
    ]

spid_cie_oidc/provider/migrations/__init__.py METASEP

spid_cie_oidc/onboarding/tests/mocked_responses.py METASEP
from django.test import Client
from django.urls import reverse

from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.entity.jwtse import unpad_jwt_payload
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

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_jws(self):
        self.req_counter += 1
        logger.info(unpad_jwt_payload(self.result.content.decode()))
        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 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:
            url = reverse('oidcfed_fetch')
            self.result = self.client.get(url,
                data={'sub': rp_onboarding_data['sub'], 'iss': intermediary_conf['sub']}
            )
        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: {res.__dict__}"
            )
        
        return self.result_as_jws()

spid_cie_oidc/onboarding/tests/__init__.py METASEP

spid_cie_oidc/onboarding/migrations/__init__.py METASEP

spid_cie_oidc/onboarding/migrations/0001_initial.py METASEP
# Generated by Django 4.0.2 on 2022-02-13 09:06

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import spid_cie_oidc.onboarding.models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("spid_cie_oidc_entity", "0001_initial"),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name="FederationDescendant",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "uid",
                    models.CharField(
                        default=spid_cie_oidc.onboarding.models.FederationDescendant.def_uid,
                        help_text="an unique code that identifies this entry. For italian public service it may be the IPA code.",
                        max_length=1024,
                        unique=True,
                    ),
                ),
                (
                    "name",
                    models.CharField(
                        help_text="human readable name of this entity. It may be a unit or organization name",
                        max_length=33,
                    ),
                ),
                (
                    "sub",
                    models.URLField(
                        help_text="URL that identifies this Entity in the Federation.",
                        max_length=255,
                        unique=True,
                        validators=[
                            spid_cie_oidc.onboarding.models.FederationDescendant.validate_entity_configuration
                        ],
                    ),
                ),
                (
                    "type",
                    models.CharField(
                        blank=True,
                        choices=[
                            ("federation_entity", "federation_entity"),
                            ("openid_relying_party", "openid_relying_party"),
                            ("openid_provider", "openid_provider"),
                            ("oauth_resource", "oauth_resource"),
                        ],
                        default="openid_relying_party",
                        help_text="OpenID Connect Federation entity type",
                        max_length=33,
                    ),
                ),
                (
                    "metadata_policy",
                    models.JSONField(
                        blank=True,
                        default=dict,
                        help_text="if present overloads the DEFAULT policy",
                    ),
                ),
                (
                    "constraints",
                    models.JSONField(
                        blank=True,
                        default=dict,
                        help_text="if present overloads the DEFAULT policy",
                    ),
                ),
                (
                    "extended_claims",
                    models.JSONField(
                        blank=True,
                        default=dict,
                        help_text="a dictionary containing any other claim like: jti, crti, policy_language_crit and any other extension",
                    ),
                ),
                (
                    "status",
                    models.CharField(
                        choices=[
                            ("unreachable", "unreachable"),
                            ("valid", "valid"),
                            ("signature_failed", "signature_failed"),
                            ("not_valid", "not_valid"),
                            ("unknown", "unknown"),
                            ("expired", "expired"),
                        ],
                        default=False,
                        help_text="Its entity configuration is periodically fetched and validated.",
                        max_length=33,
                    ),
                ),
                (
                    "status_log",
                    models.JSONField(blank=True, default=dict, help_text="status log"),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=False, help_text="If this entity is active. "
                    ),
                ),
                (
                    "registrant",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Logged in users can modify only sub, contacts and jwks attributes, if they're owner of this entry. ",
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Entity Descendant",
                "verbose_name_plural": "Federation Entity Descendants",
            },
        ),
        migrations.CreateModel(
            name="FederationEntityProfile",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                ("name", models.CharField(help_text="Profile name. ", max_length=33)),
                (
                    "profile_category",
                    models.CharField(
                        choices=[
                            ("federation_entity", "federation_entity"),
                            ("openid_relying_party", "openid_relying_party"),
                            ("openid_provider", "openid_provider"),
                            ("oauth_resource", "oauth_resource"),
                        ],
                        help_text="Profile id. It SHOULD be a URL but feel free to put whatever",
                        max_length=64,
                    ),
                ),
                (
                    "profile_id",
                    models.CharField(
                        help_text="Profile id. It SHOULD be a URL but feel free to put whatever",
                        max_length=1024,
                        unique=True,
                    ),
                ),
                (
                    "trust_mark_template",
                    models.JSONField(
                        blank=True,
                        default=dict,
                        help_text="trust marks template for this profile",
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Entity Profile",
                "verbose_name_plural": "Federation Entity Profiles",
            },
        ),
        migrations.CreateModel(
            name="FederationEntityAssignedProfile",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "descendant",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_onboarding.federationdescendant",
                    ),
                ),
                (
                    "issuer",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_entity.federationentityconfiguration",
                    ),
                ),
                (
                    "profile",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_onboarding.federationentityprofile",
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Entity Descendant Assigned Profile",
                "verbose_name_plural": "Federation Descendant Assigned Profiles",
            },
        ),
        migrations.CreateModel(
            name="FederationDescendantJwk",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "descendant",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_onboarding.federationdescendant",
                    ),
                ),
                (
                    "jwk",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_entity.publicjwk",
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Descendant Public JWK",
                "verbose_name_plural": "Federation Descendant Public JWKs",
            },
        ),
        migrations.CreateModel(
            name="FederationDescendantContact",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "contact",
                    models.CharField(
                        help_text="any kind of contact type, usually an email.",
                        max_length=255,
                    ),
                ),
                (
                    "type",
                    models.CharField(
                        choices=[
                            ("email", "email"),
                            ("telephone", "telephone"),
                            ("other", "other"),
                        ],
                        max_length=255,
                    ),
                ),
                (
                    "entity",
                    models.ForeignKey(
                        help_text="Entity for which this contac is related",
                        on_delete=django.db.models.deletion.CASCADE,
                        to="spid_cie_oidc_onboarding.federationdescendant",
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Entity Contact",
                "verbose_name_plural": "Federation Entity Contacts",
            },
        ),
    ]

spid_cie_oidc/entity/tests/test_01_entity_configuration.py METASEP
from django.contrib.auth import get_user_model
from django.test import TestCase, Client
from django.urls import reverse

from spid_cie_oidc.entity.models import *
from spid_cie_oidc.entity.jwtse import verify_jws

from . import get_admin_change_view_url
from .settings import *


class EntityConfigurationTest(TestCase):
    def setUp(self):
        self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
        self.admin_user = get_user_model().objects.create_superuser(
            username="superuser", password="secret", email="admin@example.com"
        )

    def test_entity_configuration_admin(self):
        c = Client()
        c.login(username="superuser", password="secret")

        # run test
        response = c.get(get_admin_change_view_url(self.ta_conf))
        self.assertEqual(response.status_code, 200)

    def test_entity_configuration(self):
        self.assertTrue(
            isinstance(
                FederationEntityConfiguration.get_active_conf(),
                FederationEntityConfiguration,
            )
        )
        self.assertTrue(
            isinstance(self.ta_conf, FederationEntityConfiguration)
        )
        self.assertFalse(self.ta_conf.is_leaf)

        for i in (
            "public_jwks",
            "pems_as_json",
            "kids",
            "type",
            "entity_configuration_as_jws",
        ):
            attr = getattr(self.ta_conf, i)
            self.assertTrue(attr)

        md = self.ta_conf.entity_configuration_as_dict["metadata"]["federation_entity"]
        for i in ("federation_api_endpoint",):
            self.assertTrue(md.get(i))

        # dulcis in fundo -> test the .well-knwon/openid-federation
        wk_url = reverse("entity_configuration")
        c = Client()
        res = c.get(wk_url)
        verify_jws(res.content.decode(), self.ta_conf.jwks[0])
        
    

spid_cie_oidc/entity/tests/settings.py METASEP

TA_SUB = "http://testserver/"
FA_METADATA = {
    "federation_entity": {
        "contacts": ["ops@localhost"],
        "federation_api_endpoint": f"{TA_SUB}/fetch",
        "homepage_uri": f"{TA_SUB}",
        "name": "example TA",
    }
}
TM_ISSUERS = {
    "https://www.spid.gov.it/certification/rp/public": [
        TA_SUB,
        "https://public.intermediary.spid.it",
    ],
    "https://www.spid.gov.it/certification/rp/private": [
        TA_SUB,
        "https://private.other.intermediary.it",
    ],
    "https://sgd.aa.it/onboarding": ["https://sgd.aa.it"],
}
FA_CONSTRAINTS = {"max_path_length": 1}

ta_conf_data = dict(
    sub=TA_SUB,
    metadata=FA_METADATA,
    constraints=FA_CONSTRAINTS,
    is_active=1,
    trust_marks_issuers=TM_ISSUERS
)

spid_cie_oidc/entity/tests/__init__.py METASEP
from django.urls import reverse


def get_admin_change_view_url(obj: object) -> str:
    return reverse(
        "admin:{}_{}_change".format(obj._meta.app_label, type(obj).__name__.lower()),
        args=(obj.pk,),
    )

spid_cie_oidc/entity/migrations/__init__.py METASEP

spid_cie_oidc/entity/migrations/0005_alter_federationentityconfiguration_constraints.py METASEP
# Generated by Django 4.0.2 on 2022-02-14 23:45

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('spid_cie_oidc_entity', '0004_alter_federationentityconfiguration_authority_hints_and_more'),
    ]

    operations = [
        migrations.AlterField(
            model_name='federationentityconfiguration',
            name='constraints',
            field=models.JSONField(blank=True, default=dict, help_text='\n{\n  "naming_constraints": {\n    "permitted": [\n      "https://.example.com"\n    ],\n    "excluded": [\n      "https://east.example.com"\n    ]\n  },\n  "max_path_length": 2\n}\n'),
        ),
    ]

spid_cie_oidc/entity/migrations/0004_alter_federationentityconfiguration_authority_hints_and_more.py METASEP
# Generated by Django 4.0.2 on 2022-02-14 15:16

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ("spid_cie_oidc_entity", "0003_federationentityconfiguration_constraints"),
    ]

    operations = [
        migrations.AlterField(
            model_name="federationentityconfiguration",
            name="authority_hints",
            field=models.JSONField(
                blank=True,
                default=list,
                help_text="only required if this Entity is an intermediary or leaf.",
            ),
        ),
        migrations.AlterField(
            model_name="federationentityconfiguration",
            name="default_exp",
            field=models.PositiveIntegerField(
                default=2880,
                help_text="how many minutes from now() an issued statement must expire",
            ),
        ),
    ]

spid_cie_oidc/entity/migrations/0003_federationentityconfiguration_constraints.py METASEP
# Generated by Django 4.0.2 on 2022-02-13 23:02

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ("spid_cie_oidc_entity", "0002_fetchedentitystatement"),
    ]

    operations = [
        migrations.AddField(
            model_name="federationentityconfiguration",
            name="constraints",
            field=models.JSONField(
                default=dict,
                help_text='\n{\n  "naming_constraints": {\n    "permitted": [\n      "https://.example.com"\n    ],\n    "excluded": [\n      "https://east.example.com"\n    ]\n  },\n  "max_path_length": 2\n}\n',
            ),
        ),
    ]

spid_cie_oidc/entity/migrations/0002_fetchedentitystatement.py METASEP
# Generated by Django 4.0.2 on 2022-02-13 11:33

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ("spid_cie_oidc_entity", "0001_initial"),
    ]

    operations = [
        migrations.CreateModel(
            name="FetchedEntityStatement",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "iss",
                    models.URLField(
                        help_text="URL that identifies the issuer of this statement in the Federation. ",
                        max_length=255,
                    ),
                ),
                (
                    "sub",
                    models.URLField(
                        help_text="URL that identifies this Entity in the Federation. ",
                        max_length=255,
                    ),
                ),
                ("exp", models.DateTimeField()),
                ("iat", models.DateTimeField()),
                (
                    "statement",
                    models.JSONField(default=dict, help_text="Entity statement"),
                ),
            ],
            options={
                "verbose_name": "Fetched Entity Statement",
                "verbose_name_plural": "Fetched Entity Statement",
            },
        ),
    ]

spid_cie_oidc/entity/migrations/0001_initial.py METASEP
# Generated by Django 4.0.2 on 2022-02-13 09:06

from django.db import migrations, models
import spid_cie_oidc.entity.models
import spid_cie_oidc.entity.validators
import uuid


class Migration(migrations.Migration):

    initial = True

    dependencies = []

    operations = [
        migrations.CreateModel(
            name="FederationEntityConfiguration",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "uuid",
                    models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
                ),
                (
                    "sub",
                    models.URLField(
                        help_text="URL that identifies this Entity in the Federation. This value and iss are the same in the Entity Configuration.",
                        max_length=255,
                    ),
                ),
                (
                    "default_exp",
                    models.PositiveIntegerField(
                        default=33,
                        help_text="how many minutes from now() an issued statement must expire",
                    ),
                ),
                (
                    "default_signature_alg",
                    models.CharField(
                        default="RS256",
                        help_text="default signature algorithm, eg: RS256",
                        max_length=16,
                    ),
                ),
                (
                    "authority_hints",
                    models.JSONField(
                        blank=True,
                        default=list,
                        help_text="only required if this Entity is an intermediary",
                    ),
                ),
                (
                    "jwks",
                    models.JSONField(
                        default=spid_cie_oidc.entity.models.FederationEntityConfiguration._create_jwks,
                        help_text="a list of public keys",
                    ),
                ),
                (
                    "trust_marks",
                    models.JSONField(
                        blank=True,
                        default=list,
                        help_text="which trust marks MUST be exposed in its entity configuration",
                    ),
                ),
                (
                    "trust_marks_issuers",
                    models.JSONField(
                        blank=True,
                        default=dict,
                        help_text='Only usable for Trust Anchors and intermediates. Which issuers are allowed to issue trust marks for the descendants. Example: {"https://www.spid.gov.it/certification/rp": ["https://registry.spid.agid.gov.it", "https://intermediary.spid.it"],"https://sgd.aa.it/onboarding": ["https://sgd.aa.it", ]}',
                    ),
                ),
                (
                    "metadata",
                    models.JSONField(
                        default=dict,
                        help_text='federation_entity metadata, eg: {"federation_entity": { ... },"openid_provider": { ... },"openid_relying_party": { ... },"oauth_resource": { ... }}',
                        validators=[
                            spid_cie_oidc.entity.models.FederationEntityConfiguration.validate_entity_metadata
                        ],
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=False,
                        help_text="If this configuration is active. At least one configuration must be active",
                    ),
                ),
            ],
            options={
                "verbose_name": "Federation Entity Configuration",
                "verbose_name_plural": "Federation Entity Configurations",
            },
        ),
        migrations.CreateModel(
            name="PublicJwk",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "kid",
                    models.CharField(
                        help_text="unique code that identifies this jwks. ",
                        max_length=1024,
                        unique=True,
                    ),
                ),
                (
                    "jwk",
                    models.JSONField(
                        default=dict,
                        help_text="Public jwk",
                        validators=[
                            spid_cie_oidc.entity.validators.validate_public_jwks
                        ],
                    ),
                ),
            ],
            options={
                "verbose_name": "Public JWKs",
                "verbose_name_plural": "Public JWKs",
            },
        ),
        migrations.CreateModel(
            name="TrustChain",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("created", models.DateTimeField(auto_now_add=True)),
                ("modified", models.DateTimeField(auto_now=True)),
                (
                    "sub",
                    models.URLField(
                        help_text="URL that identifies this Entity in the Federation. ",
                        max_length=255,
                    ),
                ),
                (
                    "type",
                    models.CharField(
                        blank=True,
                        choices=[
                            ("federation_entity", "federation_entity"),
                            ("openid_relying_party", "openid_relying_party"),
                            ("openid_provider", "openid_provider"),
                            ("oauth_resource", "oauth_resource"),
                        ],
                        default="openid_provider",
                        help_text="OpenID Connect Federation entity type",
                        max_length=33,
                    ),
                ),
                ("exp", models.DateTimeField()),
                ("iat", models.DateTimeField()),
                (
                    "chain",
                    models.JSONField(
                        blank=True,
                        default=list,
                        help_text="A list of entity statements collected during the metadata discovery",
                    ),
                ),
                (
                    "resultant_metadata",
                    models.JSONField(
                        default=dict,
                        help_text="The final metadata applied with the metadata policy built over the chain",
                    ),
                ),
                (
                    "parties_involved",
                    models.JSONField(
                        blank=True,
                        default=list,
                        help_text="subjects involved in the metadata discovery",
                    ),
                ),
                (
                    "status",
                    models.CharField(
                        choices=[
                            ("unreachable", "unreachable"),
                            ("valid", "valid"),
                            ("signature_failed", "signature_failed"),
                            ("not_valid", "not_valid"),
                            ("unknown", "unknown"),
                            ("expired", "expired"),
                        ],
                        default=False,
                        help_text="Status of this trust chain, on each update.",
                        max_length=33,
                    ),
                ),
                (
                    "status_log",
                    models.JSONField(blank=True, default=dict, help_text="status log"),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="If you need to disable the trust to this subject, deacticate this",
                    ),
                ),
            ],
            options={
                "verbose_name": "Trust Chain",
                "verbose_name_plural": "Trust Chains",
                "unique_together": {("sub", "type")},
            },
        ),
    ]

spid_cie_oidc/accounts/templatetags/has_group.py METASEP
from django import template
from django.contrib.auth.models import Group

register = template.Library()


@register.filter(name="has_group")
def has_group(user, group_name):  # pragma: no cover
    group = Group.objects.get(name=group_name)
    return group in user.groups.all()

spid_cie_oidc/accounts/templatetags/__init__.py METASEP

spid_cie_oidc/accounts/migrations/__init__.py METASEP

spid_cie_oidc/accounts/migrations/0001_initial.py METASEP
# Generated by Django 4.0.2 on 2022-02-06 15:45

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("password", models.CharField(max_length=128, verbose_name="password")),
                (
                    "last_login",
                    models.DateTimeField(
                        blank=True, null=True, verbose_name="last login"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=96, null=True, verbose_name="Name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=96, null=True, verbose_name="Surname"
                    ),
                ),
                ("is_active", models.BooleanField(default=True, verbose_name="active")),
                (
                    "email",
                    models.EmailField(
                        blank=True,
                        max_length=254,
                        null=True,
                        verbose_name="email address",
                    ),
                ),
                (
                    "taxpayer_id",
                    models.CharField(
                        blank=True,
                        max_length=32,
                        null=True,
                        verbose_name="Taxpayer's identification number",
                    ),
                ),
                (
                    "origin",
                    models.CharField(
                        blank=True,
                        max_length=254,
                        null=True,
                        verbose_name="from which connector this user come from",
                    ),
                ),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.Group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.Permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name_plural": "Users",
                "ordering": ["username"],
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
    ]

requirements-dev.txt METASEP
autoflake
autopep8
coverage
black
flake8
isort
bandit

linting.sh METASEP
#!/bin/bash

autopep8 -r --in-place spid_cie_oidc
autoflake -r --in-place  --remove-unused-variables --expand-star-imports --remove-all-unused-imports spid_cie_oidc

flake8 spid_cie_oidc --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 spid_cie_oidc --max-line-length 120 --count --statistics

README.md METASEP
# SPID/CIE OIDC Federation SDKs

![CI build](https://github.com/peppelinux/spid-cie-oidc-django/workflows/spid_cie_oidc/badge.svg)
![Python version](https://img.shields.io/badge/license-Apache%202-blue.svg)
![py-versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue.svg)

> ⚠️ __This project is a WiP, please wait for the first stable release v0.6.0.__

SPID/CIE OIDC Federation is a suite of Django applications designed to
make it easy to build an [Openid Connect Federation](https://openid.net/specs/openid-connect-federation-1_0.html), 
each of these can be installed separately within a django project, these are:

- __spid_cie_oidc.accounts__: customizable app that extended the Django User model.
- __spid_cie_oidc.entity__: OIDC Federation django app, with models and API that implements OIDC Federation 1.0 Entity Statements, metadata discovery, Trust Chain, Trust Marks and Metadata policy.
- __spid_cie_oidc.onboarding__: OIDC Federation OnBoarding demo application.
- __spid_cie_oidc.relying_party__: OIDC Relying Party and test suite for OIDC Providers.
- __spid_cie_oidc.provider__: OIDC Provider and test suite for OIDC Relying Parties.

The Technical specifications of these SDKs are available here:

1. [__OIDC Federation Entity/Authority/Intermediary__](docs/technical_specifications/ENTITY.md)
2. [__OIDC Federation 1.0 onboarding service DEMO__](docs/technical_specifications/ONBOARDING.md)
3. [__Openid Connect Provider__](docs/technical_specifications/PROVIDER.md)
4. [__Openid Connect Relying Party__](docs/technical_specifications/RELYING_PARTY.md)

## Contents

We have all the Django apps available in the folder `spid_cie_oidc/`.
The examples projects are instead in the folder `examples/`.

There is a substantial difference between an app and a project.
The app is installed using a common python package manager, such as poetry or pip,
and can be used, inherited, and integrated into other projects.

A project is a service configuration that integrates one or more applications.
In this repository we have three example project for demo purpose.

### Summary

- [Features](#features)
* [Setup](#setup)
    * [Dependencies](#dependencies)
      * [Install as Django application](#install-as-django-application)
      * [Setup the example project for demo purpose](#setup-the-example-project-for-demo-purpose)
* [Usage](#usage)
* [Contribute](#contribute)
    * [Contribute as end user](#contribute-as-end-user)
    * [Contribute as developer](#contribute-as-developer)
    * [Hints](#hints)
* [License and Authors](#license-and-authors)
* [Implementations notes](#implementation-notes)


## Setup

The Database storage engine can be one of which supported by Django, the example project comes with sqlite3.


#### Dependencies
````
apt install python3-dev python3-pip git
python3 -m pip install --upgrade pip
sudo pip install virtualenv
````

Activate the environment. It's optional and up to you if you want to install 
in a separate env or system wide
````
virtualenv -p python3 --copies env
source env/bin/activate
````

#### Install as Django application
Install __spid-cie-oidc__ as python package and use it in your django project
````
pip install spid-cie-oidc

# then include `spid_cie_oidc.{app_name}` in your project settings.INSTALLED_APPS
````

#### Setup the example project for demo purpose

````
git clone https://github.com/peppelinux/spid-cie-oidc
cd spid-cie-oidc
pip install -e .
````

In `examples/` folder you have three demostrations projects:
 - federation_authority
 - relying_party
 - provider

for each of the them you have to create the db and load the example data , as follows:

````
cd examples/$project_name
cp $project_name/settingslocal.py.example $project_name/settingslocal.py
# then customize (optional) $project_name/settingslocal.py

./manage.py migrate

# load the demo configuration
./manage.py loaddata dumps/example.json

# create a super user
./manage.py createsuperuser
````

## Usage

The demo propose a small federation composed by the following entities:

 - Federation Authority, acts as trust anchor and onboarding system. It's available at `http://localhost:8000`
 - OpenID Relying Party, available at `http://localhost:8001`
 - OpenID Provider, available at `http://localhost:8002`

Activate the environment
````
source env/bin/activate
cd examples
````

Then enter in the single applications projects (__federation_authority/__ or __relying_party/__ or __provider/__):
````
# run the web server
./manage.py runserver
````
Point your web browser to `http://localhost:8000/admin` to enter in the management interface.


## Contribute

Your contribution is welcome, no question is useless and no answer is obvious, we need you.

#### Contribute as end user

Please open an issue if you've discoveerd a bug or if you want to ask some features.

#### Contribute as developer

Please open your Pull Requests on the __dev__ branch. 
Please consider the following branches:

 - __main__: where we merge the code before tag a new stable release.
 - __dev__: where we push our code during development.
 - __other-custom-name__: where a new feature/contribution/bugfix will be handled, revisioned and then merged to dev branch.

#### Hints

Backup your demo data

````
# backup your data (upgrade example data), -e excludes.
./manage.py dumpdata -e spid_cie_oidc_accounts -e admin -e auth -e contenttypes -e sessions > dumps/example.json
````

In this project we adopt [Semver](https://semver.org/lang/it/) and
[Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specifications.

## License and Authors

This software is released under the Apache 2 License by:

- Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>.

## Implementation notes

All the operation related to JWT signature and encryption, and part of OIDC messages operations, 
are built on top of [IdentityPython](https://idpy.org/):

- [oidcmsg](https://github.com/IdentityPython/JWTConnect-Python-OidcMsg)
- [cryptojwt](https://github.com/IdentityPython/JWTConnect-Python-CryptoJWT)

This project proposes an implementation of the italian OIDC Federation profile with
__automatic_client_registration__ and the adoption of the trust marks as mandatory.

If you're looking for a fully compliant implementation of OIDC Federation 1.0,
with a full support of explicit client registration, please look at idpy's
[fedservice](https://github.com/rohe/fedservice).

AUTHORS METASEP
Giuseppe De Marco <demarcog83@gmail.com>

spid_cie_oidc/relying_party/views.py METASEP
import json
import logging

from django.conf import settings
from django.contrib.auth import login, logout, get_user_model
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.views import View
from django.shortcuts import render
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _

from . import OAuth2BaseView
from .exceptions import MisconfiguredClientIssuer
from .oauth2 import *
from .oidc import *
from .models import OidcAuthentication
from .utils import (
    http_dict_to_redirect_uri_path,
    http_redirect_uri_to_dict,
    random_string,
)


logger = logging.getLogger(__name__)


class SpidCieOidcRpBeginView(View):
    """View which processes the actual Authz request and
    returns a Http Redirect
    """

    def get_federation_trust(self, request):
        """
        OIDC Federation Metadata discovery for a given sub/issuer_id
        """
        # TODO
        available_issuers = {}
        available_issuers_len = len(available_issuers)

        # todo: validate it upoun a schema
        sub = request.GET.get("sub")

        if not sub:
            if available_issuers_len > 1:
                # TODO - a provider selection page here!
                raise NotImplementedError()

            elif available_issuers_len == 1:
                issuer_id = list(available_issuers.keys())[0]
            else:
                raise MisconfiguredClientIssuer("No available issuers found")
        return issuer_id, available_issuers[issuer_id]["issuer"]

    def get(self, request, *args, **kwargs):
        """
        https://tools.ietf.org/html/rfc6749#section-4.1.1

        https://login.agid.gov.it/.well-known/openid-configuration
        http://localhost:8888/oidc/spid/begin?issuer_id=agid_login_local
        """
        issuer_id, issuer_fqdn = self.get_oidc_rp_issuer(request)
        client_conf = settings.JWTCONN_RP_CLIENTS[issuer_id]

        try:
            provider_conf = self.provider_discovery(client_conf)
        except Exception as e:  # pragma: no cover
            _msg = f"Failed to get provider discovery from {issuer_fqdn}"
            logger.error(f"{_msg}: {e}")
            return HttpResponseBadRequest(_(_msg))

        try:
            jwks_dict, jwks = self.get_jwks_from_jwks_uri(
                provider_conf["jwks_uri"], verify=client_conf["httpc_params"]["verify"]
            )
        except Exception as e:  # pragma: no cover
            _msg = f"Failed to get jwks from {issuer_fqdn}"
            logger.error(f"{_msg}: {e}")
            return HttpResponseBadRequest(_(_msg))

        client_prefs = client_conf["client_preferences"]
        authz_endpoint = provider_conf["authorization_endpoint"]
        authz_data = dict(
            scope=" ".join(client_prefs["scope"]),
            redirect_uri=client_conf["redirect_uris"][0],
            response_type=client_prefs["response_types"][0],
            nonce=random_string(24),
            state=random_string(32),
            client_id=client_conf["client_id"],
            endpoint=authz_endpoint,
        )

        # TODO: generalized addons loader
        has_pkce = client_conf.get("add_ons", {}).get("pkce")
        if has_pkce:
            pkce_func = import_string(has_pkce["function"])
            pkce_values = pkce_func(**has_pkce["kwargs"])
            authz_data.update(pkce_values)

        # create request in db
        authz_entry = dict(
            client_id=client_conf["client_id"],
            state=authz_data["state"],
            endpoint=authz_endpoint,
            issuer=issuer_fqdn,
            issuer_id=issuer_id,
            data=json.dumps(authz_data),
            provider_jwks=json.dumps(jwks_dict),
            provider_configuration=json.dumps(provider_conf),
        )
        OidcAuthentication.objects.create(**authz_entry)

        authz_data.pop("code_verifier")
        uri_path = http_dict_to_redirect_uri_path(authz_data)
        url = "?".join((authz_endpoint, uri_path))
        data = http_redirect_uri_to_dict(url)
        logger.debug(f"Started Authz: {url}")
        logger.debug(f"Authorization Request data: {data}")
        return HttpResponseRedirect(url)


class SpidCieOidcRpCallbackView(
    OAuth2BaseView, View, OidcUserInfo, OAuth2AuthorizationCodeGrant
):
    """
    View which processes an Authorization Response
    https://tools.ietf.org/html/rfc6749#section-4.1.2

    eg:
    /redirect_uri?code=tYkP854StRqBVcW4Kg4sQfEN5Qz&state=R9EVqaazGsj3wg5JgxIgm8e8U4BMvf7W


    """

    def process_user_attributes(
        self, userinfo: dict, client_conf: dict, authz: OidcAuthentication
    ):
        user_map = client_conf["user_attributes_map"]
        data = dict()
        for k, v in user_map.items():
            for i in v:
                if isinstance(i, str):
                    if i in userinfo:
                        data[k] = userinfo[i]
                        break

                elif isinstance(i, dict):
                    args = (userinfo, client_conf, authz.__dict__, i["kwargs"])
                    value = import_string(i["func"])(*args)
                    if value:
                        data[k] = value
                        break
        return data

    def user_reunification(self, user_attrs: dict, client_conf: dict):
        user_model = get_user_model()
        field_name = client_conf["user_lookup_field"]
        lookup = {field_name: user_attrs[field_name]}
        user = user_model.objects.filter(**lookup)
        if user:  # pragma: no cover
            user = user.first()
            logger.info(f"{field_name} matched on user {user}")
            return user
        elif client_conf.get("user_create"):
            user = user_model.objects.create(**user_attrs)
            logger.info(f"Created new user {user}")
            return user

    def get(self, request, *args, **kwargs):
        """
        docs here
        """
        request_args = {k: v for k, v in request.GET.items()}
        authz = OidcAuthentication.objects.filter(
            state=request_args.get("state"),
        )
        if not authz:
            return HttpResponseBadRequest(_("Unsolicited response"))
        else:
            authz = authz.last()

        authz_data = json.loads(authz.data)
        provider_conf = authz.get_provider_configuration()
        client_conf = settings.JWTCONN_RP_CLIENTS[authz.issuer_id]

        code = request.GET.get("code")
        authz_token = OidcAuthenticationToken.objects.create(
            authz_request=authz, code=code
        )

        token_request = self.access_token_request(
            redirect_uri=authz_data["redirect_uri"],
            client_id=authz.client_id,
            state=authz.state,
            code=code,
            issuer_id=authz.issuer_id,
            client_conf=client_conf,
            token_endpoint_url=provider_conf["token_endpoint"],
            code_verifier=authz_data.get("code_verifier"),
        )

        if not token_request:
            return HttpResponseBadRequest(
                _("Authentication token seems not to be valid.")
            )

        keyjar = authz.get_provider_keyjar()

        if not self.validate_jwt(authz, token_request["access_token"], keyjar):
            pass
            # Actually AgID Login have a non-JWT access token!
            # return HttpResponseBadRequest(
            # _('Authentication response validation error.')
            # )
        if not self.validate_jwt(authz, token_request["id_token"], keyjar):
            return HttpResponseBadRequest(_("Authentication token validation error."))

        # just for debugging purpose ...
        decoded_id_token = self.decode_jwt(
            "ID Token", authz, token_request["id_token"], keyjar
        )
        logger.debug(decoded_id_token)
        decoded_access_token = self.decode_jwt(
            "Access Token", authz, token_request["access_token"], keyjar
        )
        logger.debug(decoded_access_token)

        authz_token.access_token = token_request["access_token"]
        authz_token.id_token = token_request["id_token"]
        authz_token.scope = token_request.get("scope")
        authz_token.token_type = token_request["token_type"]
        authz_token.expires_in = token_request["expires_in"]
        authz_token.save()

        userinfo = self.get_userinfo(
            authz.state,
            authz_token.access_token,
            provider_conf,
            verify=client_conf["httpc_params"]["verify"],
        )
        if not userinfo:
            return HttpResponseBadRequest(_("UserInfo response seems not to be valid."))

        # here django user attr mapping
        user_attrs = self.process_user_attributes(userinfo, client_conf, authz)
        if not user_attrs:
            _msg = "No user attributes have been processed"
            logger.warning(f"{_msg}: {userinfo}")
            raise PermissionDenied(_msg)
        user = self.user_reunification(user_attrs, client_conf)
        if not user:
            raise PermissionDenied()

        # authenticate the user
        login(request, user)
        request.session["oidc_rp_user_attrs"] = user_attrs
        authz_token.user = user
        authz_token.save()

        return HttpResponseRedirect(
            client_conf.get("login_redirect_url")
            or getattr(settings, "LOGIN_REDIRECT_URL")
        )


class SpidCieOidcRpCallbackEchoAttributes(View):
    def get(self, request):
        data = {"oidc_rp_user_attrs": request.session["oidc_rp_user_attrs"]}
        return render(request, "echo_attributes.html", data)


@login_required
def oidc_rpinitiated_logout(request):
    """
    http://localhost:8000/end-session/?id_token_hint=
    """
    auth_tokens = OidcAuthenticationToken.objects.filter(user=request.user).filter(
        Q(logged_out__iexact="") | Q(logged_out__isnull=True)
    )
    authz = auth_tokens.last().authz_request
    provider_conf = authz.get_provider_configuration()
    end_session_url = provider_conf.get("end_session_endpoint")

    # first of all on RP side ...
    logout(request)

    if not end_session_url:
        logger.warning(f"{authz.issuer_url} does not support end_session_endpoint !")
        return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)
    else:
        auth_token = auth_tokens.last()
        url = f"{end_session_url}?id_token_hint={auth_token.id_token}"
        auth_token.logged_out = timezone.localtime()
        auth_token.save()
        return HttpResponseRedirect(url)

spid_cie_oidc/relying_party/utils.py METASEP
import base64
import json
import hashlib
import logging
import os
import random
import re
import string
import urllib


from cryptojwt.key_jar import KeyJar
from django.utils.safestring import mark_safe
from oidcmsg.message import Message


logger = logging.getLogger(__name__)


def http_redirect_uri_to_dict(url):
    splitted = urllib.parse.splitquery(url)
    data = dict(urllib.parse.parse_qsl(splitted[1]))
    data.update({"endpoint": splitted[0]})
    return data


def http_dict_to_redirect_uri_path(data):
    return urllib.parse.urlencode(data)


def decode_token(bearer_token, keyjar, verify_sign=True):
    msg = Message().from_jwt(bearer_token, keyjar=keyjar, verify=verify_sign)
    return msg.to_dict()


def random_string(n=27):
    return "".join(random.choices(string.ascii_letters + string.digits, k=n)) # nosec


def get_pkce(code_challenge_method: str = "S256", code_challenge_length: int = 64):
    hashers = {"S256": hashlib.sha256}

    code_verifier = base64.urlsafe_b64encode(os.urandom(40)).decode("utf-8")
    code_verifier = re.sub("[^a-zA-Z0-9]+", "", code_verifier)

    code_challenge = hashers.get(code_challenge_method)(
        code_verifier.encode("utf-8")
    ).digest()
    code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
    code_challenge = code_challenge.replace("=", "")

    return {
        "code_verifier": code_verifier,
        "code_challenge": code_challenge,
        "code_challenge_method": code_challenge_method,
    }


def get_issuer_keyjar(jwks, issuer: str):
    key_jar = KeyJar()
    # "" means default, you can always point to a issuer identifier
    key_jar.import_jwks(jwks, issuer_id=issuer)
    return key_jar


def validate_jwt(jwt: str, key_jar):
    try:
        recv = Message().from_jwt(jwt, keyjar=key_jar)
        return recv.verify(), key_jar
    except Exception as e:
        logger.warning(f"{e}")
        return False


def html_json_preview(value):
    msg = json.loads(value or "{}")
    dumps = json.dumps(msg, indent=2)
    return mark_safe(dumps.replace("\n", "<br>").replace(" ", "&nbsp")) # nosec

spid_cie_oidc/relying_party/urls.py METASEP
from django.urls import path

from .views import (
    oidc_rpinitiated_logout,
    SpidCieOidcRpBeginView,
    SpidCieOidcRpCallbackEchoAttributes,
    SpidCieOidcRpCallbackView,
)

urlpatterns = []
urlpatterns += (
    path("oidc/rp/begin", SpidCieOidcRpBeginView.as_view(), name="spid_cie_rp_begin"),
)
urlpatterns += (
    path(
        "oidc/rp/callback",
        SpidCieOidcRpCallbackView.as_view(),
        name="spid_cie_rp_callback",
    ),
)
urlpatterns += (
    path(
        "echo_attributes",
        SpidCieOidcRpCallbackEchoAttributes.as_view(),
        name="spid_cie_rp_echo_attributes",
    ),
)
urlpatterns += (
    path("oidc/rp/logout", oidc_rpinitiated_logout, name="spid_cie_rpinitiated_logout"),
)

spid_cie_oidc/relying_party/settings.py METASEP
RP_PREFS = {
    "application_name": "that_fancy_rp",
    "application_type": "web",
    "contacts": ["ops@example.com"],
    "response_types": ["code"],
    "scope": ["openid", "offline_access"],
    "token_endpoint_auth_method": ["private_key_jwt"],
}


RP_ATTR_MAP = {
    "username": (
        {
            "func": "spid_cie_oidc.relying_party.processors.issuer_prefixed_sub",
            "kwargs": {"sep": "__"},
        },
    ),
    "first_name": ("firstname",),
    "last_name": ("lastname",),
    "email": ("email",),
}


RP_PKCE_CONF = {
    "function": "spid_cie_oidc.relying_party.utils.get_pkce",
    "kwargs": {"code_challenge_length": 64, "code_challenge_method": "S256"},
}


RP_PROVIDERS = {
    "op_test": {
        "issuer": "http://localhost:8888",
        "discovery_url": "http://localhost:8888/oidc/op/openid-configuration",
        "client_preferences": RP_PREFS,
        "client_id": "that-id",
        "client_secret": "that-secret",
        "redirect_uris": ["http://localhost:8888/callback"],
        "httpc_params": {"verify": False},
        "add_ons": {"pkce": RP_PKCE_CONF},
        "user_attributes_map": RP_ATTR_MAP,
        "user_lookup_field": ("username"),
        "user_create": True,
        "login_redirect_url": "/echo_attributes",
    }
}

spid_cie_oidc/relying_party/processors.py METASEP
def issuer_prefixed_sub(
    user_info: dict, client_conf: dict, data: dict = None, kwargs: dict = {}
):
    return f"{data['issuer_id']}{kwargs.get('sep', '__')}{user_info['sub']}"

spid_cie_oidc/relying_party/models.py METASEP
import json
import logging

from django.contrib.auth import get_user_model
from django.db import models

from .utils import decode_token, get_issuer_keyjar

logger = logging.getLogger(__name__)


class OidcAuthentication(models.Model):
    client_id = models.CharField(max_length=255)
    state = models.CharField(max_length=255, unique=True, default="state-is-unique")
    endpoint = models.URLField(blank=True, null=True)
    issuer = models.CharField(max_length=255, blank=True, null=True)
    issuer_id = models.CharField(max_length=255, blank=True, null=True)
    provider_jwks = models.TextField(blank=True, null=True)
    data = models.TextField(blank=True, null=True)
    successful = models.BooleanField(default=False)
    provider_configuration = models.TextField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "OIDC Authentication"
        verbose_name_plural = "OIDC Authentications"

    def __str__(self):
        return f"{self.client_id} {self.state} to {self.endpoint}"

    def get_provider_configuration(self):
        return json.loads(self.provider_configuration)

    def get_provider_keyjar(self):
        jwks = json.loads(self.provider_jwks)
        keyjar = get_issuer_keyjar(jwks, self.issuer)
        return keyjar


class OidcAuthenticationToken(models.Model):
    user = models.ForeignKey(
        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True
    )
    authz_request = models.ForeignKey(OidcAuthentication, on_delete=models.CASCADE)
    code = models.CharField(max_length=255, blank=True, null=True)
    access_token = models.TextField(blank=True, null=True)
    id_token = models.TextField(blank=True, null=True)

    scope = models.CharField(max_length=255, blank=True, null=True)
    token_type = models.CharField(max_length=255, blank=True, null=True)
    expires_in = models.IntegerField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    logged_out = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return f"{self.authz_request} {self.code}"

    def token_preview(self, token):
        keyjar = self.authz_request.get_provider_keyjar()
        try:
            msg = decode_token(token, keyjar)
            dumps = json.dumps(msg, indent=2)
            return dumps # mark_safe(dumps.replace("\n", "<br>").replace(" ", "&nbsp"))
        except Exception as e:
            logger.error(e)

    @property
    def access_token_preview(self):
        return self.token_preview(self.access_token)

    @property
    def id_token_preview(self):
        return self.token_preview(self.id_token)

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.authz_request.successful = True
        self.authz_request.save()

spid_cie_oidc/relying_party/exceptions.py METASEP
class MisconfiguredClientIssuer(Exception):
    pass

spid_cie_oidc/relying_party/apps.py METASEP
from django.apps import AppConfig


class SpidCieFederationRpConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "spid_cie_oidc.relying_party"
    verbose_name = "SPID/CIE OIDC Federation Relying Party"
    label = "spid_cie_oidc_relying_party"

spid_cie_oidc/relying_party/admin.py METASEP
import logging

from django.contrib import admin

from .models import OidcAuthentication, OidcAuthenticationToken
from .utils import html_json_preview

logger = logging.getLogger(__name__)


class OidcAuthenticationTokenInline(admin.StackedInline):
    model = OidcAuthenticationToken
    extra = 0
    max_num = 1
    readonly_fields = (
        "scope",
        "expires_in",
        "token_type",
        "code",
        "access_token",
        "id_token",
        "access_token_preview",
        "id_token_preview",
    )


@admin.register(OidcAuthentication)
class OidcAuthenticationAdmin(admin.ModelAdmin):
    search_fields = ("endpoint", "state", "client_id")
    list_display = ("client_id", "state", "endpoint", "created", "modified")
    list_filter = ("created", "endpoint")
    inlines = (OidcAuthenticationTokenInline,)
    readonly_fields = (
        "issuer",
        "client_id",
        "state",
        "endpoint",
        "successful",
        "json_preview",
        "jwks_preview",
        "provider_configuration_preview",
        "created",
        "modified",
    )
    exclude = ("issuer_id", "data", "provider_configuration", "provider_jwks")
    fieldsets = (
        (None, {"fields": ("issuer", "client_id", "state", "endpoint")}),
        (
            "Status",
            {
                "fields": (
                    "successful",
                    "created",
                    "modified",
                )
            },
        ),
        (
            "Authorization request previews",
            {
                "fields": (
                    "json_preview",
                    "jwks_preview",
                ),
                "classes": ("collapse",),
            },
        ),
        (
            "Provider Discovery result",
            {
                "fields": ("provider_configuration_preview",),
                "classes": ("collapse",),
            },
        ),
    )

    def json_preview(self, obj):
        return html_json_preview(obj.data)

    json_preview.short_description = "Authentication Request data"

    def provider_configuration_preview(self, obj):
        return html_json_preview(obj.provider_configuration)

    provider_configuration_preview.short_description = "provider configuration"

    def jwks_preview(self, obj):
        return html_json_preview(obj.provider_jwks)

    jwks_preview.short_description = "jwks"

spid_cie_oidc/relying_party/__init__.py METASEP
import logging

from cryptojwt.exception import BadSyntax
from .utils import decode_token, validate_jwt


logger = logging.getLogger(__name__)


class OAuth2BaseView(object):
    def validate_jwt(self, authz, jwt, keyjar):
        _msg = (
            f"Something went wrong with {authz} JWT validation: "
            "Token validation fails [jwt]"
        )
        if validate_jwt(jwt, key_jar=keyjar):
            return True
        else:
            logger.error(_msg)
            return False

    def decode_jwt(self, name, authz, jwt, keyjar):
        try:
            decoded_jwt = decode_token(jwt, keyjar=keyjar)
            logger.debug(f"{name}: {decoded_jwt}")
            return decoded_jwt
        except BadSyntax:
            logger.warning(f"{name} from {authz.issuer} is not in JWT format: {jwt}")
        except Exception as e:  # pragma: no cover
            logger.error(f"Something went wrong decoding {name}: {e}")

spid_cie_oidc/provider/views.py METASEP
# Create your views here.

spid_cie_oidc/provider/tests.py METASEP
# Create your tests here.

spid_cie_oidc/provider/models.py METASEP
# Create your models here.

spid_cie_oidc/provider/apps.py METASEP
from django.apps import AppConfig


class SpidCieOidcOpConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "spid_cie_oidc.provider"
    verbose_name = "SPID/CIE OIDC Federation Provider"
    label = "spid_cie_oidc_provider"

spid_cie_oidc/provider/admin.py METASEP
# Register your models here.

spid_cie_oidc/provider/__init__.py METASEP

spid_cie_oidc/onboarding/views.py METASEP
from django.http import Http404
from django.http import HttpResponse
from django.http import JsonResponse


from spid_cie_oidc.onboarding.models import (
    FederationDescendant,
    FederationEntityAssignedProfile,
    get_first_self_trust_anchor,
)


def fetch(request):
    if request.GET.get('iss'):
        iss = get_first_self_trust_anchor(sub = request.GET['iss'])
    else:
        iss = get_first_self_trust_anchor()

    if not request.GET.get("sub"):
        conf = get_first_self_trust_anchor()
        if request.GET.get("format") == "json":
            return JsonResponse(conf.entity_configuration_as_dict, safe=False)
        else:
            return HttpResponse(
                conf.entity_configuration_as_jws,
                content_type="application/jose"
            )

    sub = FederationDescendant.objects.filter(
        sub=request.GET["sub"], is_active=True
    ).first()
    if not sub:
        raise Http404()

    if request.GET.get("format") == "json":
        return JsonResponse(
            sub.entity_statement_as_dict(iss.sub, request.GET.get('aud')
        ), safe=False)
    else:
        return HttpResponse(
            sub.entity_statement_as_jws(iss.sub, request.GET.get('aud')),
            content_type="application/jose"
        )


def entity_list(request):
    is_leaf = request.GET.get("is_leaf", "").lower()
    if is_leaf == "true":
        _q = {
            "profile__profile_category__in": (
                "openid_relying_party", "openid_provider"
            )
        }
    elif is_leaf == "false":
        _q = {"profile__profile_category": "federation_entity"}
    else:
        _q = {}

    entries = FederationEntityAssignedProfile.objects.filter(**_q).values_list(
        "descendant__sub", flat=True
    )
    return JsonResponse(list(entries), safe=False)


def resolve_entity_statement(request):
    """
    resolves the final metadata of its descendants
    """
    if not all(
        request.GET.get("sub"),
        request.GET.get("anchor"),
    ):
        raise Http404("sub and anchor parameters are REQUIRED.")

    # TODO: resolve also other entities in a federation
    # TODO: release a metadata on top of a resolved trust chain
    # if request.GET.get('iss'):
    # iss = get_first_self_trust_anchor(sub = request.GET['iss'])
    # else:
    # iss = get_first_self_trust_anchor()

    entity = FederationDescendant.objects.filter(
        sub=request.GET["sub"], is_active=True
    )

    # filter by type
    if request.GET.get("type"):
        entity.metadata_policy.get(request.GET["type"])
        # metadata =

spid_cie_oidc/onboarding/urls.py METASEP
"""acsia_agent_proj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.urls import path

from .views import fetch, entity_list

_PREF = getattr(settings, "OIDC_PREFIX", "")

urlpatterns = [
    path(f"{_PREF}fetch/", fetch, name="oidcfed_fetch"),
    path(f"{_PREF}list/", entity_list, name="oidcfed_list"),
]

spid_cie_oidc/onboarding/models.py METASEP
from copy import deepcopy

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models

# from django.db.models.signals import post_save
from django.utils.translation import gettext as _

from spid_cie_oidc.entity.abstract_models import TimeStampedModel
from spid_cie_oidc.entity.models import (
    ENTITY_TYPES,
    ENTITY_STATUS,
    FederationEntityConfiguration,
    PublicJwk,
)

from spid_cie_oidc.entity.exceptions import MissingAuthorityHintsClaim, NotDescendant
from spid_cie_oidc.entity.statements import (
    get_entity_configurations,
    EntityConfiguration,
)
from spid_cie_oidc.entity.jwtse import create_jws
from spid_cie_oidc.entity.trust_chain import HTTPC_PARAMS
from spid_cie_oidc.entity.utils import iat_now
from spid_cie_oidc.entity.utils import exp_from_now
from typing import Union

from . import settings as local_settings

import json
import logging
import uuid


FEDERATION_DEFAULT_POLICY = getattr(
    settings, "FEDERATION_DEFAULT_POLICY", local_settings.FEDERATION_DEFAULT_POLICY
)
FEDERATION_DEFAULT_EXP = getattr(
    settings, "FEDERATION_DEFAULT_EXP", local_settings.FEDERATION_DEFAULT_EXP
)

logger = logging.getLogger(__name__)


def get_first_self_trust_anchor(
    sub: str = None,
) -> Union[FederationEntityConfiguration, None]:
    """
    get the first available Trust Anchor that represent self
    as a qualified issuer
    """
    lk = dict(metadata__federation_entity__isnull=False, is_active=True)
    if sub:
        lk["sub"] = sub
    return FederationEntityConfiguration.objects.filter(**lk).first()


class FederationEntityProfile(TimeStampedModel):
    """
    Federation OnBoarding profile.
    It optionally defines trust marks templates
    """

    name = models.CharField(
        max_length=33,
        help_text=_("Profile name. ")
    )
    profile_category = models.CharField(
        max_length=64,
        help_text=_("Profile id. It SHOULD be a URL but feel free to put whatever"),
        choices=[(i, i) for i in ENTITY_TYPES]
    )
    profile_id = models.CharField(
        max_length=1024,
        help_text=_("Profile id. It SHOULD be a URL but feel free to put whatever"),
        unique=True
    )
    trust_mark_template = models.JSONField(
        help_text=_("trust marks template for this profile"),
        default=dict
    )

    class Meta:
        verbose_name = "Federation Entity Profile"
        verbose_name_plural = "Federation Entity Profiles"

    @property
    def trust_mark_template_as_json(self):
        return json.dumps(self.trust_mark_template)

    def __str__(self):
        return f"{self.name} {self.profile_id}"


class FederationDescendant(TimeStampedModel):
    """
    Federation OnBoarding entries.
    """

    def validate_entity_configuration(value):
        """
        value is the sub url
        """
        try:
            jwt = get_entity_configurations(value)[0]
        except Exception as e:
            raise ValidationError(
                f"Failed to fetch Entity Configuration for {value}: {e}"
            )
        ec = EntityConfiguration(jwt, httpc_params=HTTPC_PARAMS)
        ec.validate_by_itself()

        authority_hints = ec.payload.get("authority_hints", [])
        if not authority_hints:
            raise MissingAuthorityHintsClaim(
                "authority_hints must be present "
                "in a descendant entity configuration"
            )

        proper_descendant = False
        for i in authority_hints:
            if i in settings.OIDCFED_FEDERATION_TRUST_ANCHORS:
                proper_descendant = True
                break
        if not proper_descendant:
            raise NotDescendant(
                "This participant MUST have one of "
                f"{', '.join(settings.OIDCFED_FEDERATION_TRUST_ANCHORS)} in "
                f"its authority_hints claim. It has: {authority_hints}"
            )

    def def_uid():
        return f"autouid-{uuid.uuid4()}"

    uid = models.CharField(
        max_length=1024,
        default=def_uid,
        unique=True,
        help_text=_(
            "an unique code that identifies this entry. "
            "For italian public service it may be the IPA code."
        ),
    )
    name = models.CharField(
        max_length=33,
        help_text=_(
            "human readable name of this entity. "
            "It may be a unit or organization name"
        ),
    )
    sub = models.URLField(
        max_length=255,
        blank=False,
        null=False,
        unique=True,
        help_text=_("URL that identifies this Entity in the Federation."),
        validators=[validate_entity_configuration],
    )
    type = models.CharField(
        max_length=33,
        blank=True,
        default="openid_relying_party",
        choices=[(i, i) for i in ENTITY_TYPES],
        help_text=_("OpenID Connect Federation entity type"),
    )
    registrant = models.ManyToManyField(
        get_user_model(),
        help_text=_(
            "Logged in users can modify only sub, contacts and jwks "
            "attributes, if they're owner of this entry. "
        ),
        blank=True,
    )
    metadata_policy = models.JSONField(
        blank=True, help_text=_("if present overloads the DEFAULT policy"), default=dict
    )
    constraints = models.JSONField(
        blank=True, help_text=_("if present overloads the DEFAULT policy"), default=dict
    )
    extended_claims = models.JSONField(
        blank=True,
        help_text=_(
            "a dictionary containing any other claim like: "
            "jti, crti, policy_language_crit and any other extension"
        ),
        default=dict,
    )
    status = models.CharField(
        max_length=33,
        default=False,
        help_text=_("Its entity configuration is periodically fetched and validated."),
        choices=[(i, i) for i in ENTITY_STATUS.keys()],
    )
    status_log = models.JSONField(blank=True, help_text=_("status log"), default=dict)
    is_active = models.BooleanField(
        default=False, help_text=_("If this entity is active. ")
    )

    class Meta:
        verbose_name = "Federation Entity Descendant"
        verbose_name_plural = "Federation Entity Descendants"

    @property
    def trust_marks(self):
        #
        profiles = FederationEntityAssignedProfile.objects.filter(descendant=self)
        return [i.trust_mark for i in profiles]

    @property
    def trust_marks_as_json(self):
        return json.dumps(self.trust_marks)

    @property
    def entity_profiles(self):
        return [
            i.profile.profile_category
            for i in FederationEntityAssignedProfile.objects.filter(descendant=self)
        ]

    def entity_statement_as_dict(self, iss:str = None, aud:list = None) -> dict:
        jwks = [
            i.jwk.jwk for i in FederationDescendantJwk.objects.filter(descendant=self)
        ]
        if not jwks:
            logger.warning(f"Any JWKs found for {self.sub}")
            return {}

        policies = {
            k: local_settings.FEDERATION_DEFAULT_POLICY[k]
            for k in self.entity_profiles
        }

        # apply custom policies if defined
        policies.update(self.metadata_policy)

        data = {
            "exp": exp_from_now(minutes = FEDERATION_DEFAULT_EXP),
            "iat": iat_now(),
            "iss": get_first_self_trust_anchor(iss).sub,
            "sub": self.sub,
            "jwks": {"keys": jwks},
            "metadata_policy": policies,
        }
        if aud:
            data['aud'] = [aud] if isinstance(aud, str) else aud

        # add contacts
        contacts = FederationDescendantContact.objects.filter(entity=self).values_list(
            "contact", flat=True
        )

        if contacts:
            for k, v in data["metadata_policy"].items():
                if data["metadata_policy"][k].get("contacts"):
                    data["metadata_policy"][k]["contacts"].update(
                        {"add": [i for i in contacts]}
                    )
                else:
                    data["metadata_policy"][k]["contacts"] = {
                        "add": [i for i in contacts]
                    }

        # include active trust marks
        tm = self.trust_marks
        if tm:
            data["trust_marks"] = tm

        return data

    def entity_statement_as_json(self, iss:str = None, aud:list = None) -> str:
        return json.dumps(self.entity_statement_as_dict(iss, aud))

    def entity_statement_as_jws(self, iss:str = None, aud:list = None) -> str:
        issuer = get_first_self_trust_anchor(iss)
        return create_jws(
            self.entity_statement_as_dict(iss, aud),
            issuer.jwks[0],
            alg=issuer.default_signature_alg,
        )

    def __str__(self):
        return "{} [{} and {}]".format(
            self.sub, self.status, "active" if self.is_active else "--"
        )


class FederationDescendantJwk(TimeStampedModel):
    descendant = models.ForeignKey(FederationDescendant, on_delete=models.CASCADE)
    jwk = models.ForeignKey(PublicJwk, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "Federation Descendant Public JWK"
        verbose_name_plural = "Federation Descendant Public JWKs"

    def __str__(self):
        return f"{self.descendant.sub} {self.jwk.kid}"


class FederationEntityAssignedProfile(TimeStampedModel):
    descendant = models.ForeignKey(FederationDescendant, on_delete=models.CASCADE)
    profile = models.ForeignKey(FederationEntityProfile, on_delete=models.CASCADE)
    issuer = models.ForeignKey(FederationEntityConfiguration, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "Federation Entity Descendant Assigned Profile"
        verbose_name_plural = "Federation Descendant Assigned Profiles"

    @property
    def trust_mark_as_dict(self) -> dict:
        data = deepcopy(self.profile.trust_mark_template)
        data["sub"] = self.descendant.sub
        data["iss"] = self.issuer.sub
        data["iat"] = iat_now()
        return data

    @property
    def trust_mark_as_json(self):
        return json.dumps(self.trust_mark_as_dict)

    @property
    def trust_mark_as_jws(self):
        return create_jws(
            self.trust_mark_as_dict,
            self.issuer.jwks[0],
            alg=self.issuer.default_signature_alg,
        )

    @property
    def trust_mark(self):
        return {
            "id": self.profile.profile_id,
            "trust_mark": self.trust_mark_as_jws
        }

    def __str__(self):
        return f"{self.profile} [{self.descendant}]"


class FederationDescendantContact(TimeStampedModel):
    """
    Federation OnBoarding entries.
    """

    entity = models.ForeignKey(
        FederationDescendant,
        on_delete=models.CASCADE,
        help_text=_("Entity for which this contac is related"),
    )
    contact = models.CharField(
        max_length=255,
        blank=False,
        null=False,
        help_text=_("any kind of contact type, usually an email."),
    )
    type = models.CharField(
        max_length=255,
        blank=False,
        null=False,
        choices=[(i, i) for i in ("email", "telephone", "other")],
    )

    class Meta:
        verbose_name = "Federation Entity Contact"
        verbose_name_plural = "Federation Entity Contacts"

    def __str__(self):
        return f"{self.contact} {self.entity.sub}"


# signal on each save
# def trust_chain_trigger(**kwargs):
# subject = kwargs['instance'].sub

# onboarding uses the first available configuration of federation_entity
# fe = FederationEntityConfiguration.objects.filter(
# is_active=True,
# ).first()

# logger.info(
# f"Receiving trust chain evaluation signal for {subject}"
# )
# return trust_chain_builder(subject)
# post_save.connect(trust_chain_trigger, sender=FederationDescendant)
#

spid_cie_oidc/onboarding/apps.py METASEP
from django.apps import AppConfig


class SpidCieOidcOnboardingConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "spid_cie_oidc.onboarding"
    verbose_name = "SPID/CIE OIDC Federation OnBoarding"
    label = "spid_cie_oidc_onboarding"

spid_cie_oidc/onboarding/admin.py METASEP
from django.contrib import admin

from .models import (
    FederationDescendant,
    FederationDescendantContact,
    FederationEntityProfile,
    FederationEntityAssignedProfile,
    FederationDescendantJwk,
)


class FederationDescendantContactAdminInline(admin.TabularInline):
    model = FederationDescendantContact
    extra = 0
    readonly_fields = ("created", "modified")
    raw_id_fields = ("entity",)


class FederationDescendantJwkAdminInline(admin.StackedInline):
    model = FederationDescendantJwk
    extra = 0
    readonly_fields = ("created", "modified")
    raw_id_fields = ("jwk",)


@admin.register(FederationDescendant)
class FederationDescendantAdmin(admin.ModelAdmin):
    list_display = ("sub", "name", "status", "is_active", "created")
    list_filter = ("type", "created", "modified", "is_active")
    search_fields = ("sub",)
    readonly_fields = (
        "created",
        "modified",
        "entity_statement_as_json",
    )
    inlines = (
        FederationDescendantJwkAdminInline,
        FederationDescendantContactAdminInline,
    )


@admin.register(FederationDescendantJwk)
class FederationDescendantJwkAdmin(admin.ModelAdmin):
    list_display = ("descendant", "jwk", "created")
    list_filter = ("created", "modified")
    search_fields = (
        "descendant__sub",
        "kid",
    )


@admin.register(FederationEntityProfile)
class FederationEntityProfileAdmin(admin.ModelAdmin):
    list_display = ("name", "profile_id")
    list_filter = ("created", "modified")
    search_fields = ("name", "profile_id", "created", "modified")
    readonly_fields = ("trust_mark_template_as_json",)


@admin.register(FederationEntityAssignedProfile)
class FederationEntityAssignedProfileAdmin(admin.ModelAdmin):
    list_display = ("descendant", "profile", "issuer", "created")
    raw_id_fields = ("descendant", "profile", "issuer")
    list_filter = ("created", "modified")
    search_fields = ("descendant__sub", "descendant__name", "profile")
    readonly_fields = (
        "trust_mark_as_json", "trust_mark", "created", "modified"
    )

spid_cie_oidc/onboarding/__init__.py METASEP

spid_cie_oidc/entity/views.py METASEP
from django.http import Http404
from django.http import HttpResponse
from django.http import JsonResponse

from .models import FederationEntityConfiguration
from .statements import OIDCFED_FEDERATION_WELLKNOWN_URL


def entity_configuration(request):
    """
    OIDC Federation Entity Configuration at
    .well-known/openid-federation
    """
    _sub = request.build_absolute_uri().split(OIDCFED_FEDERATION_WELLKNOWN_URL)[0]
    conf = FederationEntityConfiguration.objects.filter(
        # TODO: check for reverse proxy and forwarders ...
        sub=_sub,
        is_active=True,
    ).first()

    if not conf:
        raise Http404()

    if request.GET.get("format") == "json":
        return JsonResponse(conf.entity_configuration_as_dict, safe=False)
    else:
        return HttpResponse(
            conf.entity_configuration_as_jws, content_type="application/jose"
        )

spid_cie_oidc/entity/validators.py METASEP
from cryptojwt.jwk.jwk import key_from_jwk_dict
from django.core.exceptions import ValidationError
from .jwks import serialize_rsa_key
from typing import Union

import json


def validate_public_jwks(values: Union[dict, list]):
    if isinstance(values, dict):
        values = [values]
    try:
        for jwk_dict in values:
            _k = key_from_jwk_dict(jwk_dict)
            if _k.private_key():
                _pub = serialize_rsa_key(_k.public_key())
                raise ValidationError(
                    f"This JWK is is private {json.dumps(jwk_dict)}. "
                    f"It MUST be public instead, like this: {json.dumps([_pub])}."
                )
    except Exception as e:
        raise ValidationError(f"Not valid: {e}")


def validate_private_jwks(values: Union[dict, list]):
    if isinstance(values, dict):
        values = [values]
    try:
        for jwk_dict in values:
            _k = key_from_jwk_dict(jwk_dict)
            if not _k.private_key():
                raise ValidationError(f"Can't extract a private JWK from {jwk_dict}")
    except Exception as e:
        raise ValidationError(f"Not valid: {e}")

spid_cie_oidc/entity/utils.py METASEP
from django.utils import timezone
from django.utils.timezone import make_aware
import datetime


def iat_now() -> int:
    return int(datetime.datetime.now().timestamp())


def exp_from_now(minutes: int = 33) -> int:
    _now = timezone.localtime()
    return int((_now + datetime.timedelta(minutes=minutes)).timestamp())


def datetime_from_exp(value) -> datetime.datetime:
    return make_aware(datetime.datetime.fromtimestamp(value))

spid_cie_oidc/entity/urls.py METASEP
"""acsia_agent_proj URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.urls import path

from .views import entity_configuration

_PREF = getattr(settings, "OIDC_PREFIX", "")

urlpatterns = [
    path(
        f"{_PREF}.well-known/openid-federation",
        entity_configuration,
        name="entity_configuration",
    ),
]

spid_cie_oidc/entity/trust_chain.py METASEP
import logging

from collections import OrderedDict
from django.conf import settings
from typing import Union

from spid_cie_oidc.entity.policy import apply_policy

from . import settings as settings_local
from .statements import (
    get_entity_configurations,
    EntityConfiguration,
)
from . utils import datetime_from_exp


HTTPC_PARAMS = getattr(settings, "HTTPC_PARAMS", settings_local.HTTPC_PARAMS)
OIDCFED_MAXIMUM_AUTHORITY_HINTS = getattr(
    settings,
    "OIDCFED_MAXIMUM_AUTHORITY_HINTS",
    settings_local.OIDCFED_MAXIMUM_AUTHORITY_HINTS,
)
logger = logging.getLogger(__name__)


class TrustChainBuilder:
    """
    A trust walker that fetches statements and evaluate the evaluables

    max_intermediaries means how many hops are allowed to the trust anchor
    max_authority_hints means how much authority_hints to follow on each hop
    """

    def __init__(
        self,
        subject: str,
        trust_anchor: Union[str, EntityConfiguration],
        httpc_params: dict = {},
        max_authority_hints: int = 10,
        subject_configuration: EntityConfiguration = None,
        required_trust_marks: list = [],
        metadata_type = 'openid_provider',
        # TODO - prefetch cache?
        # pre_fetched_entity_configurations = {},
        # pre_fetched_statements = {},
        #
        **kwargs,
    ) -> None:

        self.subject = subject
        self.subject_configuration = subject_configuration
        self.httpc_params = httpc_params

        self.trust_anchor = trust_anchor
        self.trust_anchor_configuration = None

        self.required_trust_marks = required_trust_marks
        self.is_valid = False

        # TODO: to be removed? deprecation
        # self.statements_collection = OrderedDict()

        self.tree_of_trust = OrderedDict()
        self.trust_path = [] # list of valid subjects up to trust anchor

        self.max_authority_hints = max_authority_hints

        # dynamically valued
        self.max_path_len = 0

        self.metadata_type = metadata_type
        self.final_metadata:dict = {}

        self.exp = 0
        
    def apply_metadata_policy(self) -> dict:
        """
            filters the trust path from subject to trust anchor
            apply the metadata policies along the path and 
            returns the final metadata
        """
        # find the path of trust
        if not self.trust_path:
            self.trust_path = [self.subject_configuration]
        elif self.trust_path[-1].sub == self.trust_anchor_configuration.sub:
            # ok trust path completed, I just have to return over all the parent calls
            return

        logger.info(
            f"Applying metadata policy for {self.subject} over "
            f"{self.trust_anchor_configuration.sub} starting from "
            f"{self.trust_path[-1]}"
        )
        last_path = self.tree_of_trust[len(self.trust_path) - 1]

        path_found = False
        for ec in last_path:
            for sup_ec in ec.verified_by_superiors.values():
                while (len(self.trust_path) - 2 < self.max_path_len):
                    if sup_ec.sub == self.trust_anchor_configuration.sub:
                        self.trust_path.append(sup_ec)
                        path_found = True
                        break
                    if sup_ec.verified_by_superiors:
                        self.trust_path.append(sup_ec)
                        self.apply_metadata_policy()
                    else:
                        logger.info(
                            f"'Cul de sac' in {sup_ec.sub} for {self.subject} "
                            f"to {self.trust_anchor_configuration.sub}"
                        )
                        self.trust_path = [self.subject_configuration]
                        break

        # once I filtered a concrete and unique trust path I can apply the metadata policy
        if path_found:
            logger.info(
                f"Found a trust path: {self.trust_path}"
            )
            self.final_metadata = self.subject_configuration.payload['metadata'][self.metadata_type]
            for i in range(len(self.trust_path))[::-1]:
                self.trust_path[i - 1].sub
                _pol = self.trust_path[i].verified_descendant_statements.get(
                    'metadata_policy', {}
                ).get(self.metadata_type, {})
                self.final_metadata = apply_policy(self.final_metadata, _pol)

        # set exp
        self.set_exp()
        return self.final_metadata

    @property
    def exp_datetime(self):
        if self.exp:
            return datetime_from_exp(self.exp)
    
    def set_exp(self):
        exps = [i.payload['exp'] for i in self.trust_path]
        if exps:
            self.exp = min(*exps)
    
    def discovery(self) -> bool:
        """
        return a chain of verified statements
        from the lower up to the trust anchor
        """
        logger.info(f"Starting a Walk into Metadata Discovery for {self.subject}")
        self.tree_of_trust[0] = [self.subject_configuration]

        # ecs_history = []

        while (len(self.tree_of_trust) - 2) < self.max_path_len:
            last_path_n = list(self.tree_of_trust.keys())[-1]
            last_ecs = self.tree_of_trust[last_path_n]

            sup_ecs = []
            for last_ec in last_ecs:

                # TODO: Metadata discovery loop prevention
                # if last_ec.sub in ecs_history:
                # logger.warning(
                # f"Metadata discovery loop detection for {last_ec.sub}. "
                # f"Already present in {ecs_history}"
                # )

                try:
                    superiors = last_ec.get_superiors(
                        max_authority_hints = self.max_authority_hints,
                        superiors_hints = [self.trust_anchor_configuration]
                    )
                    validated_by = last_ec.validate_by_superiors(
                        superiors_entity_configurations=superiors.values()
                    )
                    vbv = list(validated_by.values())
                    sup_ecs.extend(vbv)
                    # ecs_history.extend([i.sub for i in vbv])
                except Exception as e:
                    logger.exception(
                        f"Metadata discovery exception for {last_ec.sub}: {e}"
                    )

            if sup_ecs:
                self.tree_of_trust[last_path_n + 1] = sup_ecs
            else:
                break

        last_path = list(self.tree_of_trust.keys())[-1]
        if self.tree_of_trust[0][0].is_valid and self.tree_of_trust[last_path][0].is_valid:
            self.is_valid = True

        self.apply_metadata_policy()
        return self.is_valid

    def get_trust_anchor_configuration(self) -> None:

        if isinstance(self.trust_anchor, EntityConfiguration):
            self.trust_anchor_configuration = self.trust_anchor

        elif not self.trust_anchor_configuration and isinstance(self.trust_anchor, str):
            logger.info(f"Starting Metadata Discovery for {self.subject}")
            ta_jwt = get_entity_configurations(
                self.trust_anchor, httpc_params=self.httpc_params
            )
            self.trust_anchor_configuration = EntityConfiguration(ta_jwt)

        try:
            self.trust_anchor_configuration.validate_by_itself()
        except Exception as e:
            logger.error(
                f"Trust Anchor Entity Configuration failed for {self.trust_anchor}. "
                f"{e}"
            )

        if self.trust_anchor_configuration.payload.get("constraints", {}).get(
            "max_path_length"
        ):
            self.max_path_len = int(
                self.trust_anchor_configuration.payload["constraints"][
                    "max_path_length"
                ]
            )

    def get_subject_configuration(self) -> None:
        if not self.subject_configuration:
            jwt = get_entity_configurations(
                self.subject, httpc_params=self.httpc_params
            )
            self.subject_configuration = EntityConfiguration(jwt[0])
            self.subject_configuration.validate_by_itself()

            # TODO
            # TODO: self.subject_configuration.get_valid_trust_marks()
            # valid trust marks to be compared to self.required_trust_marks

    def start(self):
        try:
            self.get_trust_anchor_configuration()
            self.get_subject_configuration()
            self.discovery()
        except Exception as e:
            self.is_valid = False
            logger.error(f"{e}")
            raise e


def trust_chain_builder(
    subject: str,
    trust_anchor: EntityConfiguration,
    httpc_params: dict = {},
    required_trust_marks: list = [],
    metadata_type: str = 'openid_provider',
) -> TrustChainBuilder:
    """
    Minimal Provider Discovery endpoint request processing

    metadata_type MUST be one of
        openid_provider
        openid_relying_party
        oauth_resource
    """
    tc = TrustChainBuilder(
        subject,
        trust_anchor=trust_anchor,
        required_trust_marks=required_trust_marks,
        httpc_params=HTTPC_PARAMS,
        metadata_type = metadata_type
    )
    tc.start()

    if not tc.is_valid:
        logger.error(
            "The tree of trust cannot be validated for "
            f"{tc.subject}: {tc.tree_of_trust}"
        )
        return False
    else:
        return tc

spid_cie_oidc/entity/statements.py METASEP
from .exceptions import UnknownKid, MissingJwksClaim
from .http_client import http_get
from .jwtse import verify_jws, unpad_jwt_head, unpad_jwt_payload

import asyncio
import json
import logging
import requests


try:
    from django.conf import settings
except ImportError:
    from . import settings


OIDCFED_FEDERATION_WELLKNOWN_URL = ".well-known/openid-federation"
logger = logging.getLogger(__name__)


def jwks_from_jwks_uri(jwks_uri: str, httpc_params: dict = {}) -> list:
    return [json.loads(asyncio.run(http_get([jwks_uri], httpc_params)))]


def get_jwks(jwt_payload: dict, httpc_params: dict = {}):
    return (
        jwt_payload.get("jwks", {}).get('keys', [])
        # TODO: we must only support signed_jwks_uri
        # or jwks_from_jwks_uri(jwks_uri, httpc_params)
    )


def get_http_url(urls: list, httpc_params: dict = {}) -> list:
    if getattr(settings, "HTTP_CLIENT_SYNC", False):
        responses = []
        for i in urls:
            res = requests.get(i, **httpc_params)
            responses.append(res.content.decode())
    else:
        responses = asyncio.run(http_get(urls, httpc_params))

    return responses


def get_entity_statements(urls: list, httpc_params: dict = {}) -> list:
    """
    Fetches an entity statement/configuration
    """
    if isinstance(urls, str):
        urls = [urls]
    for url in urls:
        logger.debug(f"Starting Entity Statement Request to {url}")
    return get_http_url(urls, httpc_params)


def get_entity_configurations(subjects: list, httpc_params: dict = {}):
    if isinstance(subjects, str):
        subjects = [subjects]
    urls = []
    for subject in subjects:
        if subject[-1] != "/":
            subject = f"{subject}/"
        url = f"{subject}{OIDCFED_FEDERATION_WELLKNOWN_URL}"
        urls.append(url)
        logger.info(f"Starting Entity Configuration Request for {url}")
    return get_http_url(urls, httpc_params)


class EntityConfiguration:
    """
    The self issued/signed statement of a federation entity
    """

    def __init__(
        self,
        jwt: str,
        httpc_params: dict = {},
        filter_by_allowed_trust_marks: list = [],
        trust_anchors_entity_confs: dict = [],
        trust_mark_issuers_entity_confs: dict = [],
    ):
        self.jwt = jwt
        self.header = unpad_jwt_head(jwt)
        self.payload = unpad_jwt_payload(jwt)
        self.sub = self.payload["sub"]
        self.jwks = get_jwks(self.payload, httpc_params)
        if not self.jwks[0]:
            _msg = f"Missing jwks in the statement for {self.sub}"
            logger.error(_msg)
            raise MissingJwksClaim(_msg)

        self.kids = [i.get("kid") for i in self.jwks]
        self.httpc_params = httpc_params

        self.filter_by_allowed_trust_marks = filter_by_allowed_trust_marks
        self.trust_anchors_entity_confs = trust_anchors_entity_confs
        self.trust_mark_issuers_entity_confs = trust_mark_issuers_entity_confs

        # a dict with sup_sub : superior entity configuration
        self.verified_superiors = {}
        # as previous but with superiors with invalid entity configurations
        self.failed_superiors = {}

        # a dict with sup_sub : entity statement issued for self
        self.verified_by_superiors = {}
        self.failed_by_superiors = {}

        # a dict with the paylaod of valid entity statements for each descendant subject
        self.verified_descendant_statements = {}
        self.failed_descendant_statements = {}

        self.is_valid = False

    def validate_by_itself(self) -> bool:
        """
        validates the entity configuration by it self
        """
        # TODO: pydantic entity configuration validation here
        if self.header.get("kid") not in self.kids:
            raise UnknownKid(f"{self.header.get('kid')} not found in {self.jwks}")
        # verify signature
        verify_jws(self.jwt, self.jwks[self.kids.index(self.header["kid"])])
        self.is_valid = True
        return True

    def get_valid_trust_marks(self) -> bool:
        """
        validate the entity configuration ony if marked by a well known
        trust mark, issued by a trusted issuer
        """
        # if not self.filter_by_allowed_trust_marks:
        # return True
        # TODO
        raise NotImplementedError()

    def get_superiors(
        self, authority_hints: list = [], max_authority_hints: int = 0,
        superiors_hints: list = []
    ) -> dict:
        """
        get superiors entity configurations
        """
        # apply limits if defined
        authority_hints = authority_hints or self.payload.get("authority_hints", [])
        if (
            max_authority_hints
            and authority_hints != authority_hints[:max_authority_hints]
        ):
            logger.warning(
                f"Found {len(authority_hints)} but "
                f"authority maximum hints is set to {max_authority_hints}. "
                "the following authorities will be ignored: "
                f"{', '.join(authority_hints[max_authority_hints:])}"
            )
            authority_hints = authority_hints[:max_authority_hints]

        for sup in superiors_hints:
            if sup.sub in authority_hints:
                logger.info(
                    "Getting Cached Entity Configurations for "
                    f"{[i.sub for i in superiors_hints]}"
                )
                authority_hints.pop(authority_hints.index(sup.sub))
                self.verified_superiors[sup.sub] = sup

        logger.debug(f"Getting Entity Configurations for {authority_hints}")

        jwts = get_entity_configurations(authority_hints, self.httpc_params)
        for jwt in jwts:
            ec = self.__class__(jwt, httpc_params=self.httpc_params)

            if ec.validate_by_itself():
                target = self.verified_superiors
            else:
                target = self.failed_superiors

            target[ec.payload["sub"]] = ec

        for ahints in authority_hints:
            ec = target[ahints]
            # TODO: this is a copy/pasted code with the previous for statement
            # TODO: it must be generalized and merged with the previous one
            if ec.validate_by_itself():
                target = self.verified_superiors
            else:
                target = self.failed_superiors

            target[ec.payload["sub"]] = ec

        return self.verified_superiors

    def validate_descendant_statement(self, jwt: str) -> bool:
        """
        jwt is a descendant entity statement issued by self
        """
        # TODO: pydantic entity configuration validation here
        header = unpad_jwt_head(jwt)
        payload = unpad_jwt_payload(jwt)

        if header.get("kid") not in self.kids:
            raise UnknownKid(
                f"{self.header.get('kid')} not found in {self.jwks}"
            )
        # verify signature
        payload = verify_jws(jwt, self.jwks[self.kids.index(header["kid"])])

        self.verified_descendant_statements[payload['sub']] = payload
        return self.verified_descendant_statements

    def validate_by_superior_statement(self, jwt: str, ec):
        """
        jwt is a statement issued by a superior
        ec is a superior entity configuration

        this method validates self with the jwks contained in statement
        of the superior
        """
        is_valid = None
        payload = {}
        try:
            payload = unpad_jwt_payload(jwt)
            ec.validate_by_itself()
            ec.validate_descendant_statement(jwt)
            _jwks = get_jwks(payload, self.httpc_params)
            _kids = [i.get("kid") for i in _jwks]
            verify_jws(self.jwt, _jwks[_kids.index(self.header["kid"])])
            is_valid = True
        except Exception as e:
            logger.warning(
                f"{self.sub} failed validation with "
                f"{ec.sub}'s superior statement {payload}. "
                f"Exception: {e}"
            )
            is_valid = False

        if is_valid:
            target = self.verified_by_superiors
            ec.verified_descendant_statements[self.sub] = payload
        else:
            target = self.failed_superiors
            ec.failed_descendant_statements[self.sub] = payload

        target[payload["iss"]] = ec
        return self.verified_by_superiors.get(ec.sub)

    def validate_by_superiors(
        self,
        superiors_entity_configurations: dict = {},
    ):  # -> dict[str, EntityConfiguration]:
        """
        validates the entity configuration with the entity statements
        issued by its superiors

        this methods create self.verified_superiors and failed ones
        and self.verified_by_superiors and failed ones
        """
        for ec in superiors_entity_configurations:
            if ec.sub in ec.verified_by_superiors:
                # already featched and cached
                continue

            try:
                # get superior fetch url
                fetch_api_url = ec.payload["metadata"]["federation_entity"][
                    "federation_api_endpoint"
                ]
            except KeyError:
                logger.warning(
                    "Missing federation_api_endpoint in  "
                    f"federation_entity metadata for {self.sub} by {ec.sub}."
                )
                self.invalid_superiors[ec.sub] = None
                continue

            else:
                logger.info(
                    f"Getting entity statements from {fetch_api_url}  for "
                    f"{self.sub}"
                )
                jwts = get_entity_statements([fetch_api_url], self.httpc_params)
                jwt = jwts[0]
                self.validate_by_superior_statement(jwt, ec)

        return self.verified_by_superiors

    def __repr__(self) -> str:
        return f"{self.sub} valid {self.is_valid}"

spid_cie_oidc/entity/settings.py METASEP
import aiohttp

DEFAULT_JWE_ENC = "A256CBC-HS512"
ENCRYPTION_ENC_SUPPORTED = [
    "A128CBC-HS256",
    "A192CBC-HS384",
    "A256CBC-HS512",
    "A128GCM",
    "A192GCM",
    "A256GCM",
]

DEFAULT_JWS_ALG = "RS256"
SIGNING_ALG_VALUES_SUPPORTED = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]

DEFAULT_JWE_ALG = "RSA-OAEP"
ENCRYPTION_ALG_VALUES_SUPPORTED = [
    "RSA-OAEP",
    "RSA-OAEP-256",
    "ECDH-ES",
    "ECDH-ES+A128KW",
    "ECDH-ES+A192KW",
    "ECDH-ES+A256KW",
]

DEFAULT_HASH_FUNC = "SHA-256"

# This is required in general project settings
# OIDCFED_FEDERATION_TRUST_ANCHORS = [https://..., ]

OIDCFED_ALLOWED_TRUST_MARKS = []
OIDCFED_FILTER_BY_TRUST_MARKS = True

# the metadata discovery will only processes the first MAXIMUM_AUTHORITY_HINTS
OIDCFED_MAXIMUM_AUTHORITY_HINTS = 2
OIDCFED_MAX_PATH_LEN = 1

# old, for requests
# HTTPC_PARAMS = {
# "verify": True,
# "timeout": 4
# }

# for aiohttp
HTTPC_PARAMS = {
    "connection": {"ssl": True},
    "session": {"timeout": aiohttp.ClientTimeout(total=4)},
}

FEDERATION_DEFAULT_EXP = 2880

spid_cie_oidc/entity/policy.py METASEP
__author__ = "Roland Hedberg"
__license__ = "Apache 2.0"
__version__ = ""

import logging

logger = logging.getLogger(__name__)


class PolicyError(Exception):
    pass


def combine_subset_of(s1, s2):
    return list(set(s1).intersection(set(s2)))


def combine_superset_of(s1, s2):
    return list(set(s1).intersection(set(s2)))


def combine_one_of(s1, s2):
    return list(set(s1).intersection(set(s2)))


def combine_add(s1, s2):
    if isinstance(s1, list):
        set1 = set(s1)
    else:
        set1 = {s1}
    if isinstance(s2, list):
        set2 = set(s2)
    else:
        set2 = {s2}
    return list(set1.union(set2))


POLICY_FUNCTIONS = {
    "subset_of",
    "superset_of",
    "one_of",
    "add",
    "value",
    "default",
    "essential",
}

OP2FUNC = {
    "subset_of": combine_subset_of,
    "superset_of": combine_superset_of,
    "one_of": combine_one_of,
    "add": combine_add,
}


def do_sub_one_super_add(superior, child, policy):
    if policy in superior and policy in child:
        comb = OP2FUNC[policy](superior[policy], child[policy])
        if comb:
            return comb
        else:
            raise PolicyError("Value sets doesn't overlap")
    elif policy in superior:
        return superior[policy]
    elif policy in child:
        return child[policy]


def do_value(superior, child, policy):
    if policy in superior and policy in child:
        if superior[policy] == child[policy]:
            return superior[policy]
        else:
            raise PolicyError("Not allowed to combine values")
    elif policy in superior:
        return superior[policy]
    elif policy in child:
        return child[policy]


def do_default(superior, child, policy):
    # A child's default can not override a superiors
    if policy in superior and policy in child:
        if superior["default"] == child["default"]:
            return superior["default"]
        else:
            raise PolicyError("Not allowed to change default")
    elif policy in superior:
        return superior[policy]
    elif policy in child:
        return child[policy]


def do_essential(superior, child, policy):
    # essential: an child can make it True if a superior has states False
    # but not the other way around

    if policy in superior and policy in child:
        if not superior[policy] and child["essential"]:
            return True
        else:
            return superior[policy]
    elif policy in superior:
        return superior[policy]
    elif policy in child:  # Not in superior is the same as essential=True
        return True


DO_POLICY = {
    "superset_of": do_sub_one_super_add,
    "subset_of": do_sub_one_super_add,
    "one_of": do_sub_one_super_add,
    "add": do_sub_one_super_add,
    "value": do_value,
    "default": do_default,
    "essential": do_essential,
}


def combine_claim_policy(superior, child):
    """
    Combine policy rules.
    Applying the child policy can only make the combined policy more restrictive.
    :param superior: Superior policy
    :param child: Intermediates policy
    """

    # weed out everything I don't recognize
    superior_set = set(superior).intersection(POLICY_FUNCTIONS)
    child_set = set(child).intersection(POLICY_FUNCTIONS)

    if "value" in superior_set:  # An exact value can not be restricted.
        if child_set:
            if "essential" in child_set:
                if len(child_set) == 1:
                    return {"value": superior["value"], "essential": child["essential"]}
                else:
                    raise PolicyError(
                        "value can only be combined with essential, not {}".format(
                            child_set
                        )
                    )
            elif "value" in child_set:
                if child["value"] != superior["value"]:  # Not OK
                    raise PolicyError("Child can not set another value then superior")
                else:
                    return superior
            else:
                raise PolicyError(
                    "Not allowed combination of policies: {} + {}".format(
                        superior, child
                    )
                )
        return superior
    else:
        if "essential" in superior_set and "essential" in child_set:
            # can only go from False to True
            if (
                superior["essential"] != child["essential"]
                and child["essential"] is False
            ):
                raise PolicyError("Essential can not go from True to False")

        comb_policy = superior_set.union(child_set)
        if "one_of" in comb_policy:
            if "subset_of" in comb_policy or "superset_of" in comb_policy:
                raise PolicyError(
                    "one_of can not be combined with subset_of/superset_of"
                )

        rule = {}
        for policy in comb_policy:
            rule[policy] = DO_POLICY[policy](superior, child, policy)

        if comb_policy == {"superset_of", "subset_of"}:
            # make sure the subset_of is a superset of superset_of.
            if set(rule["superset_of"]).difference(set(rule["subset_of"])):
                raise PolicyError("superset_of not a super set of subset_of")
        elif comb_policy == {"superset_of", "subset_of", "default"}:
            # make sure the subset_of is a superset of superset_of.
            if set(rule["superset_of"]).difference(set(rule["subset_of"])):
                raise PolicyError("superset_of not a super set of subset_of")
            if set(rule["default"]).difference(set(rule["subset_of"])):
                raise PolicyError("default not a sub set of subset_of")
            if set(rule["superset_of"]).difference(set(rule["default"])):
                raise PolicyError("default not a super set of subset_of")
        elif comb_policy == {"subset_of", "default"}:
            if set(rule["default"]).difference(set(rule["subset_of"])):
                raise PolicyError("default not a sub set of subset_of")
        elif comb_policy == {"superset_of", "default"}:
            if set(rule["superset_of"]).difference(set(rule["default"])):
                raise PolicyError("default not a super set of subset_of")
        elif comb_policy == {"one_of", "default"}:
            if isinstance(rule["default"], list):
                if set(rule["default"]).difference(set(rule["one_of"])):
                    raise PolicyError("default not a super set of one_of")
            else:
                if {rule["default"]}.difference(set(rule["one_of"])):
                    raise PolicyError("default not a super set of one_of")
    return rule


def combine_policy(superior, child):
    res = {}
    sup_set = set(superior.keys())
    chi_set = set(child.keys())

    for claim in set(sup_set).intersection(chi_set):
        res[claim] = combine_claim_policy(superior[claim], child[claim])

    for claim in sup_set.difference(chi_set):
        res[claim] = superior[claim]

    for claim in chi_set.difference(sup_set):
        res[claim] = child[claim]

    return res


def gather_policies(chain, entity_type):
    """
    Gather and combine all the metadata policies that are defined in the trust chain
    :param chain: A list of Entity Statements
    :return: The combined metadata policy
    """

    try:
        combined_policy = chain[0]["metadata_policy"][entity_type]
    except KeyError:
        combined_policy = {}

    for es in chain[1:]:
        try:
            child = es["metadata_policy"][entity_type]
        except KeyError:
            pass
        else:
            combined_policy = combine_policy(combined_policy, child)

    return combined_policy


def union(val1, val2):
    if isinstance(val1, list):
        base = set(val1)
    else:
        base = {val1}

    if isinstance(val2, list):
        ext = set(val2)
    else:
        ext = {val2}
    return base.union(ext)


def apply_policy(metadata, policy):
    """
    Apply a metadata policy to a metadata statement.
    The order is value, add, default and then the checks subset_of/superset_of and one_of
    :param metadata: A metadata statement
    :param policy: A metadata policy
    :return: A metadata statement that adheres to a metadata policy
    """

    metadata_set = set(metadata.keys())
    policy_set = set(policy.keys())

    # Metadata claims that there exists a policy for
    for claim in metadata_set.intersection(policy_set):
        if "value" in policy[claim]:  # value overrides everything
            metadata[claim] = policy[claim]["value"]
        else:
            if "one_of" in policy[claim]:
                # The is for claims that can have only one value
                if isinstance(metadata[claim], list):  # Should not be but ...
                    _claim = [
                        c for c in metadata[claim] if c in policy[claim]["one_of"]
                    ]
                    if _claim:
                        metadata[claim] = _claim[0]
                    else:
                        raise PolicyError(
                            "{}: None of {} among {}".format(
                                claim, metadata[claim], policy[claim]["one_of"]
                            )
                        )
                else:
                    if metadata[claim] in policy[claim]["one_of"]:
                        pass
                    else:
                        raise PolicyError(
                            "{} not among {}".format(
                                metadata[claim], policy[claim]["one_of"]
                            )
                        )
            else:
                # The following is for claims that can have lists of values
                if "add" in policy[claim]:
                    metadata[claim] = list(union(metadata[claim], policy[claim]["add"]))

                if "subset_of" in policy[claim]:
                    _val = set(policy[claim]["subset_of"]).intersection(
                        set(metadata[claim])
                    )
                    if _val:
                        metadata[claim] = list(_val)
                    else:
                        raise PolicyError(
                            "{} not subset of {}".format(
                                metadata[claim], policy[claim]["subset_of"]
                            )
                        )
                if "superset_of" in policy[claim]:
                    if set(policy[claim]["superset_of"]).difference(
                        set(metadata[claim])
                    ):
                        raise PolicyError(
                            "{} not superset of {}".format(
                                metadata[claim], policy[claim]["superset_of"]
                            )
                        )
                    else:
                        pass

    # In policy but not in metadata
    for claim in policy_set.difference(metadata_set):
        if "value" in policy[claim]:
            metadata[claim] = policy[claim]["value"]
        elif "add" in policy[claim]:
            metadata[claim] = policy[claim]["add"]
        elif "default" in policy[claim]:
            metadata[claim] = policy[claim]["default"]

        if claim not in metadata:
            if "essential" in policy[claim] and policy[claim]["essential"]:
                raise PolicyError("Essential claim '{}' missing".format(claim))

    # All that are in metadata but not in policy should just remain

    return metadata


def diff2policy(new, old):
    res = {}
    for claim in set(new).intersection(set(old)):
        if new[claim] == old[claim]:
            continue
        else:
            res[claim] = {"value": new[claim]}

    for claim in set(new).difference(set(old)):
        if claim in ["contacts"]:
            res[claim] = {"add": new[claim]}
        else:
            res[claim] = {"value": new[claim]}

    return res

spid_cie_oidc/entity/models.py METASEP
from cryptojwt.jwk.jwk import key_from_jwk_dict
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext as _

from spid_cie_oidc.entity.abstract_models import TimeStampedModel
from spid_cie_oidc.entity.jwks import (
    create_jwk,
    serialize_rsa_key,
    private_pem_from_jwk,
    public_pem_from_jwk,
)
from spid_cie_oidc.entity.jwtse import create_jws
from spid_cie_oidc.entity.validators import validate_public_jwks
from spid_cie_oidc.entity.utils import exp_from_now, iat_now


import json
import logging
import spid_cie_oidc.entity.settings as local_settings
import uuid


ENTITY_TYPE_LEAFS = ["openid_relying_party", "openid_provider", "oauth_resource"]

ENTITY_TYPES = ["federation_entity"] + ENTITY_TYPE_LEAFS

ENTITY_STATUS = {
    "unreachable": False,
    "valid": True,
    "signature_failed": False,
    "not_valid": False,
    "unknown": None,
    "expired": None,
}
FEDERATION_DEFAULT_EXP = getattr(
    settings, "FEDERATION_DEFAULT_EXP", local_settings.FEDERATION_DEFAULT_EXP
)
logger = logging.getLogger(__name__)


def is_leaf(statement_metadata):
    for _typ in ENTITY_TYPE_LEAFS:
        if _typ in statement_metadata:
            return True


class FederationEntityConfiguration(TimeStampedModel):
    """
    Federation Authority configuration.
    """

    def validate_entity_metadata(value):
        status = False
        for i in ENTITY_TYPES:
            if i in value:
                status = True
        if not status:
            raise ValidationError(
                _(f'Need to specify one of {", ".join(ENTITY_TYPES)}')
            )

    def _create_jwks():
        return [create_jwk()]

    uuid = models.UUIDField(
        blank=False, null=False, default=uuid.uuid4, unique=True, editable=False
    )
    sub = models.URLField(
        max_length=255,
        blank=False,
        null=False,
        help_text=_(
            "URL that identifies this Entity in the Federation. "
            "This value and iss are the same in the Entity Configuration."
        ),
    )
    default_exp = models.PositiveIntegerField(
        default=FEDERATION_DEFAULT_EXP,
        help_text=_("how many minutes from now() an issued statement must expire"),
    )
    default_signature_alg = models.CharField(
        max_length=16,
        default="RS256",
        blank=False,
        null=False,
        help_text=_("default signature algorithm, eg: RS256"),
    )
    authority_hints = models.JSONField(
        blank=True,
        null=False,
        help_text=_("only required if this Entity is an intermediary or leaf."),
        default=list,
    )
    jwks = models.JSONField(
        blank=False,
        null=False,
        help_text=_("a list of public keys"),
        default=_create_jwks,
    )
    trust_marks = models.JSONField(
        blank=True,
        help_text=_("which trust marks MUST be exposed in its entity configuration"),
        default=list,
    )
    trust_marks_issuers = models.JSONField(
        blank=True,
        help_text=_(
            "Only usable for Trust Anchors and intermediates. "
            "Which issuers are allowed to issue trust marks for the descendants. "
            'Example: {"https://www.spid.gov.it/certification/rp": '
            '["https://registry.spid.agid.gov.it", "https://intermediary.spid.it"],'
            '"https://sgd.aa.it/onboarding": ["https://sgd.aa.it", ]}'
        ),
        default=dict,
    )
    metadata = models.JSONField(
        blank=False,
        null=False,
        help_text=_(
            "federation_entity metadata, eg: "
            '{"federation_entity": { ... },'
            '"openid_provider": { ... },'
            '"openid_relying_party": { ... },'
            '"oauth_resource": { ... }'
            "}"
        ),
        default=dict,
        validators=[
            validate_entity_metadata,
        ],
    )
    constraints = models.JSONField(
        blank=True,
        help_text=_(
            """
{
  "naming_constraints": {
    "permitted": [
      "https://.example.com"
    ],
    "excluded": [
      "https://east.example.com"
    ]
  },
  "max_path_length": 2
}
"""
        ),
        default=dict,
        # TODO
        # validators=[validate_entity_metadata,]
    )
    is_active = models.BooleanField(
        default=False,
        help_text=_(
            "If this configuration is active. "
            "At least one configuration must be active"
        ),
    )

    class Meta:
        verbose_name = "Federation Entity Configuration"
        verbose_name_plural = "Federation Entity Configurations"

    @classmethod
    def get_active_conf(cls):
        """
        returns the first available active acsia engine configuration found
        """
        return cls.objects.filter(is_active=True).first()

    @property
    def public_jwks(self):
        res = []
        for i in self.jwks:
            skey = serialize_rsa_key(key_from_jwk_dict(i).public_key())
            skey["kid"] = i["kid"]
            res.append(skey)
        return res

    @property
    def pems_as_dict(self):
        res = {}
        for i in self.jwks:
            res[i["kid"]] = {
                "private": private_pem_from_jwk(i),
                "public": public_pem_from_jwk(i),
            }
        return res

    @property
    def pems_as_json(self):
        return json.dumps(self.pems_as_dict, indent=2)

    @property
    def kids(self) -> list:
        return [i["kid"] for i in self.jwks]

    @property
    def type(self) -> list:
        return [i for i in self.metadata.keys()]

    @property
    def is_leaf(self):
        return is_leaf(self.metadata)

    @property
    def entity_configuration_as_dict(self):
        conf = {
            "exp": exp_from_now(self.default_exp),
            "iat": iat_now(),
            "iss": self.sub,
            "sub": self.sub,
            "jwks": {"keys": self.public_jwks},
            "metadata": self.metadata,
        }

        if self.trust_marks_issuers:
            conf["trust_marks_issuers"] = self.trust_marks_issuers

        if self.constraints:
            conf["constraints"] = self.constraints

        if self.authority_hints:
            conf["authority_hints"] = self.authority_hints
        elif self.is_leaf:
            _msg = (
                f"Entity {self.sub} is a leaf and requires authority_hints valued"
            )
            logger.error(_msg)

        return conf

    @property
    def entity_configuration_as_json(self):
        return json.dumps(self.entity_configuration_as_dict)

    @property
    def entity_configuration_as_jws(self, **kwargs):
        return create_jws(
            self.entity_configuration_as_dict,
            self.jwks[0],
            alg=self.default_signature_alg,
            **kwargs
        )

    def __str__(self):
        return "{} [{}]".format(self.sub, "active" if self.is_active else "--")


class PublicJwk(TimeStampedModel):

    kid = models.CharField(
        max_length=1024,
        unique=True,
        help_text=_("unique code that identifies this jwks. "),
    )
    jwk = models.JSONField(
        blank=False,
        null=False,
        help_text=_("Public jwk"),
        default=dict,
        validators=[validate_public_jwks],
    )

    class Meta:
        verbose_name = "Public JWKs"
        verbose_name_plural = "Public JWKs"

    @property
    def jwk_as_json(self):
        return json.dumps(self.jwk)

    def __str__(self):
        return f"{self.kid}"


class FetchedEntityStatement(TimeStampedModel):
    """
    Entity Statement acquired by a third party
    """

    iss = models.URLField(
        max_length=255,
        blank=False,
        help_text=_(
            "URL that identifies the issuer of this statement in the Federation. "
        ),
    )
    sub = models.URLField(
        max_length=255,
        blank=False,
        help_text=_("URL that identifies this Entity in the Federation. "),
    )
    exp = models.DateTimeField()
    iat = models.DateTimeField()

    statement = models.JSONField(
        blank=False, null=False, help_text=_("Entity statement"), default=dict
    )

    class Meta:
        verbose_name = "Fetched Entity Statement"
        verbose_name_plural = "Fetched Entity Statement"

    def __str__(self):
        return f"{self.sub} issued by {self.iss}"


class TrustChain(TimeStampedModel):
    """
    Federation Trust Chain
    """

    sub = models.URLField(
        max_length=255,
        blank=False,
        help_text=_("URL that identifies this Entity in the Federation. "),
    )
    type = models.CharField(
        max_length=33,
        blank=True,
        default="openid_provider",
        choices=[(i, i) for i in ENTITY_TYPES],
        help_text=_("OpenID Connect Federation entity type"),
    )
    exp = models.DateTimeField()
    iat = models.DateTimeField()
    chain = models.JSONField(
        blank=True,
        help_text=_(
            "A list of entity statements collected during the metadata discovery"
        ),
        default=list,
    )
    resultant_metadata = models.JSONField(
        blank=False,
        null=False,
        help_text=_(
            "The final metadata applied with the metadata policy built over the chain"
        ),
        default=dict,
    )
    parties_involved = models.JSONField(
        blank=True,
        help_text=_("subjects involved in the metadata discovery"),
        default=list,
    )
    status = models.CharField(
        max_length=33,
        default=False,
        help_text=_("Status of this trust chain, on each update."),
        choices=[(i, i) for i in ENTITY_STATUS.keys()],
    )
    status_log = models.JSONField(
        blank=True,
        help_text=_("status log"),
        default=dict,
    )
    is_active = models.BooleanField(
        default=True,
        help_text=_(
            "If you need to disable the trust to this subject, deacticate this"
        ),
    )

    class Meta:
        verbose_name = "Trust Chain"
        verbose_name_plural = "Trust Chains"
        unique_together = ("sub", "type")

    @property
    def is_valid(self):
        return self.is_active and ENTITY_STATUS[self.status]

    def __str__(self):
        return "{} [{}] [{}]".format(self.sub, self.type, self.is_valid)

spid_cie_oidc/entity/jwtse.py METASEP
import base64
import binascii
import json
import logging

from cryptojwt.exception import UnsupportedAlgorithm, VerificationError
from cryptojwt.jwe.jwe import factory
from cryptojwt.jwe.jwe_rsa import JWE_RSA
from cryptojwt.jwk.jwk import key_from_jwk_dict
from cryptojwt.jws.jws import JWS
from django.conf import settings

from . import settings as local_settings

DEFAULT_JWS_ALG = getattr(settings, "DEFAULT_JWS_ALG", local_settings.DEFAULT_JWS_ALG)
DEFAULT_JWE_ALG = getattr(settings, "DEFAULT_JWE_ALG", local_settings.DEFAULT_JWE_ALG)
DEFAULT_JWE_ENC = getattr(settings, "DEFAULT_JWE_ENC", local_settings.DEFAULT_JWE_ENC)
SIGNING_ALG_VALUES_SUPPORTED = getattr(
    settings,
    "SIGNING_ALG_VALUES_SUPPORTED",
    local_settings.SIGNING_ALG_VALUES_SUPPORTED,
)
ENCRYPTION_ALG_VALUES_SUPPORTED = getattr(
    settings,
    "ENCRYPTION_ALG_VALUES_SUPPORTED",
    local_settings.ENCRYPTION_ALG_VALUES_SUPPORTED,
)

logger = logging.getLogger(__name__)


def unpad_jwt_element(jwt: str, position: int) -> dict:
    b = jwt.split(".")[position]
    padded = f"{b}{'=' * divmod(len(b),4)[1]}"
    data = json.loads(base64.b64decode(padded))
    return data


def unpad_jwt_head(jwt: str) -> dict:
    return unpad_jwt_element(jwt, position=0)


def unpad_jwt_payload(jwt: str) -> dict:
    return unpad_jwt_element(jwt, position=1)


def encrypt_dict(plain_dict:dict, jwk_dict:dict) -> str:
    logger.debug(f"Encrypting dict as JWE: " f"{plain_dict}")
    _key = key_from_jwk_dict(jwk_dict)
    _rsa = JWE_RSA(
        json.dumps(plain_dict).encode(),
        alg=DEFAULT_JWE_ALG,
        enc=DEFAULT_JWE_ENC,
        kid=_key.kid,
    )
    jwe = _rsa.encrypt(_key.public_key())
    logger.debug(f"Encrypted dict as JWE: {jwe}")
    return jwe


def decrypt_jwe(jwe:str, jwk_dict:dict) -> dict:
    # get header
    try:
        jwe_header = unpad_jwt_head(jwe)
    except (binascii.Error, Exception) as e:
        logger.error(f"Failed to extract JWT header: {e}")
        raise VerificationError("The JWT is not valid")

    _alg = jwe_header.get("alg", DEFAULT_JWE_ALG)
    _enc = jwe_header.get("enc", DEFAULT_JWE_ENC)
    jwe_header.get("kid")

    if _alg not in ENCRYPTION_ALG_VALUES_SUPPORTED:
        raise UnsupportedAlgorithm(f"{_alg} has beed disabled for security reason")

    _decryptor = factory(jwe, alg=_alg, enc=_enc)

    # _dkey = RSAKey(priv_key=PRIV_KEY)
    _dkey = key_from_jwk_dict(jwk_dict)
    msg = _decryptor.decrypt(jwe, [_dkey])

    msg_dict = json.loads(msg)
    logger.debug(f"Decrypted JWT as: {json.dumps(msg_dict, indent=2)}")
    return msg_dict


def create_jws(
    payload: dict, jwk_dict: dict, alg: str = "RS256", **kwargs
) -> str:

    _key = key_from_jwk_dict(jwk_dict)
    _signer = JWS(payload, alg=alg, **kwargs)

    signature = _signer.sign_compact([_key])
    return signature


def verify_jws(jws: str, pub_jwk: dict, **kwargs) -> str:
    _key = key_from_jwk_dict(pub_jwk)

    _head = unpad_jwt_head(jws)
    if _head.get("kid") != pub_jwk["kid"]:
        raise Exception(f"kid error: {_head.get('kid')} != {pub_jwk['kid']}")

    _alg = _head["alg"]
    if _alg not in SIGNING_ALG_VALUES_SUPPORTED or not _alg:
        raise UnsupportedAlgorithm(
            f"{_alg} has beed disabled for security reason"
        )

    verifier = JWS(alg=_head["alg"], **kwargs)
    msg = verifier.verify_compact(jws, [_key])
    return msg

spid_cie_oidc/entity/jwks.py METASEP
from cryptojwt.jwk.jwk import key_from_jwk_dict
from cryptojwt.jwk.rsa import new_rsa_key
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptojwt.jwk.rsa import RSAKey, import_public_key_from_pem_data

import cryptography
from django.conf import settings

from . import settings as local_settings


DEFAULT_HASH_FUNC = getattr(
    settings, "DEFAULT_HASH_FUNC", local_settings.DEFAULT_HASH_FUNC
)


def create_jwk(hash_func=None):
    key = new_rsa_key()
    thumbprint = key.thumbprint(hash_function=hash_func or DEFAULT_HASH_FUNC)
    jwk = key.to_dict()
    jwk["kid"] = thumbprint.decode()
    return jwk


def private_pem_from_jwk(jwk_dict: dict):
    # exports private

    _k = key_from_jwk_dict(jwk_dict)
    pk = _k.private_key()
    pem = pk.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption(),
    )
    return pem.decode()


def public_pem_from_jwk(jwk_dict: dict):
    # exports private

    _k = key_from_jwk_dict(jwk_dict)
    pk = _k.public_key()
    cert = pk.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )
    return cert.decode()


def serialize_rsa_key(rsa_key, kind="public", hash_func="SHA-256"):
    """
    rsa_key can be
        cryptography.hazmat.backends.openssl.rsa._RSAPublicKey
        or
        cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey
    """
    if isinstance(rsa_key, cryptography.hazmat.backends.openssl.rsa._RSAPublicKey):
        data = {"pub_key": rsa_key}
    elif isinstance(rsa_key, cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey):
        data = {"priv_key": rsa_key}
    elif isinstance(rsa_key, (str, bytes)):
        if kind == "private":
            data = {
                "priv_key": serialization.load_pem_private_key(
                    rsa_key, password=None, backend=default_backend()
                )
            }
        else:
            _rsa_key = rsa_key.decode() if isinstance(rsa_key, bytes) else rsa_key
            data = {"pub_key": import_public_key_from_pem_data(_rsa_key)}

    jwk_obj = RSAKey(**data)
    thumbprint = jwk_obj.thumbprint(hash_function=hash_func)

    jwk = jwk_obj.to_dict()
    jwk["kid"] = thumbprint.decode()
    return jwk

spid_cie_oidc/entity/http_client.py METASEP
import aiohttp
import asyncio


async def fetch(session, url, httpc_params: dict = {}):
    async with session.get(url, **httpc_params.get("connection", {})) as response:
        if response.status != 200:
            # response.raise_for_status()
            return ""
        return await response.text()


async def fetch_all(session, urls, httpc_params):
    tasks = []
    for url in urls:
        task = asyncio.create_task(fetch(session, url, httpc_params))
        tasks.append(task)
    results = await asyncio.gather(*tasks)
    return results


async def http_get(urls, httpc_params: dict = {}):
    async with aiohttp.ClientSession(**httpc_params.get("session", {})) as session:
        text = await fetch_all(session, urls, httpc_params)
        return text


if __name__ == "__main__":
    httpc_params = {
        "connection": {"ssl": True},
        "session": {"timeout": aiohttp.ClientTimeout(total=4)},
    }
    urls = [
        "http://127.0.0.1:8001/.well-known/openid-federation",
        "http://127.0.0.1:8000/.well-known/openid-federation",
        "http://asdasd.it",
        "http://127.0.0.1:8001/.well-known/openid-federation",
        "http://google.it",
    ]
    asyncio.run(http_get(urls, httpc_params=httpc_params))

spid_cie_oidc/entity/exceptions.py METASEP
from django.core.exceptions import ValidationError


class HttpError(Exception):
    pass


class TrustChainHttpError(HttpError):
    pass


class UnknownKid(Exception):
    pass


class MissingJwksClaim(ValidationError):
    pass


class MissingAuthorityHintsClaim(ValidationError):
    pass


class NotDescendant(ValidationError):
    pass

spid_cie_oidc/entity/apps.py METASEP
from django.apps import AppConfig


class SpidCieFederationEntityConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "spid_cie_oidc.entity"
    verbose_name = "SPID/CIE OIDC Federation Entity"
    label = "spid_cie_oidc_entity"

spid_cie_oidc/entity/admin.py METASEP
from django.contrib import admin
from django.utils.safestring import mark_safe

# from prettyjson import PrettyJSONWidget


from .models import (
    FederationEntityConfiguration,
    FetchedEntityStatement,
    TrustChain,
    PublicJwk,
)


@admin.register(FederationEntityConfiguration)
class FederationEntityConfigurationAdmin(admin.ModelAdmin):
    # formfield_overrides = {
    # JSONField: {
    # "widget": PrettyJSONWidget(
    # attrs={"initial": "parsed", "disabled": True}
    # )
    # }
    # }
    list_display = (
        "sub",
        "type",
        "kids",
        "is_active",
        "created",
    )
    list_filter = ("created", "modified", "is_active")
    # search_fields = ('command__name',)
    readonly_fields = (
        "created",
        "modified",
        "entity_configuration_as_json",
        "pems_as_html",
        "kids",
        "type",
    )

    def pems_as_html(self, obj):
        res = ""
        data = dict()
        for k, v in obj.pems_as_dict.items():
            data[k] = {}
            for i in ("public", "private"):
                data[k][i] = v[i].replace("\n", "<br>")
            res += (
                f"<b>{k}</b><br><br>"
                f"{data[k]['public']}<br>"
                f"{data[k]['private']}<br><hr>"
            )
        return mark_safe(res) # nosec


@admin.register(TrustChain)
class TrustChainAdmin(admin.ModelAdmin):
    list_display = ("sub", "type", "exp", "modified", "is_valid")
    list_filter = ("exp", "modified", "is_active", "type")
    search_fields = ("sub",)
    readonly_fields = (
        "created",
        "modified",
        "parties_involved",
        "resultant_metadata",
        "type",
        "status",
        "status_log",
        "chain",
        "exp",
        "iat",
    )


@admin.register(PublicJwk)
class PublicJwkAdmin(admin.ModelAdmin):
    list_display = ("kid", "created")
    list_filter = ("created", "modified")
    search_fields = ("kid",)
    readonly_fields = ("created", "modified", "jwk_as_json")


@admin.register(FetchedEntityStatement)
class FetchedEntityStatementAdmin(admin.ModelAdmin):
    list_display = ("sub", "iss", "exp", "iat", "created", "modified")
    list_filter = ("created", "modified", "exp", "iat")
    search_fields = ("sub", "iss")
    readonly_fields = ("sub", "statement", "created", "modified", "iat", "exp", "iss")

spid_cie_oidc/entity/abstract_models.py METASEP
from django.contrib.auth import get_user_model
from django.db import models


class TimeStampedModel(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class CreatedModifiedBy(models.Model):
    created_by = models.ForeignKey(
        get_user_model(),
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="%(class)s_created_by",
    )
    modified_by = models.ForeignKey(
        get_user_model(),
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="%(class)s_modified_by",
    )

    class Meta:
        abstract = True

spid_cie_oidc/entity/__init__.py METASEP

spid_cie_oidc/accounts/models.py METASEP
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.models import Session
from django.utils.translation import gettext_lazy as _


class User(AbstractUser):
    GENDER = (("male", _("Male")), ("female", _("Female")), ("other", _("Other")))

    first_name = models.CharField(_("Name"), max_length=96, blank=True, null=True)
    last_name = models.CharField(_("Surname"), max_length=96, blank=True, null=True)
    is_active = models.BooleanField(_("active"), default=True)
    email = models.EmailField("email address", blank=True, null=True)
    taxpayer_id = models.CharField(
        _("Taxpayer's identification number"), max_length=32, blank=True, null=True
    )
    origin = models.CharField(
        _("from which connector this user come from"),
        max_length=254,
        blank=True,
        null=True,
    )

    class Meta:
        ordering = ["username"]
        verbose_name_plural = _("Users")

    def clear_sessions(self):  # pragma: no cover
        user_sessions = []
        for session in Session.objects.all():
            if str(self.pk) == session.get_decoded().get("_auth_user_id"):
                user_sessions.append(session.pk)
        return Session.objects.filter(pk__in=user_sessions).delete()

    def __str__(self):  # pragma: no cover
        return "{} {}".format(self.first_name, self.last_name)

spid_cie_oidc/accounts/apps.py METASEP
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _


class AccountsConfig(AppConfig):
    name = "spid_cie_oidc.accounts"
    verbose_name = _("User accounts")
    default_auto_field = "django.db.models.BigAutoField"
    label = "spid_cie_oidc_accounts"

spid_cie_oidc/accounts/admin_inlines.py METASEP

spid_cie_oidc/accounts/admin.py METASEP
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.admin import UserAdmin

from .models import User


@admin.register(User)
class CustomUserAdmin(UserAdmin):
    readonly_fields = (
        "date_joined",
        "last_login",
    )
    list_display = (
        "username",
        "email",
        "is_active",
        "is_staff",
        "is_superuser",
    )
    list_editable = (
        "is_active",
        "is_staff",
        "is_superuser",
    )
    fieldsets = (
        (
            None,
            {
                "fields": (
                    (
                        "username",
                        "is_active",
                        "is_staff",
                        "is_superuser",
                    ),
                    ("password"),
                    ("origin"),
                )
            },
        ),
        (
            _("Anagrafica"),
            {"fields": (("first_name", "last_name"), "email", ("taxpayer_id",))},
        ),
        (
            _("Permissions"),
            {"fields": ("groups", "user_permissions"), "classes": ("collapse",)},
        ),
        (
            _("Date accessi sistema"),
            {
                "fields": (
                    (
                        "date_joined",
                        "last_login",
                    )
                )
            },
        ),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("username", "password1", "password2"),
            },
        ),
    )


admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)

spid_cie_oidc/accounts/__init__.py METASEP

setup.py METASEP
import re
import os
import re
import sys

from glob import glob
from setuptools import setup, find_packages

SRC_FOLDER = '.'
PKG_NAME = 'spid_cie_oidc'

INSTALL_REQUIRES = [
    "Django>=4.0",
    "oidcmsg>=1.5.4",
    "pydantic>=1.8.2",
    "pytz>=2021.3",
    "aiohttp",
    "requests"
]

with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
    README = readme.read()

with open(f'{SRC_FOLDER}{os.path.sep}{PKG_NAME}/__init__.py', 'r') as fd:
    VERSION = re.search(
        r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
        fd.read(), re.MULTILINE
    ).group(1)

LICENSE = "License :: OSI Approved :: Apache Software License"

setup(
    name=PKG_NAME,
    version=VERSION,
    description="SPID/CIE OIDC Federation Entity",
    long_description=README,
    long_description_content_type='text/markdown',
    author='Giuseppe De Marco',
    author_email='demarcog83@gmail.com',
    license=LICENSE,
    url=f"https://github.com/peppelinux/{PKG_NAME.replace('_', '-')}",
    packages=[PKG_NAME, ],
    package_dir={f"{PKG_NAME}": f"{SRC_FOLDER}/{PKG_NAME}"},

    package_data={
        f"{PKG_NAME}": [
            i.replace(
                f'{SRC_FOLDER}{os.path.sep}{PKG_NAME}{os.path.sep}', ''
            )
            for i in glob(
                f'{SRC_FOLDER}{os.path.sep}{PKG_NAME}{os.path.sep}**',
                recursive=True
            )
            if i and '__pycache__' not in i
        ]
    },
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        LICENSE,
        "Programming Language :: Python :: 3.7",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Topic :: Software Development :: Libraries :: Python Modules"],
    install_requires=INSTALL_REQUIRES,
    zip_safe=False,
)

spid_cie_oidc/__init__.py METASEP
__version__ = "0.2.0"

spid_cie_oidc/authority/tests/test_02_trust_anchor_intermediary.py METASEP

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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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,
            metadata_type="openid_relying_party",
            required_trust_marks = [
                'https://www.spid.gov.it/certification/rp'
            ]
        )

        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_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,
                metadata_type="openid_relying_party",
                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 = [],
            metadata_type = "openid_relying_party"
        )

        self.assertFalse(gctc.is_expired)
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        self.assertTrue(trust_chain)
        self.assertTrue(trust_chain.final_metadata)

        for i in trust_chain.tree_of_trust.values():
            for ec in i:
inproject
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
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 (
inproject
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
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,
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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
        )
        
inproject
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
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,
)
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 datetime


class TrustChainTest(TestCase):
    def setUp(self):
        self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
inproject
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
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,
)
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 datetime


class TrustChainTest(TestCase):
    def setUp(self):
inproject
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
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,
)
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 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)

inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            metadata = trust_chain.final_metadata,
            parties_involved = [i.sub for i in trust_chain.trust_path],
            status = 'valid',
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            metadata = trust_chain.final_metadata,
            parties_involved = [i.sub for i in trust_chain.trust_path],
            status = 'valid',
            trust_anchor = FetchedEntityStatement.objects.get(
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        url = reverse("oidcfed_fetch")
        c = Client()
        res = c.get(url, data={"sub": self.rp.sub})
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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):
        
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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,
            metadata_type="openid_relying_party",
            required_trust_marks = [
                'https://www.spid.gov.it/certification/rp'
            ]
        )

        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_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,
                metadata_type="openid_relying_party",
                required_trust_marks = [
                    'https://www.spid.gov.it/certification/rp'
                ]
            )

    @override_settings(HTTP_CLIENT_SYNC=True)
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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)
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

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

inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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)
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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))
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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])

inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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
        )
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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()
inproject
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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}
common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        url = reverse("oidcfed_fetch")
        c = Client()
common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.intermediate
        )
        self.rp_conf.authority_hints = [intermediary_conf["sub"]]
common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

common
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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()
        
commited
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
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,
)
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 datetime


class TrustChainTest(TestCase):
    def setUp(self):
        self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)
        self.rp_conf = FederationEntityConfiguration.objects.create(**rp_conf)

commited
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
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,
)
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 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)
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
commited
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
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,
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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 = [
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

commited
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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,
            metadata_type="openid_relying_party",
            required_trust_marks = [
                'https://www.spid.gov.it/certification/rp'
            ]
        )

        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_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,
                metadata_type="openid_relying_party",
                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):

commited
from django.test import TestCase, Client, override_settings
from django.urls import reverse
non_informative
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

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

non_informative
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
random
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
random
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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,
            metadata_type="openid_relying_party",
        )

        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
        )

random
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
random
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
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,
)
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 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_jwk = PublicJwk.objects.create(
            jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]
        )
        FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)
        FederationEntityAssignedProfile.objects.create(
            descendant=self.rp, profile=self.rp_profile, issuer=self.ta_conf
        )

    def test_fetch_endpoint(self):
        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[0])
        self.assertTrue(data["jwks"])

    def test_list_endpoint(self):
        url = reverse("oidcfed_list")
        c = Client()
        res = c.get(url, data={"is_leaf": True})
        self.assertTrue(res.json()[0] == self.rp.sub)
        self.assertEqual(res.status_code, 200)

        res = c.get(url, data={"is_leaf": False})
        self.assertFalse(res.json())
        self.assertEqual(res.status_code, 200)

        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,
            metadata_type="openid_relying_party",
        )
        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_jwk = PublicJwk.objects.create(
            jwk=self.intermediate.public_jwks[0],
            kid=self.intermediate.public_jwks[0]["kid"],
        )
        self.intermediate_desc = FederationDescendant.objects.create(
            **intermediary_onboarding_data
        )
        FederationDescendantJwk.objects.create(
            descendant=self.intermediate_desc, jwk=self.intermediate_jwk
        )
        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,
            metadata_type="openid_relying_party"
        )
        
        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
        )

        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,
            type = trust_chain.metadata_type,
            exp = trust_chain.exp_datetime,
            chain = trust_chain.serialize(),
            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
        )
random
            trust_anchor=trust_anchor_ec,         self.assertTrue(gctc.is_valid)                 self.assertTrue(ec.is_valid)     EntityConfiguration,     get_entity_configurations,         stored_trust_chain = TrustChain.objects.create(         self.rp_conf = FederationEntityConfiguration.objects.create(**rp_conf)             type = trust_chain.metadata_type,             exp = trust_chain.exp_datetime,         self.ta_conf = FederationEntityConfiguration.objects.create(**ta_conf_data)         self.rp_jwk = PublicJwk.objects.create(             jwk=self.rp_conf.public_jwks[0], kid=self.rp_conf.public_jwks[0]["kid"]             trust_anchor = FetchedEntityStatement.objects.get(                 sub = trust_anchor_ec.sub, iss = trust_anchor_ec.sub         data = verify_jws(res.content.decode(), self.ta_conf.jwks[0])         trust_anchor_ec = self._create_federation_with_intermediary()     @patch("requests.get", return_value=EntityResponseNoIntermediate())     @patch("requests.get", return_value=EntityResponseWithIntermediate())         jwt = get_entity_configurations(self.ta_conf.sub)         trust_anchor_ec = EntityConfiguration(jwt[0])         self.assertTrue(isinstance(trust_chain.exp_datetime, datetime.datetime))         self.intermediate = FederationEntityConfiguration.objects.create(         self.intermediate_jwk = PublicJwk.objects.create(             jwk=self.intermediate.public_jwks[0],             kid=self.intermediate.public_jwks[0]["kid"],         return trust_anchor_ec         self.ta_conf.save()         res = c.get(url, data={"sub": self.rp.sub})         self.rp_conf.save()         res = c.get(url, data={"is_leaf": True})         res = c.get(url, data={"is_leaf": False})         res = c.get(url, data={})         trust_chain = trust_chain_builder(         self.rp_profile = FederationEntityProfile.objects.create(**RP_PROFILE)         self.rp = FederationDescendant.objects.create(**rp_onboarding_data)         FederationDescendantJwk.objects.create(descendant=self.rp, jwk=self.rp_jwk)         FederationEntityAssignedProfile.objects.create(     trust_chain_builder         self.rp_conf.trust_marks = [             FederationEntityAssignedProfile.objects.filter(             ).first().trust_mark         self.intermediate_desc = FederationDescendant.objects.create(         FederationDescendantJwk.objects.create(         dumps = dumps_statements_from_trust_chain_to_db(trust_chain)         gctc = get_or_create_trust_chain(         # the RP exposes a trust marks in its entity configuration             metadata_type="openid_relying_party",             descendant=self.intermediate_desc, jwk=self.intermediate_jwk     @override_settings(HTTP_CLIENT_SYNC=True)         )