feat: link local library

This commit is contained in:
gabdevele
2026-06-17 20:43:56 +02:00 Unverified
parent 9a770a2af0
commit db59882f33
327 changed files with 22274 additions and 1 deletions
@@ -0,0 +1,28 @@
from django.contrib import admin
from .models import RelyingPartyReport, RelyingPartyTest
@admin.register(RelyingPartyReport)
class RelyingPartyReportAdmin(admin.ModelAdmin):
search_fields = ("client_id",)
list_display = (
"client_id", "status", "user", "created", "modified"
)
list_filter = ("created", "status")
@admin.register(RelyingPartyTest)
class RelyingPartyTestAdmin(admin.ModelAdmin):
search_fields = ("name", "report__client_id")
list_display = (
"client_id", "status",
"code", "http_status_code", "screenshot_as_html"
)
list_filter = ("created", "status")
readonly_fileds = ("screenshot_as_html",)
def screenshot_as_html(self, obj):
return obj.screenshot # pragma: no cover
screenshot_as_html.short_description = "Screenshot"
@@ -0,0 +1,8 @@
from django.apps import AppConfig
class RelyingPartyTestConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "spid_cie_oidc.relying_party_test"
verbose_name = "SPID/CIE OIDC Federation RP Tests"
label = "spid_cie_oidc_relying_party_test"
@@ -0,0 +1,28 @@
from django import forms
from django.utils.translation import gettext_lazy as _
class TestingPageAttributesForm(forms.Form):
attributes = forms.JSONField(
initial=dict,
label="attributes"
)
class TestingPageChecksForm(forms.Form):
CHOICES = (
('send_auth_code_no_code', _('[auth code] send an auth code response without code')),
('send_auth_code_no_state', _('[auth code] send an auth code without state')),
('unsigned_id_token', _('[token endpoint] release an unsigned id token')),
('id_token_with_wrong_signature', _('[token endpoint] release an id token with a wrong signature'))
)
test = forms.ChoiceField(
choices=CHOICES,
widget=forms.RadioSelect,
label="select",
# error_messages={"required": _("Select an item")},
required=False
)
@@ -0,0 +1,55 @@
# Generated by Django 4.0.2 on 2022-03-13 00:41
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import spid_cie_oidc.relying_party_test.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='RelyingPartyReport',
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)),
('client_id', models.URLField(blank=True, null=True)),
('status', models.CharField(choices=[('unknown', 'Sconosciuto'), ('failed', 'Failed'), ('successfull', 'successfull'), ('error', 'Internal error')], default='unknown', help_text='status', max_length=33)),
('report', models.JSONField(blank=True, default=dict, max_length=4096)),
('user', models.ForeignKey(blank=True, help_text='Staff user that started the tests', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Relying Party Status',
'verbose_name_plural': 'Relying Party Status',
},
),
migrations.CreateModel(
name='RelyingPartyTest',
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(max_length=256)),
('category', models.CharField(max_length=256)),
('code', models.CharField(max_length=256)),
('http_status_code', models.PositiveIntegerField()),
('screenshot', models.FileField(upload_to=spid_cie_oidc.relying_party_test.models.context_media_path)),
('log', models.TextField()),
('note', models.TextField()),
('status', models.CharField(choices=[('unknown', 'Sconosciuto'), ('failed', 'Failed'), ('successfull', 'successfull'), ('error', 'Internal error')], default='unknown', help_text='status', max_length=33)),
('report', models.ForeignKey(help_text='Staff user that started the tests', on_delete=django.db.models.deletion.CASCADE, to='spid_cie_oidc_relying_party_test.relyingpartyreport')),
],
options={
'verbose_name': 'Relying Party Test',
'verbose_name_plural': 'Relying Party Tests',
},
),
]
@@ -0,0 +1,19 @@
# Generated by Django 4.0.2 on 2022-03-14 16:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('spid_cie_oidc_relying_party_test', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='relyingpartytest',
name='report',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='spid_cie_oidc_relying_party_test.relyingpartyreport'),
),
]
@@ -0,0 +1,93 @@
import logging
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext as _
from spid_cie_oidc.entity.abstract_models import TimeStampedModel
logger = logging.getLogger(__name__)
TEST_STATUS = (
("unknown", _("Unknown")),
("failed", _("Failed")),
("successfull", _("successfull")),
("error", _("Internal error"))
)
def context_media_path(instance, filename): # pragma: no cover
# file will be uploaded to MEDIA_ROOT/...
return (
f'medias/{instance.client_id}/'
f'{instance.report.pk}/'
f'{filename}'
)
class RelyingPartyReport(TimeStampedModel):
"""
A test report about a RP
"""
client_id = models.URLField(blank=True, null=True)
user = models.ForeignKey(
get_user_model(), on_delete=models.SET_NULL,
null=True, blank=True,
help_text=_("Staff user that started the tests")
)
status = models.CharField(
max_length=33,
default = "unknown",
help_text=_("status"),
choices = TEST_STATUS
)
report = models.JSONField(max_length=4096, blank=True, default=dict)
def __str__(self):
return "{} {}".format(self.client_id, self.status)
class Meta:
verbose_name = "Relying Party Status"
verbose_name_plural = "Relying Party Status"
class RelyingPartyTest(TimeStampedModel):
"""
Single tests that belongs to a Report
"""
report = models.ForeignKey(
RelyingPartyReport, on_delete=models.CASCADE,
help_text=_("")
)
name = models.CharField(max_length=256)
category = models.CharField(max_length=256)
code = models.CharField(max_length=256)
http_status_code = models.PositiveIntegerField()
screenshot = models.FileField(
upload_to=context_media_path
)
log = models.TextField()
note = models.TextField()
status = models.CharField(
max_length=33,
default = "unknown",
help_text=_("status"),
choices = TEST_STATUS
)
@property
def client_id(self):
return self.report.client_id
def __str__(self):
return "{} {}".format(self.client_id, self.status)
class Meta:
verbose_name = "Relying Party Test"
verbose_name_plural = "Relying Party Tests"
@@ -0,0 +1,23 @@
# Pyppeteer
Note: When you run pyppeteer for the first time, it downloads the latest version of Chromium (~150MB) if it is not found on your system. If you don't prefer this behavior, ensure that a suitable Chrome binary is installed. One way to do this is to run pyppeteer-install command before prior to using this library.
- Documentation: https://pyppeteer.github.io/pyppeteer/
## Requisition:
pyppeteer requires Python >= 3.6
## Installation:
Install with pip from PyPI:
```
pip install pyppeteer
```
Or install the latest version from [this github repo](https://github.com/pyppeteer/pyppeteer/):
```
pip install -U git+https://github.com/pyppeteer/pyppeteer@dev
```
@@ -0,0 +1,86 @@
import asyncio
from pyppeteer import launch
import os
import json
BASE_FOLDER = os.path.join(".","spid_cie_oidc","relying_party_test","snippets","results")
class Crawler:
def __init__(self, url: str, username: str, password: str, testname: str, browser=None):
self.url = url
self.username = username
self.password = password
self.testname = testname
self.waiting = {"timeout": 60000, "waitUntil": "networkidle0"}
if browser:
self.browser = browser
def make_dir_result(self):
self.folder = os.path.join(BASE_FOLDER, self.testname)
if not os.path.exists(self.folder):
os.mkdir(self.folder)
async def start(self, page= None):
self.browser = await launch(headless=False, args= ['--start-maximized'])
if page:
self.page = page
else:
self.page = await self.browser.newPage()
await self.page.setViewport({"width": 1866, "height": 900})
page = await self.page.goto(self.url)
self.status = page.status
# async def init_browser(self):
# self.browser = await launch(headless=False, args= [ '--start-maximized' ])
# async def make_page(self,page=None):
# if page:
# self.page = page
# else:
# self.page = await self.browser.newPage()
# await self.page.setViewport({ "width": 1866, "height": 900})
# async def goto_url(self):
# await self.page.goto(self.url)
async def goto_login_provider(self):
spid_button = await self.page.querySelector(".italia-it-button-icon")
await spid_button.click()
provider = await self.page.querySelector("#provider-spid")
await provider.click()
await asyncio.ensure_future(self.page.waitForNavigation(self.waiting))
async def login(self):
lable1 = await self.page.querySelector("#label_username")
await lable1.click()
await lable1.type(self.username)
lable2 = await self.page.querySelector("#label_password")
await lable2.click()
await lable2.type(self.password)
submit = await self.page.querySelector("#submit")
await submit.click()
page = await asyncio.ensure_future(self.page.waitForNavigation(self.waiting))
self.status = page.status
async def consent(self, selector:str):
button = await self.page.querySelector(selector)
await button.click()
page = await asyncio.ensure_future(self.page.waitForNavigation(self.waiting))
self.status = page.status
async def print_result(self, content, filename, extension):
file_name = os.path.join(self.folder, f'{filename}{extension}')
f = open(file_name, 'w')
f.write(content)
f.close()
async def save_result(self):
file_path = os.path.join(self.folder, f'{self.testname}.png')
html = await self.page.content()
status_code = {"status_code": self.status}
await self.print_result(html, self.testname, ".html")
await self.print_result(json.dumps(status_code), "status", ".json")
await self.page.screenshot({'path': file_path})
@@ -0,0 +1,40 @@
import asyncio
from crawler import Crawler
OIDC_RP_REQUESTS = {
"full_authentication_ok": "http://localhost:8000/oidc/rp/landing",
}
class Pypper:
def __init__(self) -> None:
pass
async def authentication_ok():
c = Crawler(OIDC_RP_REQUESTS["full_authentication_ok"],
"user",
"oidcuser",
"authentication_ok"
)
c.make_dir_result()
await c.start()
await c.goto_login_provider()
await c.login()
await c.consent("#agree")
await c.save_result()
await c.browser.close()
async def test2():
c = Crawler(OIDC_RP_REQUESTS["request_ok"],
"user",
"oidcuser",
"send_request_ok"
)
c.make_dir_result()
await c.start()
await c.save_result()
await c.browser.close()
if __name__ == "__main__":
asyncio.run(authentication_ok())
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,219 @@
{% extends 'op_base.html' %}
{% load i18n %}
{% load static %}
{% block menu_links %}
<li class="nav-item ">
<a class="nav-link {% if request.resolver_match.view_name == 'oidc_provider_staff_testing' %}active{% endif %}"
href="{% url 'oidc_provider_staff_testing' %}">
<span>{% trans 'Home' %}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.view_name == 'oidc_provider_access_history' %}active{% endif %}"
href="{% url 'oidc_provider_access_history' %}">
<span>{% trans 'Access History' %}</span>
</a>
</li>
{% endblock menu_links %}
{% block container %}
<div>
<div class="container pt-2 p-3">
<div class="d-lg-flex">
<div class="col-12">
<form method="POST" action="{% url 'oidc_provider_staff_testing' %}">
{% csrf_token %}
<div class="card-space card-wrapper">
<div class="card card-bg no-after">
<div class="card-body pl-lg-0 pb-0">
<div class="h-100">
<div class="col-12 pl-lg-4 ">
<h4 class="bd-title">{% trans 'Relying Party test page' %}</h4>
<p class="mb-0 text-secondary">{% trans "You're a staff user and currently testing " %}<b>{{session.client_id}}</b>.</p>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="card-space card-wrapper">
<div class="card card-bg no-after">
<div class="card-body p-0">
<div class="row h-100">
<div class="col-12 col-lg-12">
<h5 class="p-2">&nbsp;
<svg class="icon icon-secondary icon-sm"><use href="{% static 'svg/sprite.svg' %}#it-settings"></use></svg>
Configuration
</h5>
<div class="p-0">
<div id="collapseDiv1-sc1" class="collapse-div collapse-background-active" role="tablist">
<div class="collapse-header" id="heading1-sc1">
<div data-toggle="collapse" data-target="#collapse1-sc1" aria-expanded="false"
aria-controls="collapse1-sc1">
{% trans "Edit redirect uri" %}
</div>
</div>
<div id="collapse1-sc1" class="collapse" role="tabpanel" aria-labelledby="heading1-sc1">
<div class="collapse-body p-2">
<p class="p-2 mb-0 text-secondary">{% trans "The callback url where the auth code will be sent" %}</p>
<input type="text" value="{{ redirect_uri }}" class="form-control" id="id_redirect_uri">
</div>
</div>
<div class="collapse-header" id="heading2-sc2">
<div data-toggle="collapse" data-target="#collapse2-sc2" aria-expanded="false"
aria-controls="collapse1-sc2">
{% trans "Edit user attributes" %}
</div>
</div>
<div id="collapse2-sc2" class="collapse" role="tabpanel" aria-labelledby="heading2-sc2">
<div class="collapse-body p-2">
<textarea rows="10" type="text"
class="form-control border" id="id_attributes"
name="attributes" >{{attributes}}
</textarea>
</div>
</div>
<div class="collapse-header" id="heading11-sc11">
<div data-toggle="collapse" data-target="#collapse11-sc11" aria-expanded="false"
aria-controls="collapse11-sc11">
{% trans "Edit Authorization url" %}
</div>
</div>
<div id="collapse11-sc11" class="collapse" role="tabpanel" aria-labelledby="heading11-sc11">
<div class="collapse-body p-2">
<input type="text" value="" placeholder="enter the url to use to batch the tests" class="form-control" id="id_authz_url">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm">
<div class="card-space card-wrapper">
<div class="card card-bg no-after h-100">
<div class="card-body p-0">
<div class="row">
<div class="col-12 col-lg-12">
<h5 class="p-2">&nbsp;
<svg class="icon icon-secondary icon-sm"><use href="{% static 'svg/sprite.svg' %}#it-tool"></use></svg>
Tests</h5>
<div class="p-0">
<div id="collapseDiv11-sc11" class="collapse-div collapse-background-active" role="tablist">
<div class="collapse-header" id="heading12-sc12">
<div data-toggle="collapse" data-target="#collapse12-sc12" aria-expanded="false"
aria-controls="collapse12-sc12">
<span class="badge badge-pill badge-secondary">&nbsp;</span>
<span class="badge badge-pill badge-success">Ok</span>
<span class="badge badge-pill badge-danger">Ko</span>
&nbsp;
{% trans "[auth code] send an auth code without state" %}
</div>
</div>
<div id="collapse12-sc12" class="collapse" role="tabpanel" aria-labelledby="heading12-sc12">
<div class="collapse-body p-4">
<div class="row">
<div class="col-sm it-list-wrapper w-25">
<ul class="it-list pl-3">
<li><b class="text-secondary">{% trans "automatic test" %}</b></li>
<li><b>response code</b>: 200</li>
<li><b>authz url</b>: url</li>
<li><b>redirect uri</b>: url</li>
<li><b>last </b>: datetime here</li>
</ul>
</div>
<div class="col-sm w-75">
<img class="img-fluid" src="{% static 'images/oops.webp' %}" />
</div>
</div>
<hr>
<div class="row it-list-wrapper">
<div class="col-sm-12 text-right">
<a class="btn btn-primary btn-xs mb-4 mr-4"
href="{% url 'oidc_provider_staff_testing' %}">
{% trans "Test manually with your browser" %}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-space card-wrapper">
<div class="card card-bg no-after">
<div class="card-body pl-lg-0 pb-0">
<div class="h-100">
<div class="col-sm-12 text-center">
<a href="{% url 'oidc_provider_not_consent' %}?redirect_uri={{ redirect_uri }}"
class="italia-it-button italia-it-button-cancel-m button-cancel mb-4 mr-4">
<span class="italia-it-buttons-text">{% trans "Deny consent" %}</span>
</a>
<button class="italia-it-button italia-it-button-size-m button-login mb-4 mr-4" type="submit">
<span class="italia-it-buttons-text">{% trans "Submit" %}</span>
</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock container %}
@@ -0,0 +1,72 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from spid_cie_oidc.authority.tests.settings import rp_conf
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.provider.models import OidcSession
from spid_cie_oidc.provider.tests.settings import op_conf
class RPTetstTest(TestCase):
def setUp(self):
self.user = get_user_model().objects.create(
username="test",
first_name="test",
last_name="test",
email="[email protected]",
is_staff = True
)
self.user.set_password("test")
self.user.save()
OidcSession.objects.create(
user=self.user,
user_uid="",
nonce="",
authz_request={
"scope": "openid",
"nonce": "123",
"acr_values" : ["https://www.spid.gov.it/SpidL2"],
"redirect_uri" : rp_conf["metadata"]["openid_relying_party"]["redirect_uris"][0],
"state" : "state",
},
client_id="",
auth_code="code",
)
self.op_conf = FederationEntityConfiguration.objects.create(**op_conf)
def test_user_staff_get(self):
c = Client()
c.login(username="test", password="test")
session = c.session
session.update({"oidc": {"auth_code": "code"}})
session.save()
url = reverse("oidc_provider_staff_testing")
res = c.get(url)
self.assertTrue("test page" in res.content.decode())
def test_user_staff_post(self):
c = Client()
c.login(username="test", password="test")
session = c.session
session.update({"oidc": {"auth_code": "code"}})
session.save()
url = reverse("oidc_provider_staff_testing")
res = c.post(url)
self.assertTrue(res.status_code == 302)
self.assertTrue("callback" in res.url)
def test_user_staff_get_no_session(self):
c = Client()
c.login(username="test", password="test")
url = reverse("oidc_provider_staff_testing")
res = c.get(url)
self.assertTrue(res.status_code == 403)
def test_user_staff_post_no_session(self):
c = Client()
c.login(username="test", password="test")
url = reverse("oidc_provider_staff_testing")
res = c.post(url)
self.assertTrue(res.status_code == 403)
@@ -0,0 +1,18 @@
from django.conf import settings
from django.urls import path
from .views import (
StaffTestingPageView
)
_PREF = getattr(settings, "OIDC_PREFIX", "oidc/op/rp-test/")
urlpatterns = [
path(
f"{_PREF}landing/",
StaffTestingPageView.as_view(),
name="oidc_provider_staff_testing",
),
]
@@ -0,0 +1,73 @@
import json
import logging
from django.http import HttpResponseForbidden
from django.views import View
from django.shortcuts import render
from spid_cie_oidc.provider.models import IssuedToken
from spid_cie_oidc.provider.views import OpBase
from spid_cie_oidc.relying_party_test.forms import TestingPageAttributesForm, TestingPageChecksForm, _
logger = logging.getLogger(__name__)
class StaffTestingPageView(OpBase, View):
template = "op_user_staff_test.html"
def get_testing_form(self):
return TestingPageChecksForm
def get(self, request):
try:
session = self.check_session(request)
except Exception:
logger.warning("Invalid session")
return HttpResponseForbidden()
session.user
attributes = self.attributes_names_to_release(
request, session
)['filtered_user_claims']
content = {
"form_checks": self.get_testing_form()(),
"form_attrs": TestingPageAttributesForm(),
"attributes": json.dumps(attributes, indent=4),
"session": session,
"redirect_uri": session.authz_request.get('redirect_uri', "")
}
return render(request, self.template, content)
def post(self, request):
try:
session = self.check_session(request)
except Exception:
logger.warning("Invalid session")
return HttpResponseForbidden()
form = self.get_testing_form()(request.POST)
if not form.is_valid():
return self.redirect_response_data(
# TODO: this is not normative -> check AgID/IPZS
session.authz_request['redirect_uri'],
error="rejected_by_user",
error_description=_(
"User rejected the release of attributes"
)
)
issuer = self.get_issuer()
iss_token_data = self.get_iss_token_data(session, issuer)
IssuedToken.objects.create(**iss_token_data)
self.payload = session.authz_request
return self.redirect_response_data(
self.payload["redirect_uri"],
code=session.auth_code,
state=session.authz_request["state"],
iss=issuer.sub if issuer else ""
)