From 37908ba1e70d9761a6dbe454fe7c258d8a7c1eb3 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 1 Dec 2023 19:31:09 +0100 Subject: [PATCH] generate presentation, step1 --- idhub/migrations/0001_initial.py | 11 ++- idhub/models.py | 6 +- idhub_auth/migrations/0001_initial.py | 2 +- oidc4vp/forms.py | 83 ++++++---------- .../templates/credentials_presentation.html | 96 +++++++++++++++++++ utils/idhub_ssikit/__init__.py | 33 +++---- 6 files changed, 155 insertions(+), 76 deletions(-) create mode 100644 oidc4vp/templates/credentials_presentation.html diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py index 4fbf53d..0a49195 100644 --- a/idhub/migrations/0001_initial.py +++ b/idhub/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-12-01 17:19 +# Generated by Django 4.2.5 on 2023-12-01 18:29 from django.conf import settings from django.db import migrations, models @@ -148,7 +148,6 @@ class Migration(migrations.Migration): ('verified', models.BooleanField()), ('created_on', models.DateTimeField(auto_now=True)), ('issued_on', models.DateTimeField(null=True)), - ('subject_did', models.CharField(max_length=250)), ('data', models.TextField()), ('csv_data', models.TextField()), ( @@ -179,6 +178,14 @@ class Migration(migrations.Migration): to='idhub.schemas', ), ), + ( + 'subject_did', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='subject_credentials', + to='idhub.did', + ), + ), ( 'user', models.ForeignKey( diff --git a/idhub/models.py b/idhub/models.py index 262f186..915929c 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -463,7 +463,6 @@ class VerificableCredential(models.Model): verified = models.BooleanField() created_on = models.DateTimeField(auto_now=True) issued_on = models.DateTimeField(null=True) - subject_did = models.CharField(max_length=250) data = models.TextField() csv_data = models.TextField() status = models.PositiveSmallIntegerField( @@ -475,6 +474,11 @@ class VerificableCredential(models.Model): on_delete=models.CASCADE, related_name='vcredentials', ) + subject_did = models.ForeignKey( + DID, + on_delete=models.CASCADE, + related_name='subject_credentials', + ) issuer_did = models.ForeignKey( DID, on_delete=models.CASCADE, diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py index 9091472..741a8b9 100644 --- a/idhub_auth/migrations/0001_initial.py +++ b/idhub_auth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-12-01 17:19 +# Generated by Django 4.2.5 on 2023-12-01 18:29 from django.db import migrations, models diff --git a/oidc4vp/forms.py b/oidc4vp/forms.py index 9a8633e..3d4b16f 100644 --- a/oidc4vp/forms.py +++ b/oidc4vp/forms.py @@ -1,54 +1,13 @@ from django import forms from django.conf import settings +from utils.idhub_ssikit import issue_verifiable_presentation from oidc4vp.models import Organization -# class OrganizationForm(forms.Form): -# wallet = forms.ChoiceField( -# "Wallet", -# choices=[(x.id, x.name) for x in Organization.objects.all()] -# ) - -# def clean_wallet(self): -# data = self.cleaned_data["wallet"] -# organization = Organization.objects.filter( -# id=data -# ) - -# if not organization.exists(): -# raise ValidationError("organization is not valid!") - -# self.organization = organization.first() - -# return data - -# def authorize(self): -# data = { -# "response_type": "vp_token", -# "response_mode": "direct_post", -# "client_id": self.organization.client_id, -# "response_uri": settings.RESPONSE_URI, -# "presentation_definition": self.pv_definition(), -# "nonce": "" -# } -# query_dict = QueryDict('', mutable=True) -# query_dict.update(data) - -# url = '{response_uri}/authorize?{params}'.format( -# response_uri=self.organization.response_uri, -# params=query_dict.urlencode() -# ) - -# def pv_definition(self): -# return "" - - class AuthorizeForm(forms.Form): - # organization = forms.ChoiceField(choices=[]) def __init__(self, *args, **kwargs): - # import pdb; pdb.set_trace() self.data = kwargs.get('data', {}).copy() self.user = kwargs.pop('user', None) self.presentation_definition = kwargs.pop('presentation_definition', []) @@ -69,20 +28,36 @@ class AuthorizeForm(forms.Form): widget=forms.RadioSelect, choices=choices ) + def clean(self): + data = super().clean() + import pdb; pdb.set_trace() + self.list_credentials = [] + for c in self.credentials: + if str(c.id) == data.get(c.schema.type.lower()): + self.list_credentials.append(c) + return data def save(self, commit=True): - # self.org = Organization.objects.filter( - # id=self.data['organization'] - # ) - # if not self.org.exists(): - # return + if not self.list_credentials: + return - # self.org = self.org[0] + did = self.list_credentials[0].subject_did - # if commit: - # url = self.org.demand_authorization() - # if url.status_code == 200: - # return url.json().get('redirect_uri') - - return + self.vp = issue_verifiable_presentation( + vp_template: Template, + vc_list: list[str], + jwk_holder: str, + holder_did: str) + + self.vp = issue_verifiable_presentation( + vp_template: Template, + self.list_credentials, + did.key_material, + did.did) + + if commit: + result = requests.post(self.vp) + return result + + return diff --git a/oidc4vp/templates/credentials_presentation.html b/oidc4vp/templates/credentials_presentation.html new file mode 100644 index 0000000..63f54ba --- /dev/null +++ b/oidc4vp/templates/credentials_presentation.html @@ -0,0 +1,96 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% if form.credentials.all %} +{% for presentation in form.presentation_definition %} +
+
+

