Merge pull request 'Pushed ssi functionality to idhub repo' (#75) from ssi into main
Reviewed-on: https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub/pulls/75
This commit is contained in:
commit
b13488fdd4
|
@ -114,12 +114,12 @@ class ImportForm(forms.Form):
|
|||
return user.first()
|
||||
|
||||
def create_credential(self, user, row):
|
||||
d = self.json_schema.copy()
|
||||
d['instance'] = row
|
||||
return VerificableCredential(
|
||||
verified=False,
|
||||
user=user,
|
||||
data=json.dumps(d)
|
||||
csv_data=json.dumps(row),
|
||||
issuer_did=self._did,
|
||||
schema=self._schema,
|
||||
)
|
||||
|
||||
def exception(self, msg):
|
||||
|
|
|
@ -19,7 +19,6 @@ from django.shortcuts import get_object_or_404, redirect
|
|||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from utils.apiregiter import iota
|
||||
from utils import credtools
|
||||
from idhub_auth.models import User
|
||||
from idhub_auth.forms import ProfileForm
|
||||
|
@ -646,7 +645,7 @@ class DidRegisterView(Credentials, CreateView):
|
|||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
form.instance.did = iota.issue_did()
|
||||
form.instance.set_did()
|
||||
form.save()
|
||||
messages.success(self.request, _('DID created successfully'))
|
||||
Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.5 on 2023-11-14 16:32
|
||||
# Generated by Django 4.2.5 on 2023-11-15 09:58
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
@ -13,6 +13,33 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DID',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('created_at', models.DateTimeField(auto_now=True)),
|
||||
('label', models.CharField(max_length=50)),
|
||||
('did', models.CharField(max_length=250)),
|
||||
('key_material', models.CharField(max_length=250)),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='dids',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='File_datas',
|
||||
fields=[
|
||||
|
@ -141,9 +168,9 @@ class Migration(migrations.Migration):
|
|||
('verified', models.BooleanField()),
|
||||
('created_on', models.DateTimeField(auto_now=True)),
|
||||
('issued_on', models.DateTimeField(null=True)),
|
||||
('did_issuer', models.CharField(max_length=250)),
|
||||
('did_subject', models.CharField(max_length=250)),
|
||||
('subject_did', models.CharField(max_length=250)),
|
||||
('data', models.TextField()),
|
||||
('csv_data', models.TextField()),
|
||||
(
|
||||
'status',
|
||||
models.PositiveSmallIntegerField(
|
||||
|
@ -156,6 +183,22 @@ class Migration(migrations.Migration):
|
|||
default=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
'issuer_did',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='vcredentials',
|
||||
to='idhub.did',
|
||||
),
|
||||
),
|
||||
(
|
||||
'schema',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='vcredentials',
|
||||
to='idhub.schemas',
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
|
@ -275,32 +318,6 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DID',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('created_at', models.DateTimeField(auto_now=True)),
|
||||
('did', models.CharField(max_length=250, unique=True)),
|
||||
('label', models.CharField(max_length=50)),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='dids',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserRol',
|
||||
fields=[
|
||||
|
|
|
@ -2,7 +2,13 @@ import json
|
|||
import requests
|
||||
import datetime
|
||||
from django.db import models
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from utils.idhub_ssikit import (
|
||||
generate_did_controller_key,
|
||||
keydid_from_controller_key,
|
||||
sign_credential,
|
||||
)
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
||||
|
@ -396,15 +402,18 @@ class Event(models.Model):
|
|||
|
||||
class DID(models.Model):
|
||||
created_at = models.DateTimeField(auto_now=True)
|
||||
did = models.CharField(max_length=250, unique=True)
|
||||
label = models.CharField(max_length=50)
|
||||
did = models.CharField(max_length=250)
|
||||
# In JWK format. Must be stored as-is and passed whole to library functions.
|
||||
# Example key material:
|
||||
# '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}'
|
||||
key_material = models.CharField(max_length=250)
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='dids',
|
||||
null=True,
|
||||
)
|
||||
# kind = "KEY|WEB"
|
||||
|
||||
@property
|
||||
def is_organization_did(self):
|
||||
|
@ -412,6 +421,13 @@ class DID(models.Model):
|
|||
return True
|
||||
return False
|
||||
|
||||
def set_did(self):
|
||||
self.key_material = generate_did_controller_key()
|
||||
self.did = keydid_from_controller_key(self.key_material)
|
||||
|
||||
def get_key(self):
|
||||
return json.loads(self.key_material)
|
||||
|
||||
|
||||
class Schemas(models.Model):
|
||||
file_schema = models.CharField(max_length=250)
|
||||
|
@ -445,9 +461,9 @@ class VerificableCredential(models.Model):
|
|||
verified = models.BooleanField()
|
||||
created_on = models.DateTimeField(auto_now=True)
|
||||
issued_on = models.DateTimeField(null=True)
|
||||
did_issuer = models.CharField(max_length=250)
|
||||
did_subject = models.CharField(max_length=250)
|
||||
subject_did = models.CharField(max_length=250)
|
||||
data = models.TextField()
|
||||
csv_data = models.TextField()
|
||||
status = models.PositiveSmallIntegerField(
|
||||
choices=Status.choices,
|
||||
default=Status.ENABLED
|
||||
|
@ -457,6 +473,16 @@ class VerificableCredential(models.Model):
|
|||
on_delete=models.CASCADE,
|
||||
related_name='vcredentials',
|
||||
)
|
||||
issuer_did = models.ForeignKey(
|
||||
DID,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='vcredentials',
|
||||
)
|
||||
schema = models.ForeignKey(
|
||||
Schemas,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='vcredentials',
|
||||
)
|
||||
|
||||
@property
|
||||
def get_schema(self):
|
||||
|
@ -474,16 +500,49 @@ class VerificableCredential(models.Model):
|
|||
return self.Status(self.status).label
|
||||
|
||||
def get_datas(self):
|
||||
data = json.loads(self.data).get('instance').items()
|
||||
data = json.loads(self.csv_data).items()
|
||||
return data
|
||||
|
||||
def issue(self, did):
|
||||
if self.status == self.Status.ISSUED:
|
||||
return
|
||||
|
||||
self.status = self.Status.ISSUED
|
||||
self.did_subject = did
|
||||
self.subject_did = did
|
||||
self.issued_on = datetime.datetime.now()
|
||||
self.data = sign_credential(
|
||||
self.render(),
|
||||
self.issuer_did.key_material
|
||||
)
|
||||
|
||||
def get_context(self):
|
||||
d = json.loads(self.csv_data)
|
||||
format = "%Y-%m-%dT%H:%M:%SZ"
|
||||
issuance_date = self.issued_on.strftime(format)
|
||||
|
||||
context = {
|
||||
'vc_id': self.id,
|
||||
'issuer_did': self.issuer_did.did,
|
||||
'subject_did': self.subject_did,
|
||||
'issuance_date': issuance_date,
|
||||
}
|
||||
context.update(d)
|
||||
return context
|
||||
|
||||
def render(self):
|
||||
context = self.get_context()
|
||||
template_name = 'credentials/{}'.format(
|
||||
self.schema.file_schema
|
||||
)
|
||||
tmpl = get_template(template_name)
|
||||
return tmpl.render(context)
|
||||
|
||||
|
||||
def get_issued_on(self):
|
||||
return self.issued_on.strftime("%m/%d/%Y")
|
||||
if self.issued_on:
|
||||
return self.issued_on.strftime("%m/%d/%Y")
|
||||
|
||||
return ''
|
||||
|
||||
class VCTemplate(models.Model):
|
||||
wkit_template_id = models.CharField(max_length=250)
|
||||
|
|
32
idhub/templates/credentials/member.json
Normal file
32
idhub/templates/credentials/member.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
{
|
||||
"name": "https://schema.org/name",
|
||||
"email": "https://schema.org/email",
|
||||
"membershipType": "https://schema.org/memberOf",
|
||||
"individual": "https://schema.org/Person",
|
||||
"organization": "https://schema.org/Organization",
|
||||
"Member": "https://schema.org/Member",
|
||||
"startDate": "https://schema.org/startDate",
|
||||
"jsonSchema": "https://schema.org/jsonSchema",
|
||||
"$ref": "https://schema.org/jsonSchemaRef"
|
||||
}
|
||||
],
|
||||
"type": ["VerifiableCredential", "VerifiableAttestation"],
|
||||
"id": "{{ vc_id }}",
|
||||
"issuer": "{{ issuer_did }}",
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"credentialSubject": {
|
||||
"id": "{{ subject_did }}",
|
||||
"Member": {
|
||||
"name": "{{ name }}",
|
||||
"email": "{{ email }}",
|
||||
"membershipType": "{{ membershipType }}",
|
||||
"startDate": "{{ startDate }}"
|
||||
},
|
||||
"jsonSchema": {
|
||||
"$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/member-schema.json"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ from django.shortcuts import get_object_or_404, redirect
|
|||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from utils.apiregiter import iota
|
||||
from idhub.user.forms import ProfileForm, RequestCredentialForm, CredentialPresentationForm
|
||||
from idhub.mixins import UserView
|
||||
from idhub.models import DID, VerificableCredential, Event
|
||||
|
@ -190,7 +189,7 @@ class DidRegisterView(MyWallet, CreateView):
|
|||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
form.instance.did = iota.issue_did()
|
||||
form.instance.set_did()
|
||||
form.save()
|
||||
messages.success(self.request, _('DID created successfully'))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.5 on 2023-11-14 16:32
|
||||
# Generated by Django 4.2.5 on 2023-11-15 09:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -6,5 +6,7 @@ python-decouple==3.8
|
|||
jsonschema==4.19.1
|
||||
pandas==2.1.1
|
||||
requests==2.31.0
|
||||
didkit==0.3.2
|
||||
jinja2==3.1.2
|
||||
jsonref==1.1.0
|
||||
pyld==2.0.3
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"$id": "https://pangea.org/schemas/member-credential-schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"name": "MemberCredential",
|
||||
"description": "MemberCredential using JsonSchemaCredential",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"membershipType": {
|
||||
"type": "string",
|
||||
"enum": ["individual", "organization"]
|
||||
}
|
||||
},
|
||||
"required": ["name", "email", "membershipType"]
|
||||
}
|
|
@ -1,21 +1,4 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/credentials/v2",
|
||||
"https://www.w3.org/ns/credentials/examples/v2"
|
||||
],
|
||||
"id": "https://example.com/credentials/3734",
|
||||
"type": ["VerifiableCredential", "JsonSchemaCredential"],
|
||||
"issuer": "https://pangea.org/issuers/10",
|
||||
"issuanceDate": "2023-09-01T19:23:24Z",
|
||||
"credentialSchema": {
|
||||
"id": "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json",
|
||||
"type": "JsonSchema",
|
||||
"digestSRI": "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW"
|
||||
},
|
||||
"credentialSubject": {
|
||||
"id": "https://pangea.org/schemas/member-credential-schema.json",
|
||||
"type": "JsonSchema",
|
||||
"jsonSchema": {
|
||||
{
|
||||
"$id": "https://pangea.org/schemas/member-credential-schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"name": "MemberCredential",
|
||||
|
@ -36,5 +19,3 @@
|
|||
},
|
||||
"required": ["name", "email", "membershipType"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
WALLETKITD = 'http://localhost:8080/'
|
||||
ISSUER = f'{WALLETKITD}issuer-api/default/'
|
||||
VERIFIER = f'{WALLETKITD}verifier-api/default/'
|
||||
|
||||
default_ctype_header = {
|
||||
'Content-Type': 'application/json', # specify the type of data you're sending
|
||||
'Accept': 'application/json', # specify the type of data you can accept
|
||||
}
|
||||
|
||||
|
||||
def include_str(path):
|
||||
with open(path, "r") as f:
|
||||
return f.read().strip()
|
||||
|
||||
|
||||
# Create DID for tenant
|
||||
# Valid methods: 'key'|'web'
|
||||
def user_create_did(did_method):
|
||||
url = f'{ISSUER}config/did/create'
|
||||
data = {
|
||||
'method': did_method
|
||||
}
|
||||
response = requests.post(url, json=data, headers=default_ctype_header)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
|
||||
|
||||
def admin_create_template(template_name, template_body):
|
||||
url = f'{ISSUER}config/templates/{template_name}'
|
||||
body = template_body
|
||||
response = requests.post(url, data=body, headers=default_ctype_header)
|
||||
response.raise_for_status()
|
||||
return
|
||||
|
||||
|
||||
def user_issue_vc(vc_name, vc_params):
|
||||
url = f'{ISSUER}credentials/issuance/request'
|
||||
# ...
|
||||
# TODO examine cross-device issuance workflow
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TENANT_CFG_TMEPLATE = include_str("./TENANT_CFG_TEMPLATE")
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import uuid
|
||||
import hashlib
|
||||
|
||||
|
||||
class Iota:
|
||||
"""
|
||||
Framework for simulate the comunication with IOTA DLT
|
||||
"""
|
||||
|
||||
def issue_did(self):
|
||||
u = str(uuid.uuid4()).encode()
|
||||
d = hashlib.sha3_256(u).hexdigest()
|
||||
did = "did:iota:{}".format(d)
|
||||
return did
|
||||
|
||||
|
||||
iota = Iota()
|
0
utils/idhub_ssikit/README.md
Normal file
0
utils/idhub_ssikit/README.md
Normal file
74
utils/idhub_ssikit/__init__.py
Normal file
74
utils/idhub_ssikit/__init__.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import didkit
|
||||
import json
|
||||
import jinja2
|
||||
from django.template.backends.django import Template
|
||||
|
||||
|
||||
def generate_did_controller_key():
|
||||
return didkit.generate_ed25519_key()
|
||||
|
||||
|
||||
def keydid_from_controller_key(key):
|
||||
return didkit.key_to_did("key", key)
|
||||
|
||||
|
||||
def generate_generic_vc_id():
|
||||
# TODO agree on a system for Verifiable Credential IDs
|
||||
return "https://pangea.org/credentials/42"
|
||||
|
||||
|
||||
def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data: dict[str, str]):
|
||||
"""
|
||||
Populates a VC template with data for issuance, and signs the result with the provided key.
|
||||
|
||||
The `vc_data` parameter must at a minimum include:
|
||||
* issuer_did
|
||||
* subject_did
|
||||
* vc_id
|
||||
and must include whatever other fields are relevant for the vc_template to be instantiated.
|
||||
|
||||
The following field(s) will be auto-generated if not passed in `vc_data`:
|
||||
* issuance_date (to `datetime.datetime.now()`)
|
||||
"""
|
||||
async def inner():
|
||||
unsigned_vc = vc_template.render(vc_data)
|
||||
signed_vc = await didkit.issue_credential(
|
||||
unsigned_vc,
|
||||
'{"proofFormat": "ldp"}',
|
||||
jwk_issuer
|
||||
)
|
||||
return signed_vc
|
||||
|
||||
if vc_data.get("issuance_date") is None:
|
||||
vc_data["issuance_date"] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||
|
||||
return asyncio.run(inner())
|
||||
|
||||
|
||||
def sign_credential(unsigned_vc: str, jwk_issuer):
|
||||
"""
|
||||
Signs the and unsigned credential with the provided key.
|
||||
"""
|
||||
async def inner():
|
||||
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):
|
||||
"""
|
||||
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 asyncio.run(inner())
|
Loading…
Reference in a new issue