Cambiado el renderizado de plantillas de textual a manipulación de estructura de datos en memoria

This commit is contained in:
Daniel Armengod 2024-01-03 16:53:06 +01:00
parent 451ee9d76a
commit dde81e23cc
21 changed files with 106 additions and 466 deletions

View file

@ -1,9 +1,12 @@
import asyncio
import datetime
from typing import Any
import didkit
import json
import jinja2
from jinja2 import Environment, FileSystemLoader, select_autoescape
from ast import literal_eval
def generate_did_controller_key():
@ -19,7 +22,7 @@ def generate_generic_vc_id():
return "https://pangea.org/credentials/42"
def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data: dict[str, str]):
def render_and_sign_credential(unsigned_vc: dict, jwk_issuer):
"""
Populates a VC template with data for issuance, and signs the result with the provided key.
@ -33,16 +36,15 @@ def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, 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,
json.dumps(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()
# if vc_data.get("issuance_date") is None:
# vc_data["issuance_date"] = datetime.datetime.now().replace(microsecond=0).isoformat()
return asyncio.run(inner())
@ -65,13 +67,24 @@ def sign_credential(unsigned_vc: str, jwk_issuer):
def verify_credential(vc):
"""
Returns a (bool, str) tuple indicating whether the credential is valid.
Checks performed:
* The credential is valid in signature and form, and
* The credential validates itself against its declared schema.
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.
If it is false, the VC is invalid and the second argument contains a string (which is a valid JSON object) with further information.
"""
async def inner():
return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
str_res = await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
res = literal_eval(str_res)
ok = res["warnings"] == [] and res["errors"] == []
return ok, str_res
(ok, res) = asyncio.run(inner())
if not ok:
# The credential doesn't pass signature checks, so early return
return ok, res
return ok, res
return asyncio.run(inner())
def issue_verifiable_presentation(vc_list: list[str], jwk_holder: str, holder_did: str) -> str:

117
main.py
View file

@ -1,70 +1,105 @@
import asyncio
from typing import Callable, Any
import didkit
import json
from jinja2 import Environment, FileSystemLoader, select_autoescape
import idhub_ssikit
from ast import literal_eval
import copy
def issue_vc_test(vc_name):
def deep_merge_dict_inplace(d1: dict, d2: dict):
"""
Implements d1 |= d2, but recursively.
Merges d1 and d2, giving preference to keys in d2.
Keys in d1 but not in d2 are left as-is.
"""
for key, val in d2.items():
if isinstance(d1.get(key, None), dict) and isinstance(val, dict):
deep_merge_dict_inplace(d1[key], val)
continue
d1[key] = val
def deep_merge_dict(d1: dict, d2: dict) -> dict:
"""
Implements d1 | d2, but recursively.
Merges d1 and d2, giving preference to keys in d2.
Keys in d1 but not in d2 are left as-is.
"""
d1 = copy.deepcopy(d1)
deep_merge_dict_inplace(d1, d2)
return d1
def deep_filter_dict(f: Callable[[Any], bool], d: dict):
"""
Implements builtin filter(), but recursively.
Applies f to all k,v pairs in d. If some v is a dict, recurse into v instead of applying f(v) directly.
"""
for key, val in d.items():
if isinstance(val, dict):
yield key, dict(deep_filter_dict(f, val))
elif f(val):
yield key, val
def test_all_vcs():
vcs = [
'membership-card',
'financial-vulnerability',
'course-credential',
'federation-membership',
]
for vc in vcs:
print(f"trying {vc}... ", end="")
try:
signed_cred = issue_vc_test_newstyle(vc)
ok, err = idhub_ssikit.verify_credential(signed_cred)
if ok:
print("OK")
else:
print("FAILED!", err)
open(f'/tmp/{vc}', mode='w').write(signed_cred)
except Exception as e:
print("FAILED! With exception:")
print(e)
def issue_vc_test_newstyle(vc_name):
jwk_issuer = didkit.generate_ed25519_key()
jwk_subject = didkit.generate_ed25519_key()
did_issuer = didkit.key_to_did("key", jwk_issuer)
did_subject = didkit.key_to_did("key", jwk_subject)
env = Environment(
loader=FileSystemLoader("vc_templates"),
autoescape=select_autoescape()
)
unsigned_vc_template = env.get_template(f"{vc_name}.json")
data_raw = open(f"vc_templates/{vc_name}--data.py").read()
data = eval(data_raw)
data["issuerDid"] = did_issuer
data["subjectDid"] = did_subject
vc_template = json.load(open(f'vc_templates/{vc_name}.json'))
data_base = json.load(open(f'vc_templates/base--data.json'))
data_base["issuer"]["id"] = did_issuer
data_base["credentialSubject"]["id"] = did_subject
data_specific = json.load(open(f'vc_templates/{vc_name}--data.json'))
data = deep_merge_dict(data_base, data_specific)
vc_rendered_unsigned = deep_merge_dict(vc_template, data)
signed_credential = idhub_ssikit.render_and_sign_credential(
unsigned_vc_template,
vc_rendered_unsigned,
jwk_issuer,
data
)
print(signed_credential)
return signed_credential
def issue_vc_test_2():
jwk_issuer = didkit.generate_ed25519_key()
jwk_subject = didkit.generate_ed25519_key()
did_issuer = didkit.key_to_did("key", jwk_issuer)
did_subject = didkit.key_to_did("key", jwk_subject)
env = Environment(
loader=FileSystemLoader("vc_templates"),
autoescape=select_autoescape()
)
unsigned_vc_template = env.get_template("membership-card.json")
data = {
"vc_id": "http://example.org/credentials/3731",
"issuer_did": did_issuer,
"subject_did": did_subject,
"issuance_date": "2020-08-19T21:41:50Z",
"validUntil": "2020-08-19T21:41:50Z",
"membershipType": "lareputa"
}
signed_credential = idhub_ssikit.render_and_sign_credential(
unsigned_vc_template,
jwk_issuer,
data
)
print(signed_credential)
def issue_vc_test_and_fail_verification(vc_name):
signed_credential = issue_vc_test_newstyle(vc_name)
verification_result = idhub_ssikit.verify_credential(signed_credential)
print(verification_result)
def replace(s, position, character):
return s[:position] + character + s[position+1:]
signed_credential = replace(signed_credential, 2843, "k")
signed_credential = replace(signed_credential, (len(signed_credential)//4)*3, ".")
verification_result = idhub_ssikit.verify_credential(signed_credential)
print(verification_result)
def issue_and_sign_vp_test():
"""
In this example execution two Verifiable Credentials associated with a single Holder are issued and then

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_examples/base--data.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_examples/course-credential--data.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_templates/course-credential.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_examples/federation-membership--data.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_templates/federation-membership.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_examples/financial-vulnerability--data.json

