Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
Cayo Puigdefabregas | c5f97ef1e4 | |
Cayo Puigdefabregas | 23c081502e | |
Cayo Puigdefabregas | 1b90d6966b | |
Cayo Puigdefabregas | 8f15544fe5 | |
Cayo Puigdefabregas | 338ab6e083 | |
Cayo Puigdefabregas | 713ab080d6 | |
Cayo Puigdefabregas | b960c87d83 | |
Cayo Puigdefabregas | fd8f404908 | |
Cayo Puigdefabregas | a975d831f9 | |
Cayo Puigdefabregas | 65b4d17d82 | |
Cayo Puigdefabregas | 2d49d1b0cc | |
Cayo Puigdefabregas | 5f84991c7d | |
Cayo Puigdefabregas | 55c917b201 | |
Cayo Puigdefabregas | 1cb52d7fcd |
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"@context": {
|
||||||
|
"legalName": "https://idhub.pangea.org/context/#legalName",
|
||||||
|
"allowedSchemas": "https://idhub.pangea.org/context/#allowedSchemas",
|
||||||
|
"domain": "https://idhub.pangea.org/context/#domain"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.2.5 on 2024-06-19 11:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('idhub', '0005_alter_file_datas_created_at_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='did',
|
||||||
|
name='ether_address',
|
||||||
|
field=models.CharField(max_length=250, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='did',
|
||||||
|
name='ether_privkey',
|
||||||
|
field=models.CharField(max_length=250, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.5 on 2024-06-20 07:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('idhub', '0006_did_ether_address_did_ether_privkey'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='did',
|
||||||
|
name='credential_as_issuer',
|
||||||
|
field=models.TextField(null=True),
|
||||||
|
),
|
||||||
|
]
|
115
idhub/models.py
115
idhub/models.py
|
@ -3,6 +3,7 @@ import ujson
|
||||||
import pytz
|
import pytz
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
|
import requests
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -16,6 +17,7 @@ from pyvckit.did import (
|
||||||
)
|
)
|
||||||
from pyvckit.sign import sign
|
from pyvckit.sign import sign
|
||||||
from pyvckit.verify import verify_vc
|
from pyvckit.verify import verify_vc
|
||||||
|
from pyvckit.ether import generate_ether_address
|
||||||
|
|
||||||
from oidc4vp.models import Organization
|
from oidc4vp.models import Organization
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
|
@ -442,6 +444,9 @@ class DID(models.Model):
|
||||||
# Example key material:
|
# Example key material:
|
||||||
# '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}'
|
# '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}'
|
||||||
key_material = models.TextField()
|
key_material = models.TextField()
|
||||||
|
ether_address = models.CharField(max_length=250, null=True)
|
||||||
|
ether_privkey = models.CharField(max_length=250, null=True)
|
||||||
|
api_token = models.CharField(max_length=250, null=True)
|
||||||
eidas1 = models.BooleanField(default=False)
|
eidas1 = models.BooleanField(default=False)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
|
@ -451,6 +456,7 @@ class DID(models.Model):
|
||||||
)
|
)
|
||||||
# JSON-serialized DID document
|
# JSON-serialized DID document
|
||||||
didweb_document = models.TextField()
|
didweb_document = models.TextField()
|
||||||
|
credential_as_issuer = models.TextField(null=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_organization_did(self):
|
def is_organization_did(self):
|
||||||
|
@ -459,19 +465,15 @@ class DID(models.Model):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_key_material(self):
|
def get_key_material(self):
|
||||||
user = self.user or self.get_organization()
|
return self.decrypt_data(self.key_material)
|
||||||
return user.decrypt_data(self.key_material)
|
|
||||||
|
|
||||||
def set_key_material(self, value):
|
def set_key_material(self, value):
|
||||||
user = self.user or self.get_organization()
|
self.key_material = self.encrypt_data(value)
|
||||||
if not user.encrypted_sensitive_data:
|
|
||||||
user.set_encrypted_sensitive_data()
|
|
||||||
user.save()
|
|
||||||
self.key_material = user.encrypt_data(value)
|
|
||||||
|
|
||||||
def set_did(self):
|
def set_did(self):
|
||||||
new_key_material = generate_keys()
|
new_key_material = generate_keys()
|
||||||
self.set_key_material(new_key_material)
|
self.set_key_material(new_key_material)
|
||||||
|
self.set_ether_address()
|
||||||
|
|
||||||
if self.type == self.Types.KEY:
|
if self.type == self.Types.KEY:
|
||||||
self.did = generate_did(new_key_material)
|
self.did = generate_did(new_key_material)
|
||||||
|
@ -485,7 +487,18 @@ class DID(models.Model):
|
||||||
|
|
||||||
self.did = generate_did(new_key_material, url)
|
self.did = generate_did(new_key_material, url)
|
||||||
key = json.loads(new_key_material)
|
key = json.loads(new_key_material)
|
||||||
url, self.didweb_document = gen_did_document(self.did, key)
|
url, didweb_document = gen_did_document(self.did, key)
|
||||||
|
if self.ether_address:
|
||||||
|
didweb_document = json.loads(didweb_document)
|
||||||
|
id_service = "{}#ethereum".format(self.did)
|
||||||
|
service = {
|
||||||
|
"id": id_service,
|
||||||
|
"type": "Ethereum",
|
||||||
|
"address": self.ether_address
|
||||||
|
}
|
||||||
|
didweb_document['service'].append(service)
|
||||||
|
didweb_document = json.dumps(didweb_document)
|
||||||
|
self.didweb_document = didweb_document
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
return json.loads(self.key_material)
|
return json.loads(self.key_material)
|
||||||
|
@ -493,6 +506,85 @@ class DID(models.Model):
|
||||||
def get_organization(self):
|
def get_organization(self):
|
||||||
return Organization.objects.get(main=True)
|
return Organization.objects.get(main=True)
|
||||||
|
|
||||||
|
def set_ether_address(self):
|
||||||
|
if self.ether_address:
|
||||||
|
return
|
||||||
|
|
||||||
|
priv, self.ether_address = generate_ether_address()
|
||||||
|
self.ether_privkey = self.encrypt_data(priv)
|
||||||
|
|
||||||
|
def encrypt_data(self, value):
|
||||||
|
user = self.user or self.get_organization()
|
||||||
|
if not user.encrypted_sensitive_data:
|
||||||
|
user.set_encrypted_sensitive_data()
|
||||||
|
user.save()
|
||||||
|
return user.encrypt_data(value)
|
||||||
|
|
||||||
|
def decrypt_data(self, value):
|
||||||
|
user = self.user or self.get_organization()
|
||||||
|
return user.decrypt_data(value)
|
||||||
|
|
||||||
|
def send_api(self, data, token=settings.TOKEN_TA_API):
|
||||||
|
url = settings.VERIFIABLE_REGISTER_URL
|
||||||
|
if not url or not token:
|
||||||
|
return
|
||||||
|
|
||||||
|
headers = {"Authenticate": "Bearer {}".format(token)}
|
||||||
|
|
||||||
|
response = requests.post(url=url, data=data, headers=headers)
|
||||||
|
if response.status_code >= 300:
|
||||||
|
return
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def send_credential_as_issuer_to_TA(self):
|
||||||
|
credential = self._render_credential_issuer()
|
||||||
|
response = self.send_api(credential)
|
||||||
|
self.credential_as_issuer = json.dumps(response)
|
||||||
|
|
||||||
|
def get_context(self):
|
||||||
|
format = "%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
issuance_date = datetime.datetime.now().strftime(format)
|
||||||
|
credential_status_id = 'https://revocation.not.supported/'
|
||||||
|
org = Organization.objects.get(main=True)
|
||||||
|
allow_schemas = [x.url for x in Schemas.objects.all()]
|
||||||
|
context = {
|
||||||
|
"vc_id": "",
|
||||||
|
"id_credential": "",
|
||||||
|
"issuer_did": "",
|
||||||
|
"organization": "",
|
||||||
|
"validUntil": "",
|
||||||
|
"issuance_date": issuance_date,
|
||||||
|
"subject_did": self.did,
|
||||||
|
"legalName": org.name or "",
|
||||||
|
"allowedSchemas": allow_schemas,
|
||||||
|
"domain": self.org.domain,
|
||||||
|
"credential_status_id": credential_status_id,
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _render_credential_issuer(self):
|
||||||
|
context = self.get_context()
|
||||||
|
template_name = "credentials/ereuse-issuer.json"
|
||||||
|
tmpl = get_template(template_name)
|
||||||
|
credential = ujson.loads(tmpl.render(context))
|
||||||
|
credential.pop("credentialStatus", None)
|
||||||
|
|
||||||
|
return ujson.dumps(credential)
|
||||||
|
|
||||||
|
def get_api_token(self):
|
||||||
|
if self.api_token:
|
||||||
|
return self.decrypt_data(self.api_token)
|
||||||
|
|
||||||
|
priv = self.decrypt_data(self.ether_privkey)
|
||||||
|
response = self.send_api(priv)
|
||||||
|
if not response:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.api_token = self.encrypt_data(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class Schemas(models.Model):
|
class Schemas(models.Model):
|
||||||
type = models.CharField(max_length=250)
|
type = models.CharField(max_length=250)
|
||||||
file_schema = models.CharField(_('Schema'), max_length=250)
|
file_schema = models.CharField(_('Schema'), max_length=250)
|
||||||
|
@ -785,6 +877,13 @@ class VerificableCredential(models.Model):
|
||||||
new_dict[key] = value
|
new_dict[key] = value
|
||||||
return new_dict
|
return new_dict
|
||||||
|
|
||||||
|
def send_api(self):
|
||||||
|
token = self.issuer_did.get_api_token()
|
||||||
|
data = self.user.decrypt_data(self.data)
|
||||||
|
response = self.issuer_did.did.send_api(data, token=token)
|
||||||
|
if response:
|
||||||
|
self.subject_did.did.get_api_token()
|
||||||
|
|
||||||
|
|
||||||
class VCTemplate(models.Model):
|
class VCTemplate(models.Model):
|
||||||
wkit_template_id = models.CharField(max_length=250)
|
wkit_template_id = models.CharField(max_length=250)
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/context/base.jsonld",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/context/e-operator-claim.jsonld"
|
"https://idhub.pangea.org/context/e-actors.jsonld"
|
||||||
],
|
],
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
"VerifiableAttestation",
|
"VerifiableAttestation",
|
||||||
"EOperatorClaim"
|
"EActors"
|
||||||
],
|
],
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"id": "{{ issuer_did }}",
|
"id": "{{ issuer_did }}",
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
"revocationBitmapIndex": "{{ id_credential }}"
|
"revocationBitmapIndex": "{{ id_credential }}"
|
||||||
},
|
},
|
||||||
"credentialSchema": {
|
"credentialSchema": {
|
||||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
"id": "https://idhub.pangea.org/vc_schemas/e-actors.json",
|
||||||
"type": "FullJsonSchemaValidator2021"
|
"type": "FullJsonSchemaValidator2021"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
|
"https://idhub.pangea.org/context/e-issuer.jsonld"
|
||||||
|
],
|
||||||
|
"id": "{{ vc_id }}",
|
||||||
|
"type": [
|
||||||
|
"VerifiableCredential",
|
||||||
|
"VerifiableAttestation",
|
||||||
|
"EIssuer"
|
||||||
|
],
|
||||||
|
"issuer": {
|
||||||
|
"id": "{{ issuer_did }}",
|
||||||
|
"name": "{{ organisation }}"
|
||||||
|
},
|
||||||
|
"issuanceDate": "{{ issuance_date }}",
|
||||||
|
"validFrom": "{{ issuance_date }}",
|
||||||
|
"validUntil": "{{ validUntil }}",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "Product and waste electronics operator claim",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Declaració d'operador de productes i residus electrònics",
|
||||||
|
"lang": "ca_ES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Declaración de operador de productos y residuos electrónicos",
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"value": "Credential for e-product and e-waste operator claim",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial per operador de productes i residus electrònics",
|
||||||
|
"lang": "ca_ES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial para operador de productos y residuos electrónicos",
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "{{ subject_did }}",
|
||||||
|
"legalName": "{{ legalName }}",
|
||||||
|
"allowedSchemas": "{{ allowedSchemas }}",
|
||||||
|
"domain": "{{ domain }}"
|
||||||
|
},
|
||||||
|
"credentialStatus": {
|
||||||
|
"id": "{{ credential_status_id }}",
|
||||||
|
"type": "RevocationBitmap2022",
|
||||||
|
"revocationBitmapIndex": "{{ id_credential }}"
|
||||||
|
},
|
||||||
|
"credentialSchema": {
|
||||||
|
"id": "https://idhub.pangea.org/vc_schemas/e-issuer.json",
|
||||||
|
"type": "FullJsonSchemaValidator2021"
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,12 +110,12 @@ def ServeDidView(request, did_id):
|
||||||
revocation_bitmap.serialize()
|
revocation_bitmap.serialize()
|
||||||
)
|
)
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
revocation_service = [{ # This is an object within a list.
|
revocation_service = { # This is an object within a list.
|
||||||
"id": f"{id_did}#revocation",
|
"id": f"{id_did}#revocation",
|
||||||
"type": "RevocationBitmap2022",
|
"type": "RevocationBitmap2022",
|
||||||
"serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}"
|
"serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}"
|
||||||
}]
|
}
|
||||||
document["service"] = revocation_service
|
document["service"][0] = revocation_service
|
||||||
# Serialize the DID + Revocation list in preparation for sending
|
# Serialize the DID + Revocation list in preparation for sending
|
||||||
document = json.dumps(document)
|
document = json.dumps(document)
|
||||||
retval = HttpResponse(document)
|
retval = HttpResponse(document)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$id": "https://idhub.pangea.org/vc_schemas/e-operator-claim.json",
|
"$id": "https://idhub.pangea.org/vc_schemas/e-actors.json",
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"title": "EOperatorClaim",
|
"title": "EActors",
|
||||||
"description": "Product and waste electronics operator claim, as proposed by eReuse.org",
|
"description": "Product and waste electronics operator claim, as proposed by eReuse.org",
|
||||||
"name": [
|
"name": [
|
||||||
{
|
{
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"$id": "https://idhub.pangea.org/vc_schemas/e-issuer.json",
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "EIssuer",
|
||||||
|
"description": "This credential allow to holder to be issuer for a list of schemas",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "Allow to be issuer",
|
||||||
|
"lang": "en"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "https://idhub.pangea.org/vc_schemas/ebsi/attestation.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"credentialSubject": {
|
||||||
|
"description": "Defines properties on credentialSubject",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Defines a unique identifier of the credential subject",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"legalName": {
|
||||||
|
"description": "Legal name of the issuer",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"allowedSchemas": {
|
||||||
|
"description": "List of schemas",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"legalName",
|
||||||
|
"allowedSchemas",
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -242,3 +242,5 @@ CREATE_TEST_USERS = config('CREATE_TEST_USERS', default=False, cast=bool)
|
||||||
ENABLE_2FACTOR_AUTH = config('ENABLE_2FACTOR_AUTH', default=True, cast=bool)
|
ENABLE_2FACTOR_AUTH = config('ENABLE_2FACTOR_AUTH', default=True, cast=bool)
|
||||||
COMMIT = config('COMMIT', default='')
|
COMMIT = config('COMMIT', default='')
|
||||||
|
|
||||||
|
VERIFIABLE_REGISTER_URL = config('VERIFIABLE_REGISTER_URL', default='')
|
||||||
|
TOKEN_TA_API = config('TOKEN_TA_API', default='')
|
||||||
|
|
Loading…
Reference in New Issue