{{ presentation|capfirst }} +

+
+ +
+
+
+ + + + + + + + + + + + {% for f in form.credentials.all %} + {% if f.schema.type.lower == presentation.lower %} + + + + + + + + {% endif %} + {% endfor %} + +
{{ f.type }}{{ f.description }}{{ f.get_issued_on }}
+
+
+
+{% endfor %} + +
+ {% trans 'I read and understood the' %} + {% trans 'data sharing notice' %} +
+ + +{% else %} +
+
+

{% trans 'There are not credentials for present' %}

+
+
+{% endif %} +
+ + + +{% endblock %} diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py index c4ac0e3..35464b3 100644 --- a/utils/idhub_ssikit/__init__.py +++ b/utils/idhub_ssikit/__init__.py @@ -4,6 +4,7 @@ import didkit import json import jinja2 from django.template.backends.django import Template +from django.template.loader import get_template def generate_did_controller_key(): @@ -49,34 +50,35 @@ def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data def sign_credential(unsigned_vc: str, jwk_issuer): """ - Signs the and unsigned credential with the provided key. + Signs the unsigned credential with the provided key. + The credential template must be rendered with all user data. """ async def inner(): - signed_vc = await didkit.issue_credential( - unsigned_vc, - '{"proofFormat": "ldp"}', - jwk_issuer - ) - return signed_vc + signed_vc = await didkit.issue_credential( + unsigned_vc, + '{"proofFormat": "ldp"}', + jwk_issuer + ) + return signed_vc return asyncio.run(inner()) -def verify_credential(vc, proof_options): +def verify_credential(vc): """ Returns a (bool, str) tuple indicating whether the credential is valid. If the boolean is true, the credential is valid and the second argument can be ignored. If it is false, the VC is invalid and the second argument contains a JSON object with further information. """ async def inner(): - return didkit.verify_credential(vc, proof_options) + return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}') return asyncio.run(inner()) -def issue_verifiable_presentation(vc_list: list[str], jwk_holder: str, holder_did: str) -> str: +def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str: async def inner(): - unsigned_vp = unsigned_vp_template.render(data) + unsigned_vp = vp_template.render(data) signed_vp = await didkit.issue_presentation( unsigned_vp, '{"proofFormat": "ldp"}', @@ -84,12 +86,6 @@ def issue_verifiable_presentation(vc_list: list[str], jwk_holder: str, holder_di ) return signed_vp - # TODO: convert from Jinja2 -> django-templates - env = Environment( - loader=FileSystemLoader("vc_templates"), - autoescape=select_autoescape() - ) - unsigned_vp_template = env.get_template("verifiable_presentation.json") data = { "holder_did": holder_did, "verifiable_credential_list": "[" + ",".join(vc_list) + "]" @@ -106,6 +102,7 @@ def verify_presentation(vp): """ async def inner(): proof_options = '{"proofFormat": "ldp"}' - return didkit.verify_presentation(vp, proof_options) + return await didkit.verify_presentation(vp, proof_options) return asyncio.run(inner()) +