View file

@ -1,22 +0,0 @@
{
"vcId": "https://idhub.pangea.org/credentials/987654321",
"issuerDid": "did:example:5678",
"issuerName": "Fundació Pare Manel",
"issuanceDate": "2023-12-06T19:23:24Z",
"validUntil": "2024-12-06T19:23:24Z",
"subjectDid": "did:example:1234",
"firstName": "Joan",
"lastName": "Pera",
"email": "joan.pera@pangea.org",
"phoneNumber": "1234567890",
"identityDocType": "DNI",
"identityNumber": "12345678A",
"streetAddress": "Tallers 19 2-1 Barcelona",
"socialWorkerName": "Mireia",
"socialWorkerSurname": "Pujol",
"financialVulnerabilityScore": "5",
"amountCoveredByOtherAids": "20",
"connectivityOptionList": "fibre, mobile",
"assessmentDate": "2023-12-06"
}

View file

@ -1,68 +0,0 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://idhub.pangea.org/credentials/base/v1",
"https://idhub.pangea.org/credentials/financial-vulnerability/v1"
],
"id": "{{ vcId }}",
"type": [
"VerifiableCredential",
"VerifiableAttestation",
"FinancialVulnerabilityCredential"
],
"issuer": {
"id": "{{ issuerDid }}",
"name": "{{ issuerName }}"
},
"issuanceDate": "{{ issuanceDate }}",
"validFrom": "{{ issuanceDate }}",
"validUntil": "{{ validUntil }}",
"name": [
{
"value": "Financial Vulnerability Credential",
"lang": "en"
},
{
"value": "Credencial de Vulnerabilitat Financera",
"lang": "ca_ES"
},
{
"value": "Credencial de Vulnerabilidad Financiera",
"lang": "es"
}
],
"description": [
{
"value": "The Financial Vulnerability Credential is issued to individuals or families to prove their financial vulnerability based on various factors, with the objective of presenting it to a third party to receive benefits or services.",
"lang": "en"
},
{
"value": "La Credencial de Vulnerabilitat Financera és emesa a persones o famílies per acreditar la seva vulnerabilitat financera sobre la base de diversos factors, amb l'objectiu que la presentin a una tercera part per rebre beneficis o serveis.",
"lang": "ca_ES"
},
{
"value": "La Credencial de Vulnerabilidad Financiera es emitida a personas o familias para acreditar su vulnerabilidad financiera con base en diversos factores, con el objetivo de que la presenten a una tercera parte para recibir beneficios o servicios.",
"lang": "es"
}
],
"credentialSubject": {
"id": "{{ subjectDid }}",
"firstName": "{{ firstName }}",
"lastName": "{{ lastName }}",
"email": "{{ email }}",
"identityDocType": "{{ identityDocType }}",
"identityNumber": "{{ identityNumber }}",
"phoneNumber": "{{ phoneNumber }}",
"streetAddress": "{{ streetAddress }}",
"socialWorkerName": "{{ socialWorkerName }}",
"socialWorkerSurname": "{{ socialWorkerSurname }}",
"financialVulnerabilityScore": "{{ financialVulnerabilityScore }}",
"amountCoveredByOtherAids": "{{ amountCoveredByOtherAids }}",
"connectivityOptionList": "{{ connectivityOptionList }}",
"assessmentDate": "{{ assessmentDate }}"
},
"credentialSchema": {
"id": "https://idhub.pangea.org/vc_schemas/financial_vulnerability.json",
"type": "JsonSchema"
}
}

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_templates/financial-vulnerability.json

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_examples/membership-card--data.json

