pyvckit #1
12
README.md
12
README.md
|
@ -31,23 +31,19 @@ The application's backend is responsible for issuing credentials upun user reque
|
|||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
3. Install the DIDKit wheel
|
||||
```
|
||||
wget https://gitea.pangea.org/trustchain-oc1-orchestral/ssikit_trustchain/raw/branch/master/didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
```
|
||||
4. Install the required packages:
|
||||
3. Install the required packages:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
5. Run migrations:
|
||||
4. Run migrations:
|
||||
```
|
||||
python manage.py migrate
|
||||
```
|
||||
6. Optionally you can install a minumum data set:
|
||||
5. Optionally you can install a minumum data set:
|
||||
```
|
||||
python manage.py initial_datas
|
||||
```
|
||||
7. Start the development server:
|
||||
6. Start the development server:
|
||||
```
|
||||
python manage.py runserver
|
||||
```
|
||||
|
|
1
cache_context.json
Normal file
1
cache_context.json
Normal file
File diff suppressed because one or more lines are too long
9
context/base.jsonld
Normal file
9
context/base.jsonld
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"@context": {
|
||||
"credentialSchema": "https://idhub.pangea.org/context/#credentialSchema",
|
||||
"value": "https://idhub.pangea.org/context/#value",
|
||||
"lang": "https://idhub.pangea.org/context/#lang",
|
||||
"description": "https://idhub.pangea.org/context/#description",
|
||||
"name": "https://idhub.pangea.org/context/#name"
|
||||
}
|
||||
}
|
22
context/course-credential.jsonld
Normal file
22
context/course-credential.jsonld
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"personalIdentifier": "https://idhub.pangea.org/context/#personalIdentifier",
|
||||
"issuedDate": "https://idhub.pangea.org/context/#issuedDate",
|
||||
"modeOfInstruction": "https://idhub.pangea.org/context/#modeOfInstruction",
|
||||
"courseDuration": "https://idhub.pangea.org/context/#courseDuration",
|
||||
"courseDays": "https://idhub.pangea.org/context/#courseDays",
|
||||
"courseName": "https://idhub.pangea.org/context/#courseName",
|
||||
"courseDescription": "https://idhub.pangea.org/context/#courseDescription",
|
||||
"gradingScheme": "https://idhub.pangea.org/context/#gradingScheme",
|
||||
"scoreAwarded": "https://idhub.pangea.org/context/#scoreAwarded",
|
||||
"qualificationAwarded": "https://idhub.pangea.org/context/#qualificationAwarded",
|
||||
"courseLevel": "https://idhub.pangea.org/context/#courseLevel",
|
||||
"courseFramework": "https://idhub.pangea.org/context/#courseFramework",
|
||||
"courseCredits": "https://idhub.pangea.org/context/#courseCredits",
|
||||
"dateOfAssessment": "https://idhub.pangea.org/context/#dateOfAssessment",
|
||||
"evidenceAssessment": "https://idhub.pangea.org/context/#evidenceAssessment",
|
||||
"email": "https://idhub.pangea.org/context/#email"
|
||||
}
|
||||
}
|
11
context/e-operator-claim.jsonld
Normal file
11
context/e-operator-claim.jsonld
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@context": {
|
||||
"legalName": "https://idhub.pangea.org/context/#legalName",
|
||||
"accreditedBy": "https://idhub.pangea.org/context/#accreditedBy",
|
||||
"operatorNumber": "https://idhub.pangea.org/context/#operatorNumber",
|
||||
"limitJurisdiction": "https://idhub.pangea.org/context/#limitJurisdiction",
|
||||
"accreditedFor": "https://idhub.pangea.org/context/#accreditedFor",
|
||||
"role": "https://idhub.pangea.org/context/#role",
|
||||
"email": "https://idhub.pangea.org/context/#email"
|
||||
}
|
||||
}
|
22
context/federation-membership.jsonld
Normal file
22
context/federation-membership.jsonld
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": {
|
||||
"federation": "https://idhub.pangea.org/context/#federation",
|
||||
"legalName": "https://idhub.pangea.org/context/#legalName",
|
||||
"shortName": "https://idhub.pangea.org/context/#shortName",
|
||||
"registrationIdentifier": "https://idhub.pangea.org/context/#registrationIdentifier",
|
||||
"publicRegistry": "https://idhub.pangea.org/context/#publicRegistry",
|
||||
"streetAddress": "https://idhub.pangea.org/context/#streetAddress",
|
||||
"postCode": "https://idhub.pangea.org/context/#postCode",
|
||||
"city": "https://idhub.pangea.org/context/#city",
|
||||
"taxReference": "https://idhub.pangea.org/context/#taxReference",
|
||||
"membershipType": "https://idhub.pangea.org/context/#membershipType",
|
||||
"membershipStatus": "https://idhub.pangea.org/context/#membershipStatus",
|
||||
"membershipId": "https://idhub.pangea.org/context/#membershipId",
|
||||
"membershipSince": "https://idhub.pangea.org/context/#membershipSince",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"phone": "https://idhub.pangea.org/context/#phone",
|
||||
"website": "https://idhub.pangea.org/context/#website",
|
||||
"evidence": "https://idhub.pangea.org/context/#evidence",
|
||||
"certificationDate": "https://idhub.pangea.org/context/#certificationDate"
|
||||
}
|
||||
}
|
17
context/financial-vulnerability.jsonld
Normal file
17
context/financial-vulnerability.jsonld
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"phoneNumber": "https://idhub.pangea.org/context/#phoneNumber",
|
||||
"identityDocType": "https://idhub.pangea.org/context/#identityDocType",
|
||||
"identityNumber": "https://idhub.pangea.org/context/#identityNumber",
|
||||
"streetAddress": "https://idhub.pangea.org/context/#streetAddress",
|
||||
"socialWorkerName": "https://idhub.pangea.org/context/#socialWorkerName",
|
||||
"socialWorkerSurname": "https://idhub.pangea.org/context/#socialWorkerSurname",
|
||||
"financialVulnerabilityScore": "https://idhub.pangea.org/context/#financialVulnerabilityScore",
|
||||
"amountCoveredByOtherAids": "https://idhub.pangea.org/context/#amountCoveredByOtherAids",
|
||||
"connectivityOptionList": "https://idhub.pangea.org/context/#connectivityOptionList",
|
||||
"assessmentDate": "https://idhub.pangea.org/context/#assessmentDate"
|
||||
}
|
||||
}
|
15
context/membership-card.jsonld
Normal file
15
context/membership-card.jsonld
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"organisation": "https://idhub.pangea.org/context/#organisation",
|
||||
"membershipType": "https://idhub.pangea.org/context/#membershipType",
|
||||
"membershipId": "https://idhub.pangea.org/context/#membershipId",
|
||||
"affiliatedSince": "https://idhub.pangea.org/context/#iaffiliatedSince",
|
||||
"affiliatedUntil": "https://idhub.pangea.org/context/#affiliatedUntil",
|
||||
"typeOfPerson": "https://idhub.pangea.org/context/#typeOfPerson",
|
||||
"identityDocType": "https://idhub.pangea.org/context/#identityDocType",
|
||||
"identityNumber": "https://idhub.pangea.org/context/#identityNumber"
|
||||
}
|
||||
}
|
113
idhub/models.py
113
idhub/models.py
|
@ -6,16 +6,17 @@ import datetime
|
|||
from collections import OrderedDict
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
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,
|
||||
webdid_from_controller_key,
|
||||
verify_credential,
|
||||
from pyvckit.did import (
|
||||
generate_keys,
|
||||
generate_did,
|
||||
gen_did_document,
|
||||
)
|
||||
from pyvckit.sign import sign
|
||||
from pyvckit.verify import verify_vc
|
||||
|
||||
from oidc4vp.models import Organization
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
@ -95,7 +96,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
# Is required?
|
||||
@classmethod
|
||||
def set_EV_DATA_UPDATE_REQUESTED_BY_USER(cls, user):
|
||||
|
@ -106,7 +107,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_DATA_UPDATE_REQUESTED_BY_USER,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
# Is required?
|
||||
@classmethod
|
||||
def set_EV_DATA_UPDATE_REQUESTED(cls, user):
|
||||
|
@ -117,7 +118,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_USR_UPDATED_BY_ADMIN(cls, user):
|
||||
msg = "The admin has updated the following user 's information: "
|
||||
|
@ -144,7 +145,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_USR_DELETED_BY_ADMIN(cls, user):
|
||||
msg = _("The admin has deleted the user: username: {username}").format(
|
||||
|
@ -154,7 +155,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_USR_DELETED_BY_ADMIN,
|
||||
message=msg
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_DID_CREATED_BY_USER(cls, did):
|
||||
msg = _("New DID with DID-ID: '{did}' created by user '{username}'").format(
|
||||
|
@ -165,7 +166,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_DID_CREATED_BY_USER,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_DID_CREATED(cls, did):
|
||||
msg = _("New DID with label: '{label}' and DID-ID: '{did}' was created'").format(
|
||||
|
@ -177,10 +178,10 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=did.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_DID_DELETED(cls, did):
|
||||
msg = _("The DID with label '{label}' and DID-ID: '{did}' was deleted from your wallet").format(
|
||||
msg = _("The DID with label '{label}' and DID-ID: '{did}' was deleted from your wallet").format(
|
||||
label=did.label,
|
||||
did=did.did
|
||||
)
|
||||
|
@ -189,7 +190,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=did.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_DELETED_BY_ADMIN(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was deleted").format(
|
||||
|
@ -200,7 +201,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_CREDENTIAL_DELETED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_DELETED(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was deleted from your wallet").format(
|
||||
|
@ -212,7 +213,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=cred.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_ISSUED_FOR_USER(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was issued for user {username}").format(
|
||||
|
@ -224,7 +225,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_CREDENTIAL_ISSUED_FOR_USER,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_ISSUED(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was issued and stored in your wallet").format(
|
||||
|
@ -236,7 +237,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=cred.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_PRESENTED_BY_USER(cls, cred, verifier):
|
||||
msg = "The credential of type '{type}' and ID: '{id}' "
|
||||
|
@ -251,7 +252,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_CREDENTIAL_PRESENTED_BY_USER,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_PRESENTED(cls, cred, verifier):
|
||||
msg = "The credential of type '{type}' and ID: '{id}' "
|
||||
|
@ -266,7 +267,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=cred.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_ENABLED(cls, cred):
|
||||
msg = _("The credential of type '{type}' was enabled for user {username}").format(
|
||||
|
@ -277,7 +278,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_CREDENTIAL_ENABLED,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_CAN_BE_REQUESTED(cls, cred):
|
||||
msg = _("You can request the '{type}' credential").format(
|
||||
|
@ -288,7 +289,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=cred.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_REVOKED_BY_ADMIN(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was revoked for ").format(
|
||||
|
@ -299,7 +300,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_CREDENTIAL_REVOKED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_CREDENTIAL_REVOKED(cls, cred):
|
||||
msg = _("The credential of type '{type}' and ID: '{id}' was revoked by admin").format(
|
||||
|
@ -311,7 +312,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=cred.user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_ROLE_CREATED_BY_ADMIN(cls):
|
||||
msg = _('A new role was created by admin')
|
||||
|
@ -319,7 +320,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_ROLE_CREATED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_ROLE_MODIFIED_BY_ADMIN(cls):
|
||||
msg = _('The role was modified by admin')
|
||||
|
@ -327,7 +328,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_ROLE_MODIFIED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_ROLE_DELETED_BY_ADMIN(cls):
|
||||
msg = _('The role was removed by admin')
|
||||
|
@ -335,7 +336,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_ROLE_DELETED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_SERVICE_CREATED_BY_ADMIN(cls):
|
||||
msg = _('A new service was created by admin')
|
||||
|
@ -343,7 +344,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_SERVICE_CREATED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_SERVICE_MODIFIED_BY_ADMIN(cls):
|
||||
msg = _('The service was modified by admin')
|
||||
|
@ -351,7 +352,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_SERVICE_MODIFIED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_SERVICE_DELETED_BY_ADMIN(cls):
|
||||
msg = _('The service was removed by admin')
|
||||
|
@ -359,7 +360,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_SERVICE_DELETED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_ORG_DID_CREATED_BY_ADMIN(cls, did):
|
||||
msg = _("New Organisational DID with label: '{label}' and DID-ID: '{did}' was created").format(
|
||||
|
@ -370,7 +371,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_ORG_DID_CREATED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_ORG_DID_DELETED_BY_ADMIN(cls, did):
|
||||
msg = _("Organisational DID with label: '{label}' and DID-ID: '{did}' was removed").format(
|
||||
|
@ -381,7 +382,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_ORG_DID_DELETED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_USR_DEACTIVATED_BY_ADMIN(cls, user):
|
||||
msg = "The user '{username}' was temporarily deactivated: "
|
||||
|
@ -395,7 +396,7 @@ class Event(models.Model):
|
|||
type=cls.Types.EV_USR_DEACTIVATED_BY_ADMIN,
|
||||
message=msg,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_USR_ACTIVATED_BY_ADMIN(cls, user):
|
||||
msg = "The user '{username}' was activated: "
|
||||
|
@ -417,7 +418,7 @@ class Event(models.Model):
|
|||
message=msg,
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_EV_USR_SEND_CREDENTIAL(cls, msg):
|
||||
cls.objects.create(
|
||||
|
@ -467,17 +468,24 @@ class DID(models.Model):
|
|||
user.set_encrypted_sensitive_data()
|
||||
user.save()
|
||||
self.key_material = user.encrypt_data(value)
|
||||
|
||||
|
||||
def set_did(self):
|
||||
new_key_material = generate_did_controller_key()
|
||||
new_key_material = generate_keys()
|
||||
self.set_key_material(new_key_material)
|
||||
|
||||
if self.type == self.Types.KEY:
|
||||
self.did = keydid_from_controller_key(new_key_material)
|
||||
self.did = generate_did(new_key_material)
|
||||
elif self.type == self.Types.WEB:
|
||||
didurl, document = webdid_from_controller_key(new_key_material, settings.DOMAIN)
|
||||
self.did = didurl
|
||||
self.didweb_document = document
|
||||
url = "https://{}".format(settings.DOMAIN)
|
||||
path = reverse("idhub:serve_did", args=["a"])
|
||||
|
||||
if path:
|
||||
path = path.split("/a/did.json")[0]
|
||||
url = "https://{}/{}".format(settings.DOMAIN, path)
|
||||
|
||||
self.did = generate_did(new_key_material, url)
|
||||
key = json.loads(new_key_material)
|
||||
url, self.didweb_document = gen_did_document(self.did, key)
|
||||
|
||||
def get_key(self):
|
||||
return json.loads(self.key_material)
|
||||
|
@ -681,15 +689,18 @@ class VerificableCredential(models.Model):
|
|||
|
||||
# hash of credential without sign
|
||||
self.hash = hashlib.sha3_256(self.render(domain).encode()).hexdigest()
|
||||
data = sign_credential(
|
||||
self.render(domain),
|
||||
self.issuer_did.get_key_material()
|
||||
)
|
||||
valid, reason = verify_credential(data)
|
||||
|
||||
key = self.issuer_did.get_key_material()
|
||||
credential = self.render(domain)
|
||||
|
||||
vc = sign(credential, key, self.issuer_did.did)
|
||||
vc_str = json.dumps(vc)
|
||||
valid = verify_vc(vc_str)
|
||||
|
||||
if not valid:
|
||||
return
|
||||
|
||||
self.data = self.user.encrypt_data(data)
|
||||
self.data = self.user.encrypt_data(vc_str)
|
||||
|
||||
self.status = self.Status.ISSUED
|
||||
|
||||
|
@ -761,7 +772,7 @@ class VerificableCredential(models.Model):
|
|||
tmpl = get_template(template_name)
|
||||
d = json.loads(tmpl.render({}))
|
||||
self.type = d.get('type')[-1]
|
||||
|
||||
|
||||
|
||||
def filter_dict(self, dic):
|
||||
new_dict = OrderedDict()
|
||||
|
@ -788,7 +799,7 @@ class File_datas(models.Model):
|
|||
|
||||
class Membership(models.Model):
|
||||
"""
|
||||
This model represent the relation of this user with the ecosystem.
|
||||
This model represent the relation of this user with the ecosystem.
|
||||
"""
|
||||
class Types(models.IntegerChoices):
|
||||
BENEFICIARY = 1, _('Beneficiary')
|
||||
|
@ -838,7 +849,7 @@ class Service(models.Model):
|
|||
if self.rol.exists():
|
||||
return ", ".join([x.name for x in self.rol.order_by("name")])
|
||||
return _("None")
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "{} -> {}".format(self.domain, self.get_roles())
|
||||
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/course-credential/v1"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"CourseCredential"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/course-credential.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"CourseCredential"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
},
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"validFrom": "{{ issuance_date }}",
|
||||
"validUntil": "{{ validUntil }}",
|
||||
"name": [
|
||||
{
|
||||
"value": "NGO Course Credential for participants",
|
||||
"lang": "en"
|
||||
},
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"validFrom": "{{ issuance_date }}",
|
||||
"validUntil": "{{ validUntil }}",
|
||||
"name": [
|
||||
{
|
||||
"value": "NGO Course Credential for participants",
|
||||
"lang": "en"
|
||||
},
|
||||
{
|
||||
"value": "Credencial per participants d'un curs impartit per una ONG",
|
||||
"lang": "ca_ES"
|
||||
},
|
||||
{
|
||||
"value": "Credencial para participantes de un curso impartido por una ONG",
|
||||
"lang": "es"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"value": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat",
|
||||
"lang": "en"
|
||||
}
|
||||
],
|
||||
"credentialSubject": {
|
||||
"id": "{{ subject_did }}",
|
||||
"firstName": "{{ firstName }}",
|
||||
"lastName": "{{ lastName }}",
|
||||
"email": "{{ email }}",
|
||||
"personalIdentifier": "{{ personalIdentifier }}",
|
||||
"issuedDate": "{{ issuedDate }}",
|
||||
"modeOfInstruction": "{{ modeOfInstruction }}",
|
||||
"courseDuration": "{{ courseDuration }}",
|
||||
"courseDays": "{{ courseDays }}",
|
||||
"courseName": "{{ courseName }}",
|
||||
"courseDescription": "{{ courseDescription }}",
|
||||
"gradingScheme": "{{ gradingScheme }}",
|
||||
"scoreAwarded": "{{ scoreAwarded }}",
|
||||
"qualificationAwarded": "{{ qualificationAwarded }}",
|
||||
"courseLevel": "{{ courseLevel }}",
|
||||
"courseFramework": "{{ courseFramework }}",
|
||||
"courseCredits": "{{ courseCredits }}",
|
||||
"dateOfAssessment": "{{ dateOfAssessment }}",
|
||||
"evidenceAssessment": "{{ evidenceAssessment }}"
|
||||
{
|
||||
"value": "Credencial per participants d'un curs impartit per una ONG",
|
||||
"lang": "ca_ES"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
{
|
||||
"value": "Credencial para participantes de un curso impartido por una ONG",
|
||||
"lang": "es"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"value": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat",
|
||||
"lang": "en"
|
||||
}
|
||||
],
|
||||
"credentialSubject": {
|
||||
"id": "{{ subject_did }}",
|
||||
"firstName": "{{ firstName }}",
|
||||
"lastName": "{{ lastName }}",
|
||||
"email": "{{ email }}",
|
||||
"personalIdentifier": "{{ personalIdentifier }}",
|
||||
"issuedDate": "{{ issuedDate }}",
|
||||
"modeOfInstruction": "{{ modeOfInstruction }}",
|
||||
"courseDuration": "{{ courseDuration }}",
|
||||
"courseDays": "{{ courseDays }}",
|
||||
"courseName": "{{ courseName }}",
|
||||
"courseDescription": "{{ courseDescription }}",
|
||||
"gradingScheme": "{{ gradingScheme }}",
|
||||
"scoreAwarded": "{{ scoreAwarded }}",
|
||||
"qualificationAwarded": "{{ qualificationAwarded }}",
|
||||
"courseLevel": "{{ courseLevel }}",
|
||||
"courseFramework": "{{ courseFramework }}",
|
||||
"courseCredits": "{{ courseCredits }}",
|
||||
"dateOfAssessment": "{{ dateOfAssessment }}",
|
||||
"evidenceAssessment": "{{ evidenceAssessment }}"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/e-operator-claim/v1"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"EOperatorClaim"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/e-operator-claim.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"EOperatorClaim"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
},
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"validFrom": "{{ issuance_date }}",
|
||||
"validUntil": "{{ validUntil }}",
|
||||
"name": [
|
||||
{
|
||||
"value": "Product and waste electronics operator claim",
|
||||
"lang": "en"
|
||||
},
|
||||
"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 }}",
|
||||
"accreditedBy": "{{ accreditedBy }}",
|
||||
"operatorNumber": "{{ operatorNumber }}",
|
||||
"limitJurisdiction": "{{ limitJurisdiction }}",
|
||||
"accreditedFor": "{{ accreditedFor }}",
|
||||
"role": "{{ role }}",
|
||||
"email": "{{ email }}"
|
||||
{
|
||||
"value": "Declaració d'operador de productes i residus electrònics",
|
||||
"lang": "ca_ES"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
{
|
||||
"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 }}",
|
||||
"accreditedBy": "{{ accreditedBy }}",
|
||||
"operatorNumber": "{{ operatorNumber }}",
|
||||
"limitJurisdiction": "{{ limitJurisdiction }}",
|
||||
"accreditedFor": "{{ accreditedFor }}",
|
||||
"role": "{{ role }}",
|
||||
"email": "{{ email }}"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/federation-membership/v1"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"FederationMembership"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/federation-membership.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
"VerifiableAttestation",
|
||||
"FederationMembership"
|
||||
],
|
||||
"issuer": {
|
||||
"id": "{{ issuer_did }}",
|
||||
"name": "{{ organisation }}"
|
||||
},
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"validFrom": "{{ issuance_date }}",
|
||||
"validUntil": "{{ validUntil }}",
|
||||
"name": [
|
||||
{
|
||||
"value": "NGO federation membership attestation credential",
|
||||
"lang": "en"
|
||||
},
|
||||
"issuanceDate": "{{ issuance_date }}",
|
||||
"validFrom": "{{ issuance_date }}",
|
||||
"validUntil": "{{ validUntil }}",
|
||||
"name": [
|
||||
{
|
||||
"value": "NGO federation membership attestation credential",
|
||||
"lang": "en"
|
||||
},
|
||||
{
|
||||
"value": "Credencial d'atestat de pertinença a federació d'ONG",
|
||||
"lang": "ca_ES"
|
||||
},
|
||||
{
|
||||
"value": "Credencial de atestado de membresía de Federación de ONG",
|
||||
"lang": "es"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"value": "Credential for NGOs that are members of a NGO federation",
|
||||
"lang": "en"
|
||||
},
|
||||
{
|
||||
"value": "Credencial para ONG que son miembros de una federación de ONG",
|
||||
"lang": "es"
|
||||
},
|
||||
{
|
||||
"value": "Credencial per a les ONG que són membres d'una federació d'ONG",
|
||||
"lang": "ca_ES"
|
||||
}
|
||||
],
|
||||
"credentialSubject": {
|
||||
"id": "{{ subject_did }}",
|
||||
"federation": "{{ federation }}",
|
||||
"legalName": "{{ legalName }}",
|
||||
"shortName": "{{ shortName }}",
|
||||
"registrationIdentifier": "{{ registrationIdentifier }}",
|
||||
"publicRegistry": "{{ publicRegistry }}",
|
||||
"streetAddress": "{{ streetAddress }}",
|
||||
"postCode": "{{ postCode }}",
|
||||
"city": "{{ city }}",
|
||||
"taxReference": "{{ taxReference }}",
|
||||
"membershipType": "{{ membershipType }}",
|
||||
"membershipStatus": "{{ membershipStatus }}",
|
||||
"membershipId": "{{ membershipId }}",
|
||||
"membershipSince": "{{ membershipSince }}",
|
||||
"email": "{{ email }}",
|
||||
"phone": "{{ phone }}",
|
||||
"website": "{{ website }}",
|
||||
"evidence": "{{ evidence }}",
|
||||
"certificationDate": "{{ certificationDate }}"
|
||||
{
|
||||
"value": "Credencial d'atestat de pertinença a federació d'ONG",
|
||||
"lang": "ca_ES"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
{
|
||||
"value": "Credencial de atestado de membresía de Federación de ONG",
|
||||
"lang": "es"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"value": "Credential for NGOs that are members of a NGO federation",
|
||||
"lang": "en"
|
||||
},
|
||||
{
|
||||
"value": "Credencial para ONG que son miembros de una federación de ONG",
|
||||
"lang": "es"
|
||||
},
|
||||
{
|
||||
"value": "Credencial per a les ONG que són membres d'una federació d'ONG",
|
||||
"lang": "ca_ES"
|
||||
}
|
||||
],
|
||||
"credentialSubject": {
|
||||
"id": "{{ subject_did }}",
|
||||
"federation": "{{ federation }}",
|
||||
"legalName": "{{ legalName }}",
|
||||
"shortName": "{{ shortName }}",
|
||||
"registrationIdentifier": "{{ registrationIdentifier }}",
|
||||
"publicRegistry": "{{ publicRegistry }}",
|
||||
"streetAddress": "{{ streetAddress }}",
|
||||
"postCode": "{{ postCode }}",
|
||||
"city": "{{ city }}",
|
||||
"taxReference": "{{ taxReference }}",
|
||||
"membershipType": "{{ membershipType }}",
|
||||
"membershipStatus": "{{ membershipStatus }}",
|
||||
"membershipId": "{{ membershipId }}",
|
||||
"membershipSince": "{{ membershipSince }}",
|
||||
"email": "{{ email }}",
|
||||
"phone": "{{ phone }}",
|
||||
"website": "{{ website }}",
|
||||
"evidence": "{{ evidence }}",
|
||||
"certificationDate": "{{ certificationDate }}"
|
||||
},
|
||||
"credentialStatus": {
|
||||
"id": "{{ credential_status_id}}",
|
||||
"type": "RevocationBitmap2022",
|
||||
"revocationBitmapIndex": "{{ id_credential }}"
|
||||
},
|
||||
"credentialSchema": {
|
||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/financial-vulnerability/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/financial-vulnerability.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/membership-card/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/membership-card.jsonld"
|
||||
],
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
|
|
|
@ -21,7 +21,7 @@ from .views import (
|
|||
LoginView,
|
||||
PasswordResetView,
|
||||
PasswordResetConfirmView,
|
||||
serve_did,
|
||||
ServeDidView,
|
||||
DobleFactorSendView,
|
||||
)
|
||||
from .admin import views as views_admin
|
||||
|
@ -183,7 +183,7 @@ urlpatterns = [
|
|||
name='admin_2fauth'),
|
||||
path('admin/auth/2f/', DobleFactorSendView.as_view(), name='confirm_send_2f'),
|
||||
|
||||
path('did-registry/<str:did_id>/did.json', serve_did)
|
||||
path('did-registry/<str:did_id>/did.json', ServeDidView, name="serve_did")
|
||||
|
||||
# path('verification_portal/verify/', views_verification_portal.verify,
|
||||
# name="verification_portal_verify")
|
||||
|
|
|
@ -90,7 +90,7 @@ class PasswordResetView(auth_views.PasswordResetView):
|
|||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
def serve_did(request, did_id):
|
||||
def ServeDidView(request, did_id):
|
||||
domain = settings.DOMAIN
|
||||
id_did = f'did:web:{domain}:did-registry:{did_id}'
|
||||
did = get_object_or_404(DID, did=id_did)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import json
|
||||
import uuid
|
||||
|
||||
from django import forms
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from utils.idhub_ssikit import create_verifiable_presentation
|
||||
from pyvckit.sign import sign
|
||||
from idhub.models import VerificableCredential
|
||||
|
||||
|
||||
|
@ -72,13 +73,19 @@ class AuthorizeForm(forms.Form):
|
|||
|
||||
def get_verificable_presentation(self):
|
||||
did = self.subject_did
|
||||
vc_list = [json.loads(x) for x in self.list_credentials]
|
||||
vp_template = get_template('credentials/verifiable_presentation.json')
|
||||
vc_list = json.dumps([json.loads(x) for x in self.list_credentials])
|
||||
|
||||
context = {
|
||||
"holder_did": did.did,
|
||||
"verifiable_credential_list": vc_list
|
||||
"id": str(uuid.uuid4())
|
||||
}
|
||||
unsigned_vp = vp_template.render(context)
|
||||
vp = json.loads(unsigned_vp)
|
||||
vp["verifiableCredential"] = vc_list
|
||||
vp_str = json.dumps(vp)
|
||||
|
||||
key_material = did.get_key_material()
|
||||
self.vp = create_verifiable_presentation(key_material, unsigned_vp)
|
||||
vp = sign(vp_str, key_material, did.did)
|
||||
self.vp = json.dumps(vp)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.http import QueryDict
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from idhub_auth.models import User
|
||||
from django.db import models
|
||||
from utils.idhub_ssikit import verify_presentation
|
||||
from pyvckit.verify import verify_vp
|
||||
|
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
@ -22,7 +22,7 @@ def gen_salt(length: int) -> str:
|
|||
"""Generate a random string of SALT_CHARS with specified ``length``."""
|
||||
if length <= 0:
|
||||
raise ValueError("Salt length must be positive")
|
||||
|
||||
|
||||
return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
|
||||
|
||||
|
||||
|
@ -48,7 +48,7 @@ class Organization(models.Model):
|
|||
For use the packages requests we need use my_client_id
|
||||
For use in the get or post method of a View, then we need use client_id
|
||||
and secret_id.
|
||||
main is a field which indicates the organization of this idhub
|
||||
main is a field which indicates the organization of this idhub
|
||||
"""
|
||||
name = models.CharField(max_length=250)
|
||||
domain = models.CharField(max_length=250, null=True, default=None)
|
||||
|
@ -130,7 +130,7 @@ class Organization(models.Model):
|
|||
sb = 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):
|
||||
|
@ -173,7 +173,7 @@ class Organization(models.Model):
|
|||
sb = secret.SecretBox(sb_key)
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
|
||||
|
||||
encrypted_data = base64.b64encode(sb.encrypt(data)).decode('utf-8')
|
||||
self.encrypted_sensitive_data = encrypted_data
|
||||
|
||||
|
@ -261,7 +261,7 @@ class OAuth2VPToken(models.Model):
|
|||
def __init__(self, *args, **kwargs):
|
||||
code = kwargs.pop("code", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
self.authorization = Authorization.objects.filter(code=code).first()
|
||||
|
||||
@property
|
||||
|
@ -271,7 +271,7 @@ class OAuth2VPToken(models.Model):
|
|||
return self.authorization.code
|
||||
|
||||
def verifing(self):
|
||||
self.result_verify = verify_presentation(self.vp_token)
|
||||
self.result_verify = verify_vp(self.vp_token)
|
||||
|
||||
def get_result_verify(self):
|
||||
if not self.result_verify:
|
||||
|
@ -284,11 +284,10 @@ class OAuth2VPToken(models.Model):
|
|||
"redirect_uri": "",
|
||||
"response": "",
|
||||
}
|
||||
verification = json.loads(self.result_verify)
|
||||
if verification.get('errors') or verification.get('warnings'):
|
||||
if not self.result_verify:
|
||||
response["verify"] = "Error, {}".format(_("Failed verification"))
|
||||
return response
|
||||
|
||||
|
||||
response["verify"] = "Ok, {}".format(_("Correct verification"))
|
||||
url = self.get_redirect_url()
|
||||
if url:
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1"
|
||||
],
|
||||
"id": "http://example.org/presentations/3731",
|
||||
"id": "{{ id }}",
|
||||
"type": [
|
||||
"VerifiablePresentation"
|
||||
],
|
||||
"holder": "{{ holder_did }}",
|
||||
"verifiableCredential": {{ verifiable_credential_list|safe }}
|
||||
"verifiableCredential": ""
|
||||
}
|
||||
|
|
|
@ -174,11 +174,7 @@ class VerifyView(View):
|
|||
"""
|
||||
Send a email when a user is activated.
|
||||
"""
|
||||
verification = self.vp_token.get_result_verify()
|
||||
if not verification:
|
||||
return
|
||||
|
||||
if verification.get('errors') or verification.get('warnings'):
|
||||
if not self.vp_token.result_verify:
|
||||
return
|
||||
|
||||
email = self.get_email(user)
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from utils.idhub_ssikit import create_verifiable_presentation
|
||||
from oidc4vp.models import Organization, Authorization
|
||||
from promotion.models import Promotion
|
||||
|
||||
|
@ -25,7 +17,7 @@ class WalletForm(forms.Form):
|
|||
self.fields['organization'].choices = [
|
||||
(x.id, x.name) for x in Organization.objects.exclude(
|
||||
domain=settings.DOMAIN
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
def save(self, commit=True):
|
||||
|
@ -51,10 +43,10 @@ class WalletForm(forms.Form):
|
|||
self.promotion.save()
|
||||
|
||||
return self.authorization.authorize()
|
||||
|
||||
return
|
||||
|
||||
|
||||
return
|
||||
|
||||
|
||||
class ContractForm(forms.Form):
|
||||
nif = forms.CharField()
|
||||
name = forms.CharField()
|
||||
|
@ -66,4 +58,3 @@ class ContractForm(forms.Form):
|
|||
birthday = forms.CharField()
|
||||
gen = forms.CharField()
|
||||
lang = forms.CharField()
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ weasyprint==60.2
|
|||
ujson==5.9.0
|
||||
openpyxl==3.1.2
|
||||
jsonpath_ng==1.6.1
|
||||
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
pyroaring==0.4.5
|
||||
coverage==7.4.3
|
||||
gunicorn==21.2.0
|
||||
pyvckit
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
# Helper routines to manage DIDs/VC/VPs
|
||||
|
||||
This module is a wrapper around the functions exported by SpruceID's `DIDKit` framework.
|
||||
|
||||
## DID generation and storage
|
||||
|
||||
For now DIDs are of the kind `did:key`, with planned support for `did:web` in the near future.
|
||||
|
||||
Creation of a DID involves two steps:
|
||||
* Generate a unique DID controller key
|
||||
* Derive a `did:key` type from the key
|
||||
|
||||
Both must be stored in the IdHub database and linked to a `User` for later retrieval.
|
||||
|
||||
```python
|
||||
# Use case: generate and link a new DID for an existing user
|
||||
user = request.user # ...
|
||||
|
||||
controller_key = idhub_ssikit.generate_did_controller_key()
|
||||
did_string = idhub_ssikit.keydid_from_controller_key(controller_key)
|
||||
|
||||
|
||||
did = idhub.models.DID(
|
||||
did = did_string,
|
||||
user = user
|
||||
)
|
||||
did_controller_key = idhub.models.DIDControllerKey(
|
||||
key_material = controller_key,
|
||||
owner_did = did
|
||||
)
|
||||
|
||||
did.save()
|
||||
did_controller_key.save()
|
||||
```
|
||||
|
||||
## Verifiable Credential issuance
|
||||
|
||||
Verifiable Credential templates are stored as Jinja2 (TBD) templates in `/schemas` folder. Please examine each template to see what data must be passed to it in order to render.
|
||||
|
||||
The data passed to the template must at a minimum include:
|
||||
* issuer_did
|
||||
* subject_did
|
||||
* vc_id
|
||||
|
||||
For example, in order to render `/schemas/member-credential.json`:
|
||||
|
||||
```python
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
import idhub_ssikit
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader("vc_templates"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
unsigned_vc_template = env.get_template("member-credential.json")
|
||||
|
||||
issuer_user = request.user
|
||||
issuer_did = user.dids[0] # TODO: Django ORM pseudocode
|
||||
issuer_did_controller_key = did.keys[0] # TODO: Django ORM pseudocode
|
||||
|
||||
data = {
|
||||
"vc_id": "http://pangea.org/credentials/3731",
|
||||
"issuer_did": issuer_did,
|
||||
"subject_did": "did:web:[...]",
|
||||
"issuance_date": "2020-08-19T21:41:50Z",
|
||||
"subject_is_member_of": "Pangea"
|
||||
}
|
||||
signed_credential = idhub_ssikit.render_and_sign_credential(
|
||||
unsigned_vc_template,
|
||||
issuer_did_controller_key,
|
||||
data
|
||||
)
|
||||
```
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"issuerApiUrl": "http://localhost:8080/issuer-api/default",
|
||||
"issuerClientName": "PANGEA Issuer Portal",
|
||||
"issuerDid": null,
|
||||
"issuerUiUrl": "http://localhost:5000",
|
||||
"wallets": {
|
||||
"walt.id": {
|
||||
"description": "walt.id web wallet",
|
||||
"id": "walt.id",
|
||||
"presentPath": "api/siop/initiatePresentation",
|
||||
"receivePath": "api/siop/initiateIssuance",
|
||||
"url": "http://localhost:3000"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
import asyncio
|
||||
import base64
|
||||
import datetime
|
||||
import zlib
|
||||
from ast import literal_eval
|
||||
|
||||
import didkit
|
||||
import json
|
||||
import urllib
|
||||
import jinja2
|
||||
from django.template.backends.django import Template
|
||||
from django.template.loader import get_template
|
||||
from pyroaring import BitMap
|
||||
|
||||
from trustchain_idhub import settings
|
||||
|
||||
|
||||
def generate_did_controller_key():
|
||||
return didkit.generate_ed25519_key()
|
||||
|
||||
|
||||
def keydid_from_controller_key(key):
|
||||
return didkit.key_to_did("key", key)
|
||||
|
||||
|
||||
def resolve_did(keydid):
|
||||
async def inner():
|
||||
return await didkit.resolve_did(keydid, "{}")
|
||||
|
||||
return asyncio.run(inner())
|
||||
|
||||
|
||||
def webdid_from_controller_key(key, domain):
|
||||
"""
|
||||
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(resolve_did(keydid)) # Documento DID en terminos "key"
|
||||
# domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:]
|
||||
webdid_url = f"did:web:{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():
|
||||
# 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 unsigned credential with the provided key.
|
||||
The credential template must be rendered with all user data.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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():
|
||||
str_res = await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
|
||||
res = literal_eval(str_res)
|
||||
ok = res["warnings"] == [] and res["errors"] == []
|
||||
return ok, str_res
|
||||
|
||||
valid, reason = asyncio.run(inner())
|
||||
if not valid:
|
||||
return valid, reason
|
||||
# Credential passes basic signature verification. Now check it against its schema.
|
||||
# TODO: check agasint schema
|
||||
# pass
|
||||
# Credential verifies against its schema. Now check revocation status.
|
||||
vc = json.loads(vc)
|
||||
if "credentialStatus" in vc:
|
||||
revocation_index = int(vc["credentialStatus"]["revocationBitmapIndex"]) # NOTE: THIS FIELD SHOULD BE SERIALIZED AS AN INTEGER, BUT IOTA DOCUMENTAITON SERIALIZES IT AS A STRING. DEFENSIVE CAST ADDED JUST IN CASE.
|
||||
vc_issuer = vc["issuer"]["id"] # This is a DID
|
||||
if vc_issuer[:7] == "did:web": # Only DID:WEB can revoke
|
||||
issuer_did_document = json.loads(resolve_did(vc_issuer)) # TODO: implement a caching layer so we don't have to fetch the DID (and thus the revocation list) every time a VC is validated.
|
||||
issuer_revocation_list = issuer_did_document["service"][0]
|
||||
assert issuer_revocation_list["type"] == "RevocationBitmap2022"
|
||||
revocation_bitmap = BitMap.deserialize(
|
||||
zlib.decompress(
|
||||
base64.b64decode(
|
||||
issuer_revocation_list["serviceEndpoint"].rsplit(",")[1].encode('utf-8')
|
||||
)
|
||||
)
|
||||
)
|
||||
if revocation_index in revocation_bitmap:
|
||||
return False, "Credential has been revoked by the issuer"
|
||||
# Fallthrough means all is good.
|
||||
return True, "Credential passes all checks"
|
||||
|
||||
|
||||
def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str:
|
||||
async def inner():
|
||||
unsigned_vp = vp_template.render(data)
|
||||
signed_vp = await didkit.issue_presentation(
|
||||
unsigned_vp,
|
||||
'{"proofFormat": "ldp"}',
|
||||
jwk_holder
|
||||
)
|
||||
return signed_vp
|
||||
|
||||
data = {
|
||||
"holder_did": holder_did,
|
||||
"verifiable_credential_list": "[" + ",".join(vc_list) + "]"
|
||||
}
|
||||
|
||||
return asyncio.run(inner())
|
||||
|
||||
|
||||
def create_verifiable_presentation(jwk_holder: str, unsigned_vp: str) -> str:
|
||||
async def inner():
|
||||
signed_vp = await didkit.issue_presentation(
|
||||
unsigned_vp,
|
||||
'{"proofFormat": "ldp"}',
|
||||
jwk_holder
|
||||
)
|
||||
return signed_vp
|
||||
|
||||
return asyncio.run(inner())
|
||||
|
||||
|
||||
def verify_presentation(vp):
|
||||
"""
|
||||
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():
|
||||
proof_options = '{"proofFormat": "ldp"}'
|
||||
return await didkit.verify_presentation(vp, proof_options)
|
||||
|
||||
return asyncio.run(inner())
|
||||
|
Loading…
Reference in a new issue