Compare commits

...

14 Commits
release ... dlt

Author SHA1 Message Date
Cayo Puigdefabregas c5f97ef1e4 fix revocation_service not a list 2024-06-21 15:00:03 +02:00
Cayo Puigdefabregas 23c081502e fix list for array 2024-06-21 14:59:30 +02:00
Cayo Puigdefabregas 1b90d6966b add send_api in credential and fix 2024-06-21 14:59:08 +02:00
Cayo Puigdefabregas 8f15544fe5 fix schemas and credentials names 2024-06-21 12:08:42 +02:00
Cayo Puigdefabregas 338ab6e083 fix name EIssuer 2024-06-21 12:04:25 +02:00
Cayo Puigdefabregas 713ab080d6 rename ereuse-issuer 2024-06-21 12:03:30 +02:00
Cayo Puigdefabregas b960c87d83 get token from api dlt 2024-06-21 12:01:55 +02:00
Cayo Puigdefabregas fd8f404908 send credential as issuer to TA 2024-06-20 12:18:53 +02:00
Cayo Puigdefabregas a975d831f9 add credential_as_issuer in models 2024-06-20 10:00:07 +02:00
Cayo Puigdefabregas 65b4d17d82 add ereuse-issuer credential 2024-06-20 09:24:28 +02:00
Cayo Puigdefabregas 2d49d1b0cc fix ereuse-roles 2024-06-20 09:05:58 +02:00
Cayo Puigdefabregas 5f84991c7d fix renamed e-operator for ereuse-roles 2024-06-20 09:04:59 +02:00
Cayo Puigdefabregas 55c917b201 rename e-operator schema 2024-06-19 16:23:04 +02:00
Cayo Puigdefabregas 1cb52d7fcd add ether address 2024-06-19 16:19:04 +02:00
11 changed files with 279 additions and 17 deletions

7
context/e-issuer.jsonld Normal file
View File

@ -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"
}
}

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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)

View File

@ -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"
} }
} }

View File

@ -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"
}
}

View File

@ -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)

View File

@ -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": [
{ {
@ -75,4 +75,4 @@
} }
} }
] ]
} }

52
schemas/e-issuer.json Normal file
View File

@ -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"
]
}
}
}
]
}

View File

@ -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='')