View file

@ -1,19 +0,0 @@
{
"vcId": "https://idhub.pangea.org/credentials/987654321",
"issuerDid": "did:example:5678",
"issuerName": "Pangea Internet Solidari",
"issuanceDate": "2023-12-06T19:23:24Z",
"validUntil": "2024-12-06T19:23:24Z",
"subjectDid": "did:example:1234",
"firstName": "Joan",
"lastName": "Pera",
"email": "joan.pera@pangea.org",
"membershipType": "individual",
"membershipId": "123456",
"affiliatedSince": "2023-01-01T00:00:00Z",
"affiliatedUntil": "2024-01-01T00:00:00Z",
"typeOfPerson": "natural",
"identityDocType": "DNI",
"identityNumber": "12345678A"
}

View file

@ -1,67 +0,0 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://idhub.pangea.org/credentials/base/v1",
"https://idhub.pangea.org/credentials/membership-card/v1"
],
"type": [
"VerifiableCredential",
"VerifiableAttestation",
"MembershipCard"
],
"id": "{{ vcId }}",
"issuer": {
"id": "{{ issuerDid }}",
"name": "{{ issuerName }}"
},
"issuanceDate": "{{ issuanceDate }}",
"issued": "{{ issuanceDate }}",
"validFrom": "{{ issuanceDate }}",
"validUntil": "{{ validUntil }}",
"name": [
{
"value": "Membership Card",
"lang": "en"
},
{
"value": "Carnet de soci/a",
"lang": "ca_ES"
},
{
"value": "Carnet de socio/a",
"lang": "es"
}
],
"description": [
{
"value": "The membership card specifies an individual's subscription or enrollment in specific services or benefits issued by an organization.",
"lang": "en"
},
{
"value": "El carnet de soci especifica la subscripció o la inscripció d'un individu en serveis o beneficis específics emesos per una organització.",
"lang": "ca_ES"
},
{
"value": "El carnet de socio especifica la suscripción o inscripción de un individuo en servicios o beneficios específicos emitidos por uns organización.",
"lang": "es"
}
],
"credentialSubject": {
"id": "{{ subjectDid }}",
"firstName": "{{ firstName }}",
"lastName": "{{ lastName }}",
"email": "{{ email }}",
"typeOfPerson": "{{ typeOfPerson }}",
"identityDocType": "{{ identityDocType }}",
"identityNumber": "{{ identityNumber }}",
"organisation": "Pangea",
"membershipType": "{{ membershipType }}",
"membershipId": "{{ membershipId }}",
"affiliatedSince": "{{ affiliatedSince }}",
"affiliatedUntil": "{{ affiliatedUntil }}"
},
"credentialSchema": {
"id": "https://idhub.pangea.org/vc_schemas/membership-card.json",
"type": "JsonSchema"
}
}

View file

@ -0,0 +1 @@
/home/daniel/schemas/vc_templates/membership-card.json

View file

@ -1,30 +0,0 @@
{
"@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",
"street_address": "https://schema.org/streetAddress",
"connectivity_option_list": "https://schema.org/connectivityOptionList",
"$ref": "https://schema.org/jsonSchemaRef"
}
],
"id": "{{ vc_id }}",
"type": ["VerifiableCredential", "HomeConnectivitySurveyCredential"],
"issuer": "{{ issuer_did }}",
"issuanceDate": "{{ issuance_date }}",
"credentialSubject": {
"id": "{{ subject_did }}",
"street_address": "{{ street_address }}",
"connectivity_option_list": "{{ connectivity_option_list }}",
"jsonSchema": {
"$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
}
}
}

View file

@ -1,32 +0,0 @@
{
"@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"
}
}
}

View file

