resolve super conflict
This commit is contained in:
commit
c76ed799be
|
@ -1,2 +1,2 @@
|
||||||
name surnames email typeOfPerson membershipType organisation affiliatedSince
|
firstName lastName email membershipType membershipId affiliatedUntil affiliatedSince typeOfPerson identityDocType identityNumber
|
||||||
Pepe Gómez user1@example.org individual Member Pangea 01-01-2023
|
Pepe Gómez user1@example.org individual 123456 2024-01-01T00:00:00Z 2023-01-01T00:00:00Z natural DNI 12345678A
|
||||||
|
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,11 +1,13 @@
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
import copy
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from pyhanko.sign import signers
|
from pyhanko.sign import signers
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.cache import cache
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from utils import credtools, certs
|
from utils import credtools, certs
|
||||||
|
@ -23,24 +25,39 @@ from idhub_auth.models import User
|
||||||
|
|
||||||
class ImportForm(forms.Form):
|
class ImportForm(forms.Form):
|
||||||
did = forms.ChoiceField(label=_("Did"), choices=[])
|
did = forms.ChoiceField(label=_("Did"), choices=[])
|
||||||
|
eidas1 = forms.ChoiceField(
|
||||||
|
label=_("Signature with Eidas1"),
|
||||||
|
choices=[],
|
||||||
|
required=False
|
||||||
|
)
|
||||||
schema = forms.ChoiceField(label=_("Schema"), choices=[])
|
schema = forms.ChoiceField(label=_("Schema"), choices=[])
|
||||||
file_import = forms.FileField(label=_("File import"))
|
file_import = forms.FileField(label=_("File import"))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._schema = None
|
self._schema = None
|
||||||
self._did = None
|
self._did = None
|
||||||
|
self._eidas1 = None
|
||||||
self.rows = {}
|
self.rows = {}
|
||||||
self.properties = {}
|
self.properties = {}
|
||||||
self.user = kwargs.pop('user', None)
|
self.user = kwargs.pop('user', None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
dids = DID.objects.filter(user=self.user)
|
||||||
self.fields['did'].choices = [
|
self.fields['did'].choices = [
|
||||||
(x.did, x.label) for x in DID.objects.filter(user=self.user)
|
(x.did, x.label) for x in dids.filter(eidas1=False)
|
||||||
]
|
]
|
||||||
self.fields['schema'].choices = [
|
self.fields['schema'].choices = [
|
||||||
(x.id, x.name()) for x in Schemas.objects.filter()
|
(x.id, x.name()) for x in Schemas.objects.filter()
|
||||||
]
|
]
|
||||||
|
if dids.filter(eidas1=True).exists():
|
||||||
|
choices = [("", "")]
|
||||||
|
choices.extend([
|
||||||
|
(x.did, x.label) for x in dids.filter(eidas1=True)
|
||||||
|
])
|
||||||
|
self.fields['eidas1'].choices = choices
|
||||||
|
else:
|
||||||
|
self.fields.pop('eidas1')
|
||||||
|
|
||||||
def clean_did(self):
|
def clean(self):
|
||||||
data = self.cleaned_data["did"]
|
data = self.cleaned_data["did"]
|
||||||
did = DID.objects.filter(
|
did = DID.objects.filter(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
@ -52,6 +69,14 @@ class ImportForm(forms.Form):
|
||||||
|
|
||||||
self._did = did.first()
|
self._did = did.first()
|
||||||
|
|
||||||
|
eidas1 = self.cleaned_data.get('eidas1')
|
||||||
|
if eidas1:
|
||||||
|
self._eidas1 = DID.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
eidas1=True,
|
||||||
|
did=eidas1
|
||||||
|
).first()
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean_schema(self):
|
def clean_schema(self):
|
||||||
|
@ -65,7 +90,8 @@ class ImportForm(forms.Form):
|
||||||
self._schema = schema.first()
|
self._schema = schema.first()
|
||||||
try:
|
try:
|
||||||
self.json_schema = json.loads(self._schema.data)
|
self.json_schema = json.loads(self._schema.data)
|
||||||
prop = self.json_schema['properties']
|
props = [x for x in self.json_schema["allOf"] if 'properties' in x.keys()]
|
||||||
|
prop = props[0]['properties']
|
||||||
self.properties = prop['credentialSubject']['properties']
|
self.properties = prop['credentialSubject']['properties']
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValidationError("Schema is not valid!")
|
raise ValidationError("Schema is not valid!")
|
||||||
|
@ -73,7 +99,10 @@ class ImportForm(forms.Form):
|
||||||
if not self.properties:
|
if not self.properties:
|
||||||
raise ValidationError("Schema is not valid!")
|
raise ValidationError("Schema is not valid!")
|
||||||
|
|
||||||
|
# TODO we need filter "$ref" of schema for can validate a csv
|
||||||
|
self.json_schema_filtered = copy.copy(self.json_schema)
|
||||||
|
allOf = [x for x in self.json_schema["allOf"] if '$ref' not in x.keys()]
|
||||||
|
self.json_schema_filtered["allOf"] = allOf
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean_file_import(self):
|
def clean_file_import(self):
|
||||||
|
@ -115,7 +144,9 @@ class ImportForm(forms.Form):
|
||||||
|
|
||||||
def validate_jsonld(self, line, row):
|
def validate_jsonld(self, line, row):
|
||||||
try:
|
try:
|
||||||
credtools.validate_json(row, self.json_schema)
|
check = credtools.validate_json(row, self.json_schema_filtered)
|
||||||
|
if check is not True:
|
||||||
|
raise ValidationError("Not valid row")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = "line {}: {}".format(line+1, e)
|
msg = "line {}: {}".format(line+1, e)
|
||||||
self.exception(msg)
|
self.exception(msg)
|
||||||
|
@ -135,6 +166,7 @@ class ImportForm(forms.Form):
|
||||||
csv_data=json.dumps(row),
|
csv_data=json.dumps(row),
|
||||||
issuer_did=self._did,
|
issuer_did=self._did,
|
||||||
schema=self._schema,
|
schema=self._schema,
|
||||||
|
eidas1_did=self._eidas1
|
||||||
)
|
)
|
||||||
|
|
||||||
def exception(self, msg):
|
def exception(self, msg):
|
||||||
|
@ -268,9 +300,13 @@ class ImportCertificateForm(forms.Form):
|
||||||
did=self.file_name,
|
did=self.file_name,
|
||||||
label=self._label,
|
label=self._label,
|
||||||
eidas1=True,
|
eidas1=True,
|
||||||
user=self.user
|
user=self.user,
|
||||||
|
type=DID.Types.KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pw = cache.get("KEY_DIDS")
|
||||||
|
self._did.set_key_material(key_material, pw)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
|
|
|
@ -17,6 +17,7 @@ from django.views.generic.edit import (
|
||||||
UpdateView,
|
UpdateView,
|
||||||
)
|
)
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.core.cache import cache
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -640,19 +641,20 @@ class DidRegisterView(Credentials, CreateView):
|
||||||
icon = 'bi bi-patch-check-fill'
|
icon = 'bi bi-patch-check-fill'
|
||||||
wallet = True
|
wallet = True
|
||||||
model = DID
|
model = DID
|
||||||
fields = ('label',)
|
fields = ('label', 'type')
|
||||||
success_url = reverse_lazy('idhub:admin_dids')
|
success_url = reverse_lazy('idhub:admin_dids')
|
||||||
object = None
|
object = None
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.set_did()
|
form.instance.set_did(cache.get("KEY_DIDS"))
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(self.request, _('DID created successfully'))
|
messages.success(self.request, _('DID created successfully'))
|
||||||
Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance)
|
Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DidEditView(Credentials, UpdateView):
|
class DidEditView(Credentials, UpdateView):
|
||||||
template_name = "idhub/admin/did_register.html"
|
template_name = "idhub/admin/did_register.html"
|
||||||
subtitle = _('Organization Identities (DID)')
|
subtitle = _('Organization Identities (DID)')
|
||||||
|
|
|
@ -7,6 +7,7 @@ from utils import credtools
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core.cache import cache
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from idhub.models import DID, Schemas
|
from idhub.models import DID, Schemas
|
||||||
from oidc4vp.models import Organization
|
from oidc4vp.models import Organization
|
||||||
|
@ -36,17 +37,25 @@ class Command(BaseCommand):
|
||||||
self.create_organizations(r[0].strip(), r[1].strip())
|
self.create_organizations(r[0].strip(), r[1].strip())
|
||||||
self.sync_credentials_organizations("pangea.org", "somconnexio.coop")
|
self.sync_credentials_organizations("pangea.org", "somconnexio.coop")
|
||||||
self.sync_credentials_organizations("local 8000", "local 9000")
|
self.sync_credentials_organizations("local 8000", "local 9000")
|
||||||
self.create_defaults_dids()
|
|
||||||
self.create_schemas()
|
self.create_schemas()
|
||||||
|
|
||||||
def create_admin_users(self, email, password):
|
def create_admin_users(self, email, password):
|
||||||
User.objects.create_superuser(email=email, password=password)
|
su = User.objects.create_superuser(email=email, password=password)
|
||||||
|
su.set_encrypted_sensitive_data(password)
|
||||||
|
su.save()
|
||||||
|
key = su.decrypt_sensitive_data(password)
|
||||||
|
key_dids = {su.id: key}
|
||||||
|
cache.set("KEY_DIDS", key_dids, None)
|
||||||
|
self.create_defaults_dids(su, key)
|
||||||
|
|
||||||
|
|
||||||
def create_users(self, email, password):
|
def create_users(self, email, password):
|
||||||
u= User.objects.create(email=email, password=password)
|
u = User.objects.create(email=email, password=password)
|
||||||
u.set_password(password)
|
u.set_password(password)
|
||||||
|
u.set_encrypted_sensitive_data(password)
|
||||||
u.save()
|
u.save()
|
||||||
|
key = u.decrypt_sensitive_data(password)
|
||||||
|
self.create_defaults_dids(u, key)
|
||||||
|
|
||||||
|
|
||||||
def create_organizations(self, name, url):
|
def create_organizations(self, name, url):
|
||||||
|
@ -61,11 +70,9 @@ class Command(BaseCommand):
|
||||||
org1.my_client_secret = org2.client_secret
|
org1.my_client_secret = org2.client_secret
|
||||||
org1.save()
|
org1.save()
|
||||||
org2.save()
|
org2.save()
|
||||||
|
def create_defaults_dids(self, u, password):
|
||||||
def create_defaults_dids(self):
|
did = DID(label="Default", user=u, type=DID.Types.KEY)
|
||||||
for u in User.objects.all():
|
did.set_did(password)
|
||||||
did = DID(label="Default", user=u)
|
|
||||||
did.set_did()
|
|
||||||
did.save()
|
did.save()
|
||||||
|
|
||||||
def create_schemas(self):
|
def create_schemas(self):
|
||||||
|
@ -82,10 +89,18 @@ class Command(BaseCommand):
|
||||||
try:
|
try:
|
||||||
ldata = json.loads(data)
|
ldata = json.loads(data)
|
||||||
assert credtools.validate_schema(ldata)
|
assert credtools.validate_schema(ldata)
|
||||||
name = ldata.get('name')
|
dname = ldata.get('name')
|
||||||
assert name
|
assert dname
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
name = ''
|
||||||
|
try:
|
||||||
|
for x in dname:
|
||||||
|
if settings.LANGUAGE_CODE in x['lang']:
|
||||||
|
name = x.get('value', '')
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
Schemas.objects.create(file_schema=file_name, data=data, type=name)
|
Schemas.objects.create(file_schema=file_name, data=data, type=name)
|
||||||
|
|
||||||
def open_file(self, file_name):
|
def open_file(self, file_name):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2024-01-11 10:17
|
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -25,11 +25,18 @@ class Migration(migrations.Migration):
|
||||||
verbose_name='ID',
|
verbose_name='ID',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'type',
|
||||||
|
models.PositiveSmallIntegerField(
|
||||||
|
choices=[(1, 'Key'), (2, 'Web')], verbose_name='Type'
|
||||||
|
),
|
||||||
|
),
|
||||||
('created_at', models.DateTimeField(auto_now=True)),
|
('created_at', models.DateTimeField(auto_now=True)),
|
||||||
('label', models.CharField(max_length=50, verbose_name='Label')),
|
('label', models.CharField(max_length=50, verbose_name='Label')),
|
||||||
('did', models.CharField(max_length=250)),
|
('did', models.CharField(max_length=250)),
|
||||||
('key_material', models.TextField()),
|
('key_material', models.TextField()),
|
||||||
('eidas1', models.BooleanField(default=False)),
|
('eidas1', models.BooleanField(default=False)),
|
||||||
|
('didweb_document', models.TextField()),
|
||||||
(
|
(
|
||||||
'user',
|
'user',
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
|
@ -151,7 +158,6 @@ class Migration(migrations.Migration):
|
||||||
('issued_on', models.DateTimeField(null=True)),
|
('issued_on', models.DateTimeField(null=True)),
|
||||||
('data', models.TextField()),
|
('data', models.TextField()),
|
||||||
('csv_data', models.TextField()),
|
('csv_data', models.TextField()),
|
||||||
('public', models.BooleanField(default=True)),
|
|
||||||
('hash', models.CharField(max_length=260)),
|
('hash', models.CharField(max_length=260)),
|
||||||
(
|
(
|
||||||
'status',
|
'status',
|
||||||
|
@ -165,6 +171,14 @@ class Migration(migrations.Migration):
|
||||||
default=1,
|
default=1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'eidas1_did',
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to='idhub.did',
|
||||||
|
),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
'issuer_did',
|
'issuer_did',
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
|
|
135
idhub/models.py
135
idhub/models.py
|
@ -1,15 +1,21 @@
|
||||||
import json
|
import json
|
||||||
|
import ujson
|
||||||
import pytz
|
import pytz
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
|
from collections import OrderedDict
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from nacl import secret
|
||||||
|
|
||||||
from utils.idhub_ssikit import (
|
from utils.idhub_ssikit import (
|
||||||
generate_did_controller_key,
|
generate_did_controller_key,
|
||||||
keydid_from_controller_key,
|
keydid_from_controller_key,
|
||||||
sign_credential,
|
sign_credential,
|
||||||
|
webdid_from_controller_key,
|
||||||
)
|
)
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
|
|
||||||
|
@ -404,6 +410,13 @@ class Event(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class DID(models.Model):
|
class DID(models.Model):
|
||||||
|
class Types(models.IntegerChoices):
|
||||||
|
KEY = 1, "Key"
|
||||||
|
WEB = 2, "Web"
|
||||||
|
type = models.PositiveSmallIntegerField(
|
||||||
|
_("Type"),
|
||||||
|
choices=Types.choices,
|
||||||
|
)
|
||||||
created_at = models.DateTimeField(auto_now=True)
|
created_at = models.DateTimeField(auto_now=True)
|
||||||
label = models.CharField(_("Label"), max_length=50)
|
label = models.CharField(_("Label"), max_length=50)
|
||||||
did = models.CharField(max_length=250)
|
did = models.CharField(max_length=250)
|
||||||
|
@ -418,6 +431,13 @@ class DID(models.Model):
|
||||||
related_name='dids',
|
related_name='dids',
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
didweb_document = models.TextField()
|
||||||
|
|
||||||
|
def get_key_material(self, password):
|
||||||
|
return self.user.decrypt_data(self.key_material, password)
|
||||||
|
|
||||||
|
def set_key_material(self, value, password):
|
||||||
|
self.key_material = self.user.encrypt_data(value, password)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_organization_did(self):
|
def is_organization_did(self):
|
||||||
|
@ -425,14 +445,20 @@ class DID(models.Model):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_did(self):
|
def set_did(self, password):
|
||||||
self.key_material = generate_did_controller_key()
|
new_key_material = generate_did_controller_key()
|
||||||
self.did = keydid_from_controller_key(self.key_material)
|
self.set_key_material(new_key_material, password)
|
||||||
|
|
||||||
|
if self.type == self.Types.KEY:
|
||||||
|
self.did = keydid_from_controller_key(new_key_material)
|
||||||
|
elif self.type == self.Types.WEB:
|
||||||
|
didurl, document = webdid_from_controller_key(new_key_material)
|
||||||
|
self.did = didurl
|
||||||
|
self.didweb_document = document
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
return json.loads(self.key_material)
|
return json.loads(self.key_material)
|
||||||
|
|
||||||
|
|
||||||
class Schemas(models.Model):
|
class Schemas(models.Model):
|
||||||
type = models.CharField(max_length=250)
|
type = models.CharField(max_length=250)
|
||||||
file_schema = models.CharField(max_length=250)
|
file_schema = models.CharField(max_length=250)
|
||||||
|
@ -445,8 +471,18 @@ class Schemas(models.Model):
|
||||||
return {}
|
return {}
|
||||||
return json.loads(self.data)
|
return json.loads(self.data)
|
||||||
|
|
||||||
def name(self):
|
def name(self, request=None):
|
||||||
return self.get_schema.get('name', '')
|
names = {}
|
||||||
|
for name in self.get_schema.get('name', []):
|
||||||
|
lang = name.get('lang')
|
||||||
|
if 'ca' in lang:
|
||||||
|
lang = 'ca'
|
||||||
|
names[lang]= name.get('value')
|
||||||
|
|
||||||
|
if request and request.LANGUAGE_CODE in names.keys():
|
||||||
|
return names[request.LANGUAGE_CODE]
|
||||||
|
|
||||||
|
return names[settings.LANGUAGE_CODE]
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return self.get_schema.get('description', '')
|
return self.get_schema.get('description', '')
|
||||||
|
@ -468,7 +504,6 @@ class VerificableCredential(models.Model):
|
||||||
issued_on = models.DateTimeField(null=True)
|
issued_on = models.DateTimeField(null=True)
|
||||||
data = models.TextField()
|
data = models.TextField()
|
||||||
csv_data = models.TextField()
|
csv_data = models.TextField()
|
||||||
public = models.BooleanField(default=settings.DEFAULT_PUBLIC_CREDENTIALS)
|
|
||||||
hash = models.CharField(max_length=260)
|
hash = models.CharField(max_length=260)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.PositiveSmallIntegerField(
|
||||||
choices=Status.choices,
|
choices=Status.choices,
|
||||||
|
@ -490,21 +525,50 @@ class VerificableCredential(models.Model):
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='vcredentials',
|
related_name='vcredentials',
|
||||||
)
|
)
|
||||||
|
eidas1_did = models.ForeignKey(
|
||||||
|
DID,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
schema = models.ForeignKey(
|
schema = models.ForeignKey(
|
||||||
Schemas,
|
Schemas,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='vcredentials',
|
related_name='vcredentials',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_data(self, password):
|
||||||
|
if not self.data:
|
||||||
|
return ""
|
||||||
|
if self.eidas1_did:
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
return self.user.decrypt_data(self.data, password)
|
||||||
|
|
||||||
|
def set_data(self, value, password):
|
||||||
|
self.data = self.user.encrypt_data(value, password)
|
||||||
|
|
||||||
def type(self):
|
def type(self):
|
||||||
return self.schema.type
|
return self.schema.type
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
for des in json.loads(self.render()).get('description', []):
|
for des in json.loads(self.render("")).get('description', []):
|
||||||
if settings.LANGUAGE_CODE == des.get('lang'):
|
if settings.LANGUAGE_CODE in des.get('lang'):
|
||||||
return des.get('value', '')
|
return des.get('value', '')
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def get_type(self, lang=None):
|
||||||
|
schema = json.loads(self.schema.data)
|
||||||
|
if not schema.get('name'):
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
for x in schema['name']:
|
||||||
|
if lang or settings.LANGUAGE_CODE in x['lang']:
|
||||||
|
return x.get('value', '')
|
||||||
|
except:
|
||||||
|
return self.schema.type
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
return self.Status(self.status).label
|
return self.Status(self.status).label
|
||||||
|
|
||||||
|
@ -512,21 +576,29 @@ class VerificableCredential(models.Model):
|
||||||
data = json.loads(self.csv_data).items()
|
data = json.loads(self.csv_data).items()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def issue(self, did):
|
def issue(self, did, password, domain=settings.DOMAIN.strip("/")):
|
||||||
if self.status == self.Status.ISSUED:
|
if self.status == self.Status.ISSUED:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.status = self.Status.ISSUED
|
self.status = self.Status.ISSUED
|
||||||
self.subject_did = did
|
self.subject_did = did
|
||||||
self.issued_on = datetime.datetime.now().astimezone(pytz.utc)
|
self.issued_on = datetime.datetime.now().astimezone(pytz.utc)
|
||||||
self.data = sign_credential(
|
issuer_pass = cache.get("KEY_DIDS")
|
||||||
self.render(),
|
# issuer_pass = self.user.decrypt_data(
|
||||||
self.issuer_did.key_material
|
# cache.get("KEY_DIDS"),
|
||||||
|
# settings.SECRET_KEY,
|
||||||
|
# )
|
||||||
|
data = sign_credential(
|
||||||
|
self.render(domain),
|
||||||
|
self.issuer_did.get_key_material(issuer_pass)
|
||||||
)
|
)
|
||||||
if self.public:
|
if self.eidas1_did:
|
||||||
|
self.data = data
|
||||||
self.hash = hashlib.sha3_256(self.data.encode()).hexdigest()
|
self.hash = hashlib.sha3_256(self.data.encode()).hexdigest()
|
||||||
|
else:
|
||||||
|
self.data = self.user.encrypt_data(data, password)
|
||||||
|
|
||||||
def get_context(self):
|
def get_context(self, domain):
|
||||||
d = json.loads(self.csv_data)
|
d = json.loads(self.csv_data)
|
||||||
issuance_date = ''
|
issuance_date = ''
|
||||||
if self.issued_on:
|
if self.issued_on:
|
||||||
|
@ -534,31 +606,38 @@ class VerificableCredential(models.Model):
|
||||||
issuance_date = self.issued_on.strftime(format)
|
issuance_date = self.issued_on.strftime(format)
|
||||||
|
|
||||||
cred_path = 'credentials'
|
cred_path = 'credentials'
|
||||||
if self.public:
|
if self.eidas1_did:
|
||||||
cred_path = 'public/credentials'
|
cred_path = 'public/credentials'
|
||||||
|
|
||||||
url_id = "{}/{}/{}".format(
|
url_id = "{}/{}/{}".format(
|
||||||
settings.DOMAIN.strip("/"),
|
domain,
|
||||||
cred_path,
|
cred_path,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'vc_id': url_id,
|
'vc_id': url_id,
|
||||||
'issuer_did': self.issuer_did.did,
|
'issuer_did': self.issuer_did.did,
|
||||||
'subject_did': self.subject_did and self.subject_did.did or '',
|
'subject_did': self.subject_did and self.subject_did.did or '',
|
||||||
'issuance_date': issuance_date,
|
'issuance_date': issuance_date,
|
||||||
'first_name': self.user.first_name,
|
'firstName': self.user.first_name or "",
|
||||||
'last_name': self.user.last_name,
|
'lastName': self.user.last_name or "",
|
||||||
|
'email': self.user.email,
|
||||||
|
'organisation': settings.ORGANIZATION or '',
|
||||||
}
|
}
|
||||||
context.update(d)
|
context.update(d)
|
||||||
|
context['firstName'] = ""
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def render(self):
|
def render(self, domain):
|
||||||
context = self.get_context()
|
context = self.get_context(domain)
|
||||||
template_name = 'credentials/{}'.format(
|
template_name = 'credentials/{}'.format(
|
||||||
self.schema.file_schema
|
self.schema.file_schema
|
||||||
)
|
)
|
||||||
tmpl = get_template(template_name)
|
tmpl = get_template(template_name)
|
||||||
return tmpl.render(context)
|
d_ordered = ujson.loads(tmpl.render(context))
|
||||||
|
d_minimum = self.filter_dict(d_ordered)
|
||||||
|
return ujson.dumps(d_minimum)
|
||||||
|
|
||||||
|
|
||||||
def get_issued_on(self):
|
def get_issued_on(self):
|
||||||
|
@ -567,6 +646,18 @@ class VerificableCredential(models.Model):
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def filter_dict(self, dic):
|
||||||
|
new_dict = OrderedDict()
|
||||||
|
for key, value in dic.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
new_value = self.filter_dict(value)
|
||||||
|
if new_value:
|
||||||
|
new_dict[key] = new_value
|
||||||
|
elif value:
|
||||||
|
new_dict[key] = value
|
||||||
|
return new_dict
|
||||||
|
|
||||||
|
|
||||||
class VCTemplate(models.Model):
|
class VCTemplate(models.Model):
|
||||||
wkit_template_id = models.CharField(max_length=250)
|
wkit_template_id = models.CharField(max_length=250)
|
||||||
data = models.TextField()
|
data = models.TextField()
|
||||||
|
|
|
@ -1,31 +1,8 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
{
|
"https://idhub.pangea.org/credentials/base/v1",
|
||||||
"individual": "https://schema.org/Person",
|
"https://idhub.pangea.org/credentials/membership-card/v1"
|
||||||
"Member": "https://schema.org/Member",
|
|
||||||
"startDate": "https://schema.org/startDate",
|
|
||||||
"jsonSchema": "https://schema.org/jsonSchema",
|
|
||||||
"$ref": "https://schema.org/jsonSchemaRef",
|
|
||||||
"credentialSchema": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#credentialSchema",
|
|
||||||
"organisation": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#organisation",
|
|
||||||
"membershipType": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#membershipType",
|
|
||||||
"membershipId": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#membershipId",
|
|
||||||
"typeOfPerson": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#typeOfPerson",
|
|
||||||
"identityDocType": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#identityDocType",
|
|
||||||
"identityNumber": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#identityNumber",
|
|
||||||
"name": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#name",
|
|
||||||
"description": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#description",
|
|
||||||
"value": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#value",
|
|
||||||
"lang": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#lang",
|
|
||||||
"surnames": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#surnames",
|
|
||||||
"email": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#email",
|
|
||||||
"affiliatedSince": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#affiliatedSince",
|
|
||||||
"affiliatedUntil": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#affiliatedUntil",
|
|
||||||
"issued": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#issued",
|
|
||||||
"validFrom": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#validFrom",
|
|
||||||
"validUntil": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#validUntil"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
|
@ -35,22 +12,7 @@
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"id": "{{ issuer_did }}",
|
"id": "{{ issuer_did }}",
|
||||||
"name": "Pangea",
|
"name": "{{ organisation }}"
|
||||||
"description": [
|
|
||||||
{
|
|
||||||
"value": "Pangea.org is a service provider leveraging open-source technologies to provide affordable and accessible solutions for social enterprises and solidarity organisations.",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Pangea.org és un proveïdor de serveis que aprofita les tecnologies de codi obert per oferir solucions assequibles i accessibles per a empreses socials i organitzacions solidàries.",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Pangea.org es un proveedor de servicios que aprovecha tecnologías de código abierto para proporcionar soluciones asequibles y accesibles para empresas sociales y organizaciones solidarias.",
|
|
||||||
"lang": "es"
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"issuanceDate": "{{ issuance_date }}",
|
"issuanceDate": "{{ issuance_date }}",
|
||||||
"issued": "{{ issuance_date }}",
|
"issued": "{{ issuance_date }}",
|
||||||
|
@ -86,20 +48,20 @@
|
||||||
],
|
],
|
||||||
"credentialSubject": {
|
"credentialSubject": {
|
||||||
"id": "{{ subject_did }}",
|
"id": "{{ subject_did }}",
|
||||||
"organisation": "Pangea",
|
"firstName": "{{ firstName }}",
|
||||||
"membershipType": "{{ membershipType }}",
|
"lastName": "{{ lastName }}",
|
||||||
"membershipId": "{{ vc_id }}",
|
"email": "{{ email }}",
|
||||||
"affiliatedSince": "{{ affiliatedSince }}",
|
|
||||||
"affiliatedUntil": "{{ affiliatedUntil }}",
|
|
||||||
"typeOfPerson": "{{ typeOfPerson }}",
|
"typeOfPerson": "{{ typeOfPerson }}",
|
||||||
"identityDocType": "{{ identityDocType }}",
|
"identityDocType": "{{ identityDocType }}",
|
||||||
"identityNumber": "{{ identityNumber }}",
|
"identityNumber": "{{ identityNumber }}",
|
||||||
"name": "{{ first_name }}",
|
"organisation": "{{ organisation }}",
|
||||||
"surnames": "{{ last_name }}",
|
"membershipType": "{{ membershipType }}",
|
||||||
"email": "{{ email }}",
|
"membershipId": "{{ vc_id }}",
|
||||||
|
"affiliatedSince": "{{ affiliatedSince }}",
|
||||||
|
"affiliatedUntil": "{{ affiliatedUntil }}"
|
||||||
|
},
|
||||||
"credentialSchema": {
|
"credentialSchema": {
|
||||||
"id": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/membership-card-schema.json",
|
"id": "https://idhub.pangea.org/vc_schemas/membership-card.json",
|
||||||
"type": "JsonSchema"
|
"type": "FullJsonSchemaValidator2021"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for f in credentials.all %}
|
{% for f in credentials.all %}
|
||||||
<tr style="font-size:15px;">
|
<tr style="font-size:15px;">
|
||||||
<td>{{ f.type }}</td>
|
<td>{{ f.get_type }}</td>
|
||||||
<td>{{ f.description }}</td>
|
<td>{{ f.description }}</td>
|
||||||
<td>{{ f.get_issued_on }}</td>
|
<td>{{ f.get_issued_on }}</td>
|
||||||
<td class="text-center">{{ f.get_status }}</td>
|
<td class="text-center">{{ f.get_status }}</td>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
{% if object.public %}
|
{% if object.eidas1_did %}
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a class="btn btn-green-user" href="{% url 'idhub:user_credential_pdf' object.id %}">{% trans 'Sign credential in PDF format' %}</a>
|
<a class="btn btn-green-user" href="{% url 'idhub:user_credential_pdf' object.id %}">{% trans 'Sign credential in PDF format' %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for f in credentials.all %}
|
{% for f in credentials.all %}
|
||||||
<tr style="font-size:15px;">
|
<tr style="font-size:15px;">
|
||||||
<td>{{ f.type }}</td>
|
<td>{{ f.get_type }}</td>
|
||||||
<td>{{ f.description }}</td>
|
<td>{{ f.description }}</td>
|
||||||
<td>{{ f.get_issued_on }}</td>
|
<td>{{ f.get_issued_on }}</td>
|
||||||
<td class="text-center">{{ f.get_status }}</td>
|
<td class="text-center">{{ f.get_status }}</td>
|
||||||
|
|
|
@ -17,7 +17,7 @@ Including another URLconf
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from django.urls import path, reverse_lazy
|
from django.urls import path, reverse_lazy
|
||||||
from .views import LoginView
|
from .views import LoginView, PasswordResetConfirmView, serve_did
|
||||||
from .admin import views as views_admin
|
from .admin import views as views_admin
|
||||||
from .user import views as views_user
|
from .user import views as views_user
|
||||||
# from .verification_portal import views as views_verification_portal
|
# from .verification_portal import views as views_verification_portal
|
||||||
|
@ -45,13 +45,16 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
name='password_reset_done'
|
name='password_reset_done'
|
||||||
),
|
),
|
||||||
path('auth/reset/<uidb64>/<token>/',
|
path('auth/reset/<uidb64>/<token>/', PasswordResetConfirmView.as_view(),
|
||||||
auth_views.PasswordResetConfirmView.as_view(
|
|
||||||
template_name='auth/password_reset_confirm.html',
|
|
||||||
success_url=reverse_lazy('idhub:password_reset_complete')
|
|
||||||
),
|
|
||||||
name='password_reset_confirm'
|
name='password_reset_confirm'
|
||||||
),
|
),
|
||||||
|
# path('auth/reset/<uidb64>/<token>/',
|
||||||
|
# auth_views.PasswordResetConfirmView.as_view(
|
||||||
|
# template_name='auth/password_reset_confirm.html',
|
||||||
|
# success_url=reverse_lazy('idhub:password_reset_complete')
|
||||||
|
# ),
|
||||||
|
# name='password_reset_confirm'
|
||||||
|
# ),
|
||||||
path('auth/reset/done/',
|
path('auth/reset/done/',
|
||||||
auth_views.PasswordResetCompleteView.as_view(
|
auth_views.PasswordResetCompleteView.as_view(
|
||||||
template_name='auth/password_reset_complete.html'
|
template_name='auth/password_reset_complete.html'
|
||||||
|
@ -177,6 +180,8 @@ urlpatterns = [
|
||||||
path('admin/import/new', views_admin.ImportAddView.as_view(),
|
path('admin/import/new', views_admin.ImportAddView.as_view(),
|
||||||
name='admin_import_add'),
|
name='admin_import_add'),
|
||||||
|
|
||||||
|
path('did-registry/<str:did_id>/did.json', serve_did)
|
||||||
|
|
||||||
# path('verification_portal/verify/', views_verification_portal.verify,
|
# path('verification_portal/verify/', views_verification_portal.verify,
|
||||||
# name="verification_portal_verify")
|
# name="verification_portal_verify")
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,12 +22,15 @@ class RequestCredentialForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop('user', None)
|
self.user = kwargs.pop('user', None)
|
||||||
|
self.lang = kwargs.pop('lang', None)
|
||||||
|
self._domain = kwargs.pop('domain', None)
|
||||||
|
self.password = kwargs.pop('password', None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['did'].choices = [
|
self.fields['did'].choices = [
|
||||||
(x.did, x.label) for x in DID.objects.filter(user=self.user)
|
(x.did, x.label) for x in DID.objects.filter(user=self.user)
|
||||||
]
|
]
|
||||||
self.fields['credential'].choices = [
|
self.fields['credential'].choices = [
|
||||||
(x.id, x.type()) for x in VerificableCredential.objects.filter(
|
(x.id, x.get_type(lang=self.lang)) for x in VerificableCredential.objects.filter(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
status=VerificableCredential.Status.ENABLED
|
status=VerificableCredential.Status.ENABLED
|
||||||
)
|
)
|
||||||
|
@ -49,7 +52,8 @@ class RequestCredentialForm(forms.Form):
|
||||||
did = did[0]
|
did = did[0]
|
||||||
cred = cred[0]
|
cred = cred[0]
|
||||||
try:
|
try:
|
||||||
cred.issue(did)
|
if self.password:
|
||||||
|
cred.issue(did, self.password, domain=self._domain)
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ from django.views.generic.base import TemplateView
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.core.cache import cache
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from idhub.user.forms import (
|
from idhub.user.forms import (
|
||||||
|
@ -140,7 +141,7 @@ class CredentialPdfView(MyWallet, TemplateView):
|
||||||
self.object = get_object_or_404(
|
self.object = get_object_or_404(
|
||||||
VerificableCredential,
|
VerificableCredential,
|
||||||
pk=pk,
|
pk=pk,
|
||||||
public=True,
|
eidas1_did__isnull=False,
|
||||||
user=self.request.user
|
user=self.request.user
|
||||||
)
|
)
|
||||||
self.url_id = "{}://{}/public/credentials/{}".format(
|
self.url_id = "{}://{}/public/credentials/{}".format(
|
||||||
|
@ -150,7 +151,7 @@ class CredentialPdfView(MyWallet, TemplateView):
|
||||||
)
|
)
|
||||||
|
|
||||||
data = self.build_certificate()
|
data = self.build_certificate()
|
||||||
if DID.objects.filter(eidas1=True).exists():
|
if self.object.eidas1_did:
|
||||||
doc = self.insert_signature(data)
|
doc = self.insert_signature(data)
|
||||||
else:
|
else:
|
||||||
doc = data
|
doc = data
|
||||||
|
@ -221,10 +222,11 @@ class CredentialPdfView(MyWallet, TemplateView):
|
||||||
return base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
return base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
||||||
|
|
||||||
def get_pfx_data(self):
|
def get_pfx_data(self):
|
||||||
did = DID.objects.filter(eidas1=True).first()
|
did = self.object.eidas1_did
|
||||||
if not did:
|
if not did:
|
||||||
return None, None
|
return None, None
|
||||||
key_material = json.loads(did.key_material)
|
pw = cache.get("KEY_DIDS")
|
||||||
|
key_material = json.loads(did.get_key_material(pw))
|
||||||
cert = key_material.get("cert")
|
cert = key_material.get("cert")
|
||||||
passphrase = key_material.get("passphrase")
|
passphrase = key_material.get("passphrase")
|
||||||
if cert and passphrase:
|
if cert and passphrase:
|
||||||
|
@ -274,7 +276,15 @@ class CredentialJsonView(MyWallet, TemplateView):
|
||||||
pk=pk,
|
pk=pk,
|
||||||
user=self.request.user
|
user=self.request.user
|
||||||
)
|
)
|
||||||
response = HttpResponse(self.object.data, content_type="application/json")
|
pass_enc = self.request.session.get("key_did")
|
||||||
|
data = ""
|
||||||
|
if pass_enc:
|
||||||
|
user_pass = self.request.user.decrypt_data(
|
||||||
|
pass_enc,
|
||||||
|
self.request.user.password+self.request.session._session_key
|
||||||
|
)
|
||||||
|
data = self.object.get_data(user_pass)
|
||||||
|
response = HttpResponse(data, content_type="application/json")
|
||||||
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -286,6 +296,7 @@ class PublicCredentialJsonView(View):
|
||||||
self.object = get_object_or_404(
|
self.object = get_object_or_404(
|
||||||
VerificableCredential,
|
VerificableCredential,
|
||||||
hash=pk,
|
hash=pk,
|
||||||
|
eidas1_did__isnull=False,
|
||||||
)
|
)
|
||||||
response = HttpResponse(self.object.data, content_type="application/json")
|
response = HttpResponse(self.object.data, content_type="application/json")
|
||||||
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
||||||
|
@ -302,6 +313,18 @@ class CredentialsRequestView(MyWallet, FormView):
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs['user'] = self.request.user
|
kwargs['user'] = self.request.user
|
||||||
|
kwargs['lang'] = self.request.LANGUAGE_CODE
|
||||||
|
domain = "{}://{}".format(self.request.scheme, self.request.get_host())
|
||||||
|
kwargs['domain'] = domain
|
||||||
|
pass_enc = self.request.session.get("key_did")
|
||||||
|
if pass_enc:
|
||||||
|
user_pass = self.request.user.decrypt_data(
|
||||||
|
pass_enc,
|
||||||
|
self.request.user.password+self.request.session._session_key
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pass_enc = None
|
||||||
|
kwargs['password'] = user_pass
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -366,13 +389,17 @@ class DidRegisterView(MyWallet, CreateView):
|
||||||
icon = 'bi bi-patch-check-fill'
|
icon = 'bi bi-patch-check-fill'
|
||||||
wallet = True
|
wallet = True
|
||||||
model = DID
|
model = DID
|
||||||
fields = ('label',)
|
fields = ('label', 'type')
|
||||||
success_url = reverse_lazy('idhub:user_dids')
|
success_url = reverse_lazy('idhub:user_dids')
|
||||||
object = None
|
object = None
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.set_did()
|
pw = self.request.user.decrypt_data(
|
||||||
|
self.request.session.get("key_did"),
|
||||||
|
self.request.user.password+self.request.session._session_key
|
||||||
|
)
|
||||||
|
form.instance.set_did(pw)
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(self.request, _('DID created successfully'))
|
messages.success(self.request, _('DID created successfully'))
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.contrib.auth import login as auth_login
|
from django.contrib.auth import login as auth_login
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
|
||||||
|
from idhub.models import DID
|
||||||
|
from trustchain_idhub import settings
|
||||||
|
|
||||||
|
|
||||||
class LoginView(auth_views.LoginView):
|
class LoginView(auth_views.LoginView):
|
||||||
|
@ -21,8 +27,45 @@ class LoginView(auth_views.LoginView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
user = form.get_user()
|
user = form.get_user()
|
||||||
|
password = form.cleaned_data.get("password")
|
||||||
|
auth_login(self.request, user)
|
||||||
|
|
||||||
|
sensitive_data_encryption_key = user.decrypt_sensitive_data(password)
|
||||||
|
|
||||||
if not user.is_anonymous and user.is_admin:
|
if not user.is_anonymous and user.is_admin:
|
||||||
admin_dashboard = reverse_lazy('idhub:admin_dashboard')
|
admin_dashboard = reverse_lazy('idhub:admin_dashboard')
|
||||||
self.extra_context['success_url'] = admin_dashboard
|
self.extra_context['success_url'] = admin_dashboard
|
||||||
auth_login(self.request, user)
|
# encryption_key = user.encrypt_data(
|
||||||
|
# sensitive_data_encryption_key,
|
||||||
|
# settings.SECRET_KEY
|
||||||
|
# )
|
||||||
|
# cache.set("KEY_DIDS", encryption_key, None)
|
||||||
|
cache.set("KEY_DIDS", sensitive_data_encryption_key, None)
|
||||||
|
|
||||||
|
self.request.session["key_did"] = user.encrypt_data(
|
||||||
|
sensitive_data_encryption_key,
|
||||||
|
user.password+self.request.session._session_key
|
||||||
|
)
|
||||||
|
|
||||||
return HttpResponseRedirect(self.extra_context['success_url'])
|
return HttpResponseRedirect(self.extra_context['success_url'])
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
|
||||||
|
template_name = 'auth/password_reset_confirm.html'
|
||||||
|
success_url = reverse_lazy('idhub:password_reset_complete')
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
password = form.cleaned_data.get("password")
|
||||||
|
user = form.get_user()
|
||||||
|
user.set_encrypted_sensitive_data(password)
|
||||||
|
user.save()
|
||||||
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
|
def serve_did(request, did_id):
|
||||||
|
id_did = f'did:web:{settings.DOMAIN}:did-registry:{did_id}'
|
||||||
|
did = get_object_or_404(DID, did=id_did)
|
||||||
|
document = did.didweb_document
|
||||||
|
retval = HttpResponse(document)
|
||||||
|
retval.headers["Content-Type"] = "application/json"
|
||||||
|
return retval
|
||||||
|
|
|
@ -31,4 +31,3 @@ class ProfileForm(forms.ModelForm):
|
||||||
|
|
||||||
return last_name
|
return last_name
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2024-01-11 10:17
|
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -48,6 +48,8 @@ class Migration(migrations.Migration):
|
||||||
blank=True, max_length=255, null=True, verbose_name='Last name'
|
blank=True, max_length=255, null=True, verbose_name='Last name'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
('encrypted_sensitive_data', models.CharField(max_length=255)),
|
||||||
|
('salt', models.CharField(max_length=255)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import nacl
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from nacl import pwhash
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.core.cache import cache
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
|
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
|
||||||
|
|
||||||
|
@ -44,6 +49,8 @@ class User(AbstractBaseUser):
|
||||||
is_admin = models.BooleanField(default=False)
|
is_admin = models.BooleanField(default=False)
|
||||||
first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True)
|
first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True)
|
||||||
last_name = models.CharField(_("Last name"), max_length=255, blank=True, null=True)
|
last_name = models.CharField(_("Last name"), max_length=255, blank=True, null=True)
|
||||||
|
encrypted_sensitive_data = models.CharField(max_length=255)
|
||||||
|
salt = models.CharField(max_length=255)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
@ -86,3 +93,65 @@ class User(AbstractBaseUser):
|
||||||
for r in s.service.rol.all():
|
for r in s.service.rol.all():
|
||||||
roles.append(r.name)
|
roles.append(r.name)
|
||||||
return ", ".join(set(roles))
|
return ", ".join(set(roles))
|
||||||
|
|
||||||
|
def derive_key_from_password(self, password):
|
||||||
|
kdf = pwhash.argon2i.kdf
|
||||||
|
ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE
|
||||||
|
mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE
|
||||||
|
return kdf(
|
||||||
|
nacl.secret.SecretBox.KEY_SIZE,
|
||||||
|
password,
|
||||||
|
self.get_salt(),
|
||||||
|
opslimit=ops,
|
||||||
|
memlimit=mem
|
||||||
|
)
|
||||||
|
|
||||||
|
def decrypt_sensitive_data(self, password, data=None):
|
||||||
|
sb_key = self.derive_key_from_password(password.encode('utf-8'))
|
||||||
|
sb = nacl.secret.SecretBox(sb_key)
|
||||||
|
if not data:
|
||||||
|
data = self.get_encrypted_sensitive_data()
|
||||||
|
if not isinstance(data, bytes):
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
|
||||||
|
return sb.decrypt(data).decode('utf-8')
|
||||||
|
|
||||||
|
def encrypt_sensitive_data(self, password, data):
|
||||||
|
sb_key = self.derive_key_from_password(password.encode('utf-8'))
|
||||||
|
sb = nacl.secret.SecretBox(sb_key)
|
||||||
|
if not isinstance(data, bytes):
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
|
||||||
|
return base64.b64encode(sb.encrypt(data)).decode('utf-8')
|
||||||
|
|
||||||
|
def get_salt(self):
|
||||||
|
return base64.b64decode(self.salt.encode('utf-8'))
|
||||||
|
|
||||||
|
def set_salt(self):
|
||||||
|
self.salt = base64.b64encode(nacl.utils.random(16)).decode('utf-8')
|
||||||
|
|
||||||
|
def get_encrypted_sensitive_data(self):
|
||||||
|
return base64.b64decode(self.encrypted_sensitive_data.encode('utf-8'))
|
||||||
|
|
||||||
|
def set_encrypted_sensitive_data(self, password):
|
||||||
|
key = base64.b64encode(nacl.utils.random(64))
|
||||||
|
self.set_salt()
|
||||||
|
|
||||||
|
key_crypted = self.encrypt_sensitive_data(password, key)
|
||||||
|
self.encrypted_sensitive_data = key_crypted
|
||||||
|
|
||||||
|
def encrypt_data(self, data, password):
|
||||||
|
sb = self.get_secret_box(password)
|
||||||
|
value_enc = sb.encrypt(data.encode('utf-8'))
|
||||||
|
return base64.b64encode(value_enc).decode('utf-8')
|
||||||
|
|
||||||
|
def decrypt_data(self, data, password):
|
||||||
|
# import pdb; pdb.set_trace()
|
||||||
|
sb = self.get_secret_box(password)
|
||||||
|
value = base64.b64decode(data.encode('utf-8'))
|
||||||
|
return sb.decrypt(value).decode('utf-8')
|
||||||
|
|
||||||
|
def get_secret_box(self, password):
|
||||||
|
pw = base64.b64decode(password.encode('utf-8')*4)
|
||||||
|
sb_key = self.derive_key_from_password(pw)
|
||||||
|
return nacl.secret.SecretBox(sb_key)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2024-01-11 10:17
|
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2024-01-11 10:17
|
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
|
@ -9,10 +9,10 @@ pandas==2.1.1
|
||||||
xlrd==2.0.1
|
xlrd==2.0.1
|
||||||
odfpy==1.4.1
|
odfpy==1.4.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
didkit==0.3.2
|
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
jsonref==1.1.0
|
jsonref==1.1.0
|
||||||
pyld==2.0.3
|
pyld==2.0.3
|
||||||
|
pynacl==1.5.0
|
||||||
more-itertools==10.1.0
|
more-itertools==10.1.0
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
PyPDF2
|
PyPDF2
|
||||||
|
@ -25,3 +25,5 @@ qrcode
|
||||||
uharfbuzz==0.38.0
|
uharfbuzz==0.38.0
|
||||||
fontTools==4.47.0
|
fontTools==4.47.0
|
||||||
weasyprint==60.2
|
weasyprint==60.2
|
||||||
|
ujson==5.9.0
|
||||||
|
didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||||
|
|
|
@ -1,17 +1,47 @@
|
||||||
{
|
{
|
||||||
"$id": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/membership-card-schema.json",
|
"$id": "https://idhub.pangea.org/vc_schemas/membership-card.json",
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"name": "MembershipCard",
|
"title": "Membership Card",
|
||||||
"description": "MembershipCard credential using JsonSchema",
|
"description": "The membership card specifies an individual's subscription or enrollment in specific services or benefits issued by an organization.",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "Membership Card",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Carnet de soci/a",
|
||||||
|
"lang": "ca_ES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Carnet de socio/a",
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "https://idhub.pangea.org/vc_schemas/ebsi/attestation.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"credentialSubject": {
|
"credentialSubject": {
|
||||||
|
"description": "Defines additional properties on credentialSubject",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Defines a unique identifier of the credential subject",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"organisation": {
|
"organisation": {
|
||||||
|
"description": "Organisation the credential subject is affiliated with",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"membershipType": {
|
"membershipType": {
|
||||||
|
"description": "Type of membership",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"membershipId": {
|
||||||
|
"description": "Membership identifier",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"affiliatedSince": {
|
"affiliatedSince": {
|
||||||
|
@ -25,26 +55,23 @@
|
||||||
"typeOfPerson": {
|
"typeOfPerson": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"individual",
|
"natural",
|
||||||
"org"
|
"legal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"identityDocType": {
|
"identityDocType": {
|
||||||
"type": "string",
|
"description": "Type of the Identity Document of the credential subject",
|
||||||
"enum": [
|
"type": "string"
|
||||||
"DNI",
|
|
||||||
"NIF",
|
|
||||||
"NIE",
|
|
||||||
"PASSPORT"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"identityNumber": {
|
"identityNumber": {
|
||||||
|
"description": "Number of the Identity Document of the credential subject",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"name": {
|
"firstName": {
|
||||||
|
"description": "Name of the natural person or name of the legal person (organisation)",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"surnames": {
|
"lastName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
|
@ -53,13 +80,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
"id",
|
||||||
"organisation",
|
"organisation",
|
||||||
"affiliatedSince",
|
"affiliatedSince",
|
||||||
"typeOfPerson",
|
"typeOfPerson",
|
||||||
"name",
|
"firstName",
|
||||||
"surnames",
|
|
||||||
"email"
|
"email"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -149,6 +149,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
@ -222,4 +223,4 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_PUBLIC_CREDENTIALS = True
|
ORGANIZATION = config('ORGANIZATION', 'Pangea')
|
||||||
|
|
|
@ -6,6 +6,8 @@ import jinja2
|
||||||
from django.template.backends.django import Template
|
from django.template.backends.django import Template
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
|
|
||||||
|
from trustchain_idhub import settings
|
||||||
|
|
||||||
|
|
||||||
def generate_did_controller_key():
|
def generate_did_controller_key():
|
||||||
return didkit.generate_ed25519_key()
|
return didkit.generate_ed25519_key()
|
||||||
|
@ -15,6 +17,30 @@ def keydid_from_controller_key(key):
|
||||||
return didkit.key_to_did("key", key)
|
return didkit.key_to_did("key", key)
|
||||||
|
|
||||||
|
|
||||||
|
async def resolve_keydid(keydid):
|
||||||
|
return await didkit.resolve_did(keydid, "{}")
|
||||||
|
|
||||||
|
|
||||||
|
def webdid_from_controller_key(key):
|
||||||
|
"""
|
||||||
|
Se siguen los pasos para generar un webdid a partir de un keydid.
|
||||||
|
Documentado en la docu de spruceid.
|
||||||
|
"""
|
||||||
|
keydid = keydid_from_controller_key(key) # "did:key:<...>"
|
||||||
|
pubkeyid = keydid.rsplit(":")[-1] # <...>
|
||||||
|
document = json.loads(asyncio.run(resolve_keydid(keydid))) # Documento DID en terminos "key"
|
||||||
|
webdid_url = f"did:web:{settings.DOMAIN}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
|
||||||
|
webdid_url_owner = webdid_url + "#owner"
|
||||||
|
# Reemplazamos los campos del documento DID necesarios:
|
||||||
|
document["id"] = webdid_url
|
||||||
|
document["verificationMethod"][0]["id"] = webdid_url_owner
|
||||||
|
document["verificationMethod"][0]["controller"] = webdid_url
|
||||||
|
document["authentication"][0] = webdid_url_owner
|
||||||
|
document["assertionMethod"][0] = webdid_url_owner
|
||||||
|
document_fixed_serialized = json.dumps(document)
|
||||||
|
return webdid_url, document_fixed_serialized
|
||||||
|
|
||||||
|
|
||||||
def generate_generic_vc_id():
|
def generate_generic_vc_id():
|
||||||
# TODO agree on a system for Verifiable Credential IDs
|
# TODO agree on a system for Verifiable Credential IDs
|
||||||
return "https://pangea.org/credentials/42"
|
return "https://pangea.org/credentials/42"
|
||||||
|
|
Loading…
Reference in New Issue