generate presentation, step1

This commit is contained in:
Cayo Puigdefabregas 2023-12-01 19:31:09 +01:00
parent 2c97bf8d36
commit 37908ba1e7
6 changed files with 155 additions and 76 deletions

View File

@ -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.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -148,7 +148,6 @@ class Migration(migrations.Migration):
('verified', models.BooleanField()), ('verified', models.BooleanField()),
('created_on', models.DateTimeField(auto_now=True)), ('created_on', models.DateTimeField(auto_now=True)),
('issued_on', models.DateTimeField(null=True)), ('issued_on', models.DateTimeField(null=True)),
('subject_did', models.CharField(max_length=250)),
('data', models.TextField()), ('data', models.TextField()),
('csv_data', models.TextField()), ('csv_data', models.TextField()),
( (
@ -179,6 +178,14 @@ class Migration(migrations.Migration):
to='idhub.schemas', to='idhub.schemas',
), ),
), ),
(
'subject_did',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='subject_credentials',
to='idhub.did',
),
),
( (
'user', 'user',
models.ForeignKey( models.ForeignKey(

View File

@ -463,7 +463,6 @@ class VerificableCredential(models.Model):
verified = models.BooleanField() verified = models.BooleanField()
created_on = models.DateTimeField(auto_now=True) created_on = models.DateTimeField(auto_now=True)
issued_on = models.DateTimeField(null=True) issued_on = models.DateTimeField(null=True)
subject_did = models.CharField(max_length=250)
data = models.TextField() data = models.TextField()
csv_data = models.TextField() csv_data = models.TextField()
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
@ -475,6 +474,11 @@ class VerificableCredential(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='vcredentials', related_name='vcredentials',
) )
subject_did = models.ForeignKey(
DID,
on_delete=models.CASCADE,
related_name='subject_credentials',
)
issuer_did = models.ForeignKey( issuer_did = models.ForeignKey(
DID, DID,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View File

@ -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 from django.db import migrations, models

View File

@ -1,54 +1,13 @@
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from utils.idhub_ssikit import issue_verifiable_presentation
from oidc4vp.models import Organization 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): class AuthorizeForm(forms.Form):
# organization = forms.ChoiceField(choices=[])
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# import pdb; pdb.set_trace()
self.data = kwargs.get('data', {}).copy() self.data = kwargs.get('data', {}).copy()
self.user = kwargs.pop('user', None) self.user = kwargs.pop('user', None)
self.presentation_definition = kwargs.pop('presentation_definition', []) self.presentation_definition = kwargs.pop('presentation_definition', [])
@ -69,20 +28,36 @@ class AuthorizeForm(forms.Form):
widget=forms.RadioSelect, widget=forms.RadioSelect,
choices=choices 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): def save(self, commit=True):
# self.org = Organization.objects.filter( if not self.list_credentials:
# id=self.data['organization'] return
# )
# if not self.org.exists():
# return
# self.org = self.org[0] did = self.list_credentials[0].subject_did
# if commit: self.vp = issue_verifiable_presentation(
# url = self.org.demand_authorization() vp_template: Template,
# if url.status_code == 200: vc_list: list[str],
# return url.json().get('redirect_uri') jwk_holder: str,
holder_did: str)
return
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

View File

@ -0,0 +1,96 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% if form.credentials.all %}
{% for presentation in form.presentation_definition %}
<div class="row mt-5">
<div class="col">
<h3>{{ presentation|capfirst }}</3>
</div>
</div>
<div class="row mt-2">
<div class="col">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Type' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Details' %}</button></th>
<th scope="col"><button type="button" class="btn btn-grey border border-dark">{% trans 'Issued' %}</button></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for f in form.credentials.all %}
{% if f.schema.type.lower == presentation.lower %}
<tr style="font-size:15px;">
<td><input class="form-check-input" type="radio" value="{{ f.id }}" name="{{ presentation.lower }}"></td>
<td>{{ f.type }}</td>
<td>{{ f.description }}</td>
<td>{{ f.get_issued_on }}</td>
<td><a href="{% url 'idhub:user_credential' f.id %}" class="text-primary" title="{% trans 'View' %}"><i class="bi bi-eye"></i></a></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endfor %}
<div class="form-actions-no-box mt-3">
<input class="form-check-input" type="checkbox" value="" name="consent" required="required" /> {% trans 'I read and understood the' %}
<a href="javascript:void()" data-bs-toggle="modal" data-bs-target="#legality-consent">{% trans 'data sharing notice' %}</a>
</div>
<div class="form-actions-no-box mt-5">
<a class="btn btn-grey" href="{% url 'idhub:user_demand_authorization' %}">{% trans "Cancel" %}</a>
<input class="btn btn-green-user" type="submit" name="submit" value="{% trans 'Present' %}" />
</div>
{% else %}
<div class="row mt-5">
<div class="col">
<h3>{% trans 'There are not credentials for present' %}</h3>
</div>
</div>
{% endif %}
</form>
<!-- Modal -->
<div class="modal" id="legality-consent" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{% trans 'Data sharing notice' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% trans 'Are you sure that you want delete this user?' %}
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -4,6 +4,7 @@ import didkit
import json import json
import jinja2 import jinja2
from django.template.backends.django import Template from django.template.backends.django import Template
from django.template.loader import get_template
def generate_did_controller_key(): 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): 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(): async def inner():
signed_vc = await didkit.issue_credential( signed_vc = await didkit.issue_credential(
unsigned_vc, unsigned_vc,
'{"proofFormat": "ldp"}', '{"proofFormat": "ldp"}',
jwk_issuer jwk_issuer
) )
return signed_vc return signed_vc
return asyncio.run(inner()) 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. 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 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. If it is false, the VC is invalid and the second argument contains a JSON object with further information.
""" """
async def inner(): async def inner():
return didkit.verify_credential(vc, proof_options) return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
return asyncio.run(inner()) 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(): async def inner():
unsigned_vp = unsigned_vp_template.render(data) unsigned_vp = vp_template.render(data)
signed_vp = await didkit.issue_presentation( signed_vp = await didkit.issue_presentation(
unsigned_vp, unsigned_vp,
'{"proofFormat": "ldp"}', '{"proofFormat": "ldp"}',
@ -84,12 +86,6 @@ def issue_verifiable_presentation(vc_list: list[str], jwk_holder: str, holder_di
) )
return signed_vp 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 = { data = {
"holder_did": holder_did, "holder_did": holder_did,
"verifiable_credential_list": "[" + ",".join(vc_list) + "]" "verifiable_credential_list": "[" + ",".join(vc_list) + "]"
@ -106,6 +102,7 @@ def verify_presentation(vp):
""" """
async def inner(): async def inner():
proof_options = '{"proofFormat": "ldp"}' proof_options = '{"proofFormat": "ldp"}'
return didkit.verify_presentation(vp, proof_options) return await didkit.verify_presentation(vp, proof_options)
return asyncio.run(inner()) return asyncio.run(inner())