@ -1,105 +0,0 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
{
"individual": "https://schema.org/Person",
"Member": "https://schema.org/Member",
"startDate": "https://schema.org/startDate",
"jsonSchema": "https://schema.org/jsonSchema",
"$ref": "https://schema.org/jsonSchemaRef",
"credentialSchema": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#credentialSchema",
"organisation": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#organisation",
"membershipType": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#membershipType",
"membershipId": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#membershipId",
"typeOfPerson": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#typeOfPerson",
"identityDocType": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#identityDocType",
"identityNumber": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#identityNumber",
"name": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#name",
"description": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#description",
"value": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#value",
"lang": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#lang",
"surnames": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#surnames",
"email": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#email",
"affiliatedSince": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#affiliatedSince",
"affiliatedUntil": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/contexts/vocab#affiliatedUntil",
"issued": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#issued",
"validFrom": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#validFrom",
"validUntil": "https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/Verifiable+Attestation#validUntil"
}
],
"type": [
"VerifiableCredential",
"VerifiableAttestation",
"MembershipCard"
],
"id": "{{ vc_id }}",
"issuer": {
"id": "{{ issuer_did }}",
"name": "Pangea",
"description": [
{
"value": "Pangea.org is a service provider leveraging open-source technologies to provide affordable and accessible solutions for social enterprises and solidarity organisations.",
"lang": "en"
},
{
"value": "Pangea.org és un proveïdor de serveis que aprofita les tecnologies de codi obert per oferir solucions assequibles i accessibles per a empreses socials i organitzacions solidàries.",
"lang": "ca_ES"
},
{
"value": "Pangea.org es un proveedor de servicios que aprovecha tecnologías de código abierto para proporcionar soluciones asequibles y accesibles para empresas sociales y organizaciones solidarias.",
"lang": "es"
}
]
},
"issuanceDate": "{{ issuance_date }}",
"issued": "{{ issuance_date }}",
"validFrom": "{{ issuance_date }}",
"validUntil": "{{ validUntil }}",
"name": [
{
"value": "Membership Card",
"lang": "en"
},
{
"value": "Carnet de soci/a",
"lang": "ca_ES"
},
{
"value": "Carnet de socio/a",
"lang": "es"
}
],
"description": [
{
"value": "The membership card specifies an individual's subscription or enrollment in specific services or benefits issued by an organization.",
"lang": "en"
},
{
"value": "El carnet de soci especifica la subscripció o la inscripció d'un individu en serveis o beneficis específics emesos per una organització.",
"lang": "ca_ES"
},
{
"value": "El carnet de socio especifica la suscripción o inscripción de un individuo en servicios o beneficios específicos emitidos por uns organización.",
"lang": "es"
}
],
"credentialSubject": {
"id": "{{ subject_did }}",
"organisation": "Pangea",
"membershipType": "{{ membershipType }}",
"membershipId": "{{ vc_id }}",
"affiliatedSince": "{{ affiliatedSince }}",
"affiliatedUntil": "{{ affiliatedUntil }}",
"typeOfPerson": "{{ typeOfPerson }}",
"identityDocType": "{{ identityDocType }}",
"identityNumber": "{{ identityNumber }}",
"name": "{{ first_name }}",
"surnames": "{{ last_name }}",
"email": "{{ email }}",
"jsonSchema": {
"id": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/membership-card-schema.json",
"type": "JsonSchema"
}
}
}

View file

@ -1,33 +0,0 @@
{
"@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",
"destination_country": "https://schema.org/destinationCountry",
"offboarding_date": "https://schema.org/offboardingDate",
"$ref": "https://schema.org/jsonSchemaRef"
}
],
"id": "{{ vc_id }}",
"type": ["VerifiableCredential", "MigrantRescueCredential"],
"issuer": "{{ issuer_did }}",
"issuanceDate": "{{ issuance_date }}",
"credentialSubject": {
"id": "{{ subject_did }}",
"name": "{{ name }}",
"country_of_origin": "{{ country_of_origin }}",
"rescue_date": "{{ rescue_date }}",
"destination_country": "{{ destination_country }}",
"offboarding_date": "{{ offboarding_date }}",
"jsonSchema": {
"$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
}
}
}

View file

@ -1,30 +0,0 @@
{
"@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",
"street_address": "https://schema.org/streetAddress",
"financial_vulnerability_score": "https://schema.org/financialVulnerabilityScore",
"$ref": "https://schema.org/jsonSchemaRef"
}
],
"id": "{{ vc_id }}",
"type": ["VerifiableCredential", "FinancialSituationCredential"],
"issuer": "{{ issuer_did }}",
"issuanceDate": "{{ issuance_date }}",
"credentialSubject": {
"id": "{{ subject_did }}",
"street_address": "{{ street_address }}",
"financial_vulnerability_score": "{{ financial_vulnerability_score }}",
"jsonSchema": {
"$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
}
}
}

View file

@ -1,11 +0,0 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"id": "http://example.org/presentations/3731",
"type": [
"VerifiablePresentation"
],
"holder": "{{ holder_did }}",
"verifiableCredential": {{ verifiable_credential_list }}
}