From 9e8596f39d07d1b0567647c52c0720ade273cc2c Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 24 Nov 2023 17:53:43 +0100 Subject: [PATCH] add more details to flow --- oidc4vp/migrations/0001_initial.py | 134 +++++++++++++++++++++++++++++ oidc4vp/models.py | 86 +++++++++++++++--- 2 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 oidc4vp/migrations/0001_initial.py diff --git a/oidc4vp/migrations/0001_initial.py b/oidc4vp/migrations/0001_initial.py new file mode 100644 index 0000000..cc0d8fd --- /dev/null +++ b/oidc4vp/migrations/0001_initial.py @@ -0,0 +1,134 @@ +# Generated by Django 4.2.5 on 2023-11-24 16:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import oidc4vp.models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Authorization', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'code', + models.CharField(default=oidc4vp.models.set_code, max_length=24), + ), + ('created', models.DateTimeField(auto_now=True)), + ('presentation_definition', models.CharField(max_length=250)), + ], + ), + migrations.CreateModel( + name='Organization', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('name', models.CharField(max_length=250)), + ( + 'client_id', + models.CharField( + default=oidc4vp.models.set_client_id, max_length=24 + ), + ), + ( + 'client_secret', + models.CharField( + default=oidc4vp.models.set_client_secret, max_length=48 + ), + ), + ( + 'response_uri', + models.URLField( + help_text='Url where to send the verificable presentation', + max_length=250, + ), + ), + ], + ), + migrations.CreateModel( + name='OAuth2VPToken', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('created', models.DateTimeField(auto_now=True)), + ('code', models.CharField(max_length=250)), + ('result_verify', models.BooleanField(max_length=250)), + ('presentation_definition', models.CharField(max_length=250)), + ( + 'authorization', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to='oidc4vp.authorization', + ), + ), + ( + 'organization', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='vp_tokens', + to='oidc4vp.organization', + ), + ), + ( + 'user', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='vp_tokens', + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddField( + model_name='authorization', + name='organization', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='authorizations', + to='oidc4vp.organization', + ), + ), + migrations.AddField( + model_name='authorization', + name='user', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/oidc4vp/models.py b/oidc4vp/models.py index ac6da6d..fa273b7 100644 --- a/oidc4vp/models.py +++ b/oidc4vp/models.py @@ -1,34 +1,80 @@ import requests +import secrets -from django.db import models +from django.conf import settings from django.http import QueryDict from django.utils.translation import gettext_lazy as _ from idhub_auth.models import User +from django.db import models + + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + +def gen_salt(length: int) -> str: + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be positive") + + return "".join(secrets.choice(SALT_CHARS) for _ in range(length)) + + +def set_client_id(): + return gen_salt(24) + + +def set_client_secret(): + return gen_salt(48) + + +def set_code(): + return gen_salt(24) class Organization(models.Model): + """ + This class represent a member of one net trust or federated host + """ name = models.CharField(max_length=250) - client_id = models.CharField() - client_secret = models.CharField() + client_id = models.CharField(max_length=24, default=set_client_id) + client_secret = models.CharField(max_length=48, default=set_client_secret) response_uri = models.URLField( - help_text=_("Url where to send the presentation"), + help_text=_("Url where to send the verificable presentation"), max_length=250 ) + def send(self, vp): + """ + Send the verificable presentation to Verifier + """ + org = Organization.objects.get( + response_uri=settings.RESPONSE_URI + ) + auth = (org.client_id, org.client_secret) + return requests.post(self.url, data=vp, auth=auth) + def __str__(self): return self.name - def send(self, vcred): - return requests.post(self.url, data=vcred) + +################### +# Verifier clases # +################### class Authorization(models.Model): + """ + This class represent a query through browser the client to the wallet. + The Verifier need to do a redirection to the user to Wallet. + The code we use as a soft foreing key between Authorization and OAuth2VPToken. + """ + code = models.CharField(max_length=24, default=set_code) created = models.DateTimeField(auto_now=True) - presentation_definition = models.CharField() + presentation_definition = models.CharField(max_length=250) organization = models.ForeignKey( Organization, on_delete=models.CASCADE, - related_name='vp_tokens', + related_name='authorizations', null=True, ) user = models.ForeignKey( @@ -44,10 +90,10 @@ class Authorization(models.Model): data = { "response_type": "vp_token", "response_mode": "direct_post", - "client_id": "...", + "client_id": self.organization.client_id, "response_uri": response_uri, "presentation_definition": "...", - "nonce": "" + "nonce": gen_salt(5), } query_dict = QueryDict('', mutable=True) query_dict.update(data) @@ -56,12 +102,18 @@ class Authorization(models.Model): response_uri=self.organization.response_uri, params=query_dict.urlencode() ) + return url + class OAuth2VPToken(models.Model): + """ + This class represent the response of Wallet to Verifier + and the result of verify. + """ created = models.DateTimeField(auto_now=True) - response_code = models.CharField() - result_verify = models.BooleanField() - presentation_definition = models.CharField() + code = models.CharField(max_length=250) + result_verify = models.BooleanField(max_length=250) + presentation_definition = models.CharField(max_length=250) organization = models.ForeignKey( Organization, on_delete=models.CASCADE, @@ -74,4 +126,12 @@ class OAuth2VPToken(models.Model): related_name='vp_tokens', null=True, ) + authorization = models.ForeignKey( + Authorization, + on_delete=models.SET_NULL, + null=True, + ) + + def verifing(self): + pass