diff --git a/README.md b/README.md index 38056d7..353f7b4 100644 --- a/README.md +++ b/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 ``` diff --git a/cache_context.json b/cache_context.json new file mode 100644 index 0000000..035640e --- /dev/null +++ b/cache_context.json @@ -0,0 +1 @@ +{"https://www.w3.org/2018/credentials/v1": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://www.w3.org/2018/credentials/v1", "document": {"@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "VerifiableCredential": {"@id": "https://www.w3.org/2018/credentials#VerifiableCredential", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "cred": "https://www.w3.org/2018/credentials#", "sec": "https://w3id.org/security#", "xsd": "http://www.w3.org/2001/XMLSchema#", "credentialSchema": {"@id": "cred:credentialSchema", "@type": "@id", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "cred": "https://www.w3.org/2018/credentials#", "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018"}}, "credentialStatus": {"@id": "cred:credentialStatus", "@type": "@id"}, "credentialSubject": {"@id": "cred:credentialSubject", "@type": "@id"}, "evidence": {"@id": "cred:evidence", "@type": "@id"}, "expirationDate": {"@id": "cred:expirationDate", "@type": "xsd:dateTime"}, "holder": {"@id": "cred:holder", "@type": "@id"}, "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"}, "issuer": {"@id": "cred:issuer", "@type": "@id"}, "issuanceDate": {"@id": "cred:issuanceDate", "@type": "xsd:dateTime"}, "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, "refreshService": {"@id": "cred:refreshService", "@type": "@id", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "cred": "https://www.w3.org/2018/credentials#", "ManualRefreshService2018": "cred:ManualRefreshService2018"}}, "termsOfUse": {"@id": "cred:termsOfUse", "@type": "@id"}, "validFrom": {"@id": "cred:validFrom", "@type": "xsd:dateTime"}, "validUntil": {"@id": "cred:validUntil", "@type": "xsd:dateTime"}}}, "VerifiablePresentation": {"@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "cred": "https://www.w3.org/2018/credentials#", "sec": "https://w3id.org/security#", "holder": {"@id": "cred:holder", "@type": "@id"}, "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, "verifiableCredential": {"@id": "cred:verifiableCredential", "@type": "@id", "@container": "@graph"}}}, "EcdsaSecp256k1Signature2019": {"@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "xsd": "http://www.w3.org/2001/XMLSchema#", "challenge": "sec:challenge", "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, "domain": "sec:domain", "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "jws": "sec:jws", "nonce": "sec:nonce", "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}}}, "proofValue": "sec:proofValue", "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}}}, "EcdsaSecp256r1Signature2019": {"@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "xsd": "http://www.w3.org/2001/XMLSchema#", "challenge": "sec:challenge", "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, "domain": "sec:domain", "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "jws": "sec:jws", "nonce": "sec:nonce", "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}}}, "proofValue": "sec:proofValue", "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}}}, "Ed25519Signature2018": {"@id": "https://w3id.org/security#Ed25519Signature2018", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "xsd": "http://www.w3.org/2001/XMLSchema#", "challenge": "sec:challenge", "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, "domain": "sec:domain", "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "jws": "sec:jws", "nonce": "sec:nonce", "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}}}, "proofValue": "sec:proofValue", "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}}}, "RsaSignature2018": {"@id": "https://w3id.org/security#RsaSignature2018", "@context": {"@version": 1.1, "@protected": true, "challenge": "sec:challenge", "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, "domain": "sec:domain", "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "jws": "sec:jws", "nonce": "sec:nonce", "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab", "@context": {"@version": 1.1, "@protected": true, "id": "@id", "type": "@type", "sec": "https://w3id.org/security#", "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}}}, "proofValue": "sec:proofValue", "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}}}, "proof": {"@id": "https://w3id.org/security#proof", "@type": "@id", "@container": "@graph"}}}}, "https://idhub.pangea.org/context/base.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/base.jsonld", "document": {"@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"}}}, "https://idhub.pangea.org/context/course-credential.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/course-credential.jsonld", "document": {"@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"}}}, "https://w3id.org/security/v2": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://w3c-ccg.github.io/security-vocab/contexts/security-v2.jsonld", "document": {"@context": [{"@version": 1.1}, "https://w3id.org/security/v1", {"AesKeyWrappingKey2019": "sec:AesKeyWrappingKey2019", "DeleteKeyOperation": "sec:DeleteKeyOperation", "DeriveSecretOperation": "sec:DeriveSecretOperation", "EcdsaSecp256k1Signature2019": "sec:EcdsaSecp256k1Signature2019", "EcdsaSecp256r1Signature2019": "sec:EcdsaSecp256r1Signature2019", "EcdsaSecp256k1VerificationKey2019": "sec:EcdsaSecp256k1VerificationKey2019", "EcdsaSecp256r1VerificationKey2019": "sec:EcdsaSecp256r1VerificationKey2019", "Ed25519Signature2018": "sec:Ed25519Signature2018", "Ed25519VerificationKey2018": "sec:Ed25519VerificationKey2018", "EquihashProof2018": "sec:EquihashProof2018", "ExportKeyOperation": "sec:ExportKeyOperation", "GenerateKeyOperation": "sec:GenerateKeyOperation", "KmsOperation": "sec:KmsOperation", "RevokeKeyOperation": "sec:RevokeKeyOperation", "RsaSignature2018": "sec:RsaSignature2018", "RsaVerificationKey2018": "sec:RsaVerificationKey2018", "Sha256HmacKey2019": "sec:Sha256HmacKey2019", "SignOperation": "sec:SignOperation", "UnwrapKeyOperation": "sec:UnwrapKeyOperation", "VerifyOperation": "sec:VerifyOperation", "WrapKeyOperation": "sec:WrapKeyOperation", "X25519KeyAgreementKey2019": "sec:X25519KeyAgreementKey2019", "allowedAction": "sec:allowedAction", "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}, "capability": {"@id": "sec:capability", "@type": "@id"}, "capabilityAction": "sec:capabilityAction", "capabilityChain": {"@id": "sec:capabilityChain", "@type": "@id", "@container": "@list"}, "capabilityDelegation": {"@id": "sec:capabilityDelegationMethod", "@type": "@id", "@container": "@set"}, "capabilityInvocation": {"@id": "sec:capabilityInvocationMethod", "@type": "@id", "@container": "@set"}, "caveat": {"@id": "sec:caveat", "@type": "@id", "@container": "@set"}, "challenge": "sec:challenge", "ciphertext": "sec:ciphertext", "controller": {"@id": "sec:controller", "@type": "@id"}, "delegator": {"@id": "sec:delegator", "@type": "@id"}, "equihashParameterK": {"@id": "sec:equihashParameterK", "@type": "xsd:integer"}, "equihashParameterN": {"@id": "sec:equihashParameterN", "@type": "xsd:integer"}, "invocationTarget": {"@id": "sec:invocationTarget", "@type": "@id"}, "invoker": {"@id": "sec:invoker", "@type": "@id"}, "jws": "sec:jws", "keyAgreement": {"@id": "sec:keyAgreementMethod", "@type": "@id", "@container": "@set"}, "kmsModule": {"@id": "sec:kmsModule"}, "parentCapability": {"@id": "sec:parentCapability", "@type": "@id"}, "plaintext": "sec:plaintext", "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab"}, "proofValue": "sec:proofValue", "referenceId": "sec:referenceId", "unwrappedKey": "sec:unwrappedKey", "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}, "verifyData": "sec:verifyData", "wrappedKey": "sec:wrappedKey"}]}}, "https://w3id.org/security/v1": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://w3c-ccg.github.io/security-vocab/contexts/security-v1.jsonld", "document": {"@context": {"id": "@id", "type": "@type", "dc": "http://purl.org/dc/terms/", "sec": "https://w3id.org/security#", "xsd": "http://www.w3.org/2001/XMLSchema#", "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", "Ed25519Signature2018": "sec:Ed25519Signature2018", "EncryptedMessage": "sec:EncryptedMessage", "GraphSignature2012": "sec:GraphSignature2012", "LinkedDataSignature2015": "sec:LinkedDataSignature2015", "LinkedDataSignature2016": "sec:LinkedDataSignature2016", "CryptographicKey": "sec:Key", "authenticationTag": "sec:authenticationTag", "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", "cipherAlgorithm": "sec:cipherAlgorithm", "cipherData": "sec:cipherData", "cipherKey": "sec:cipherKey", "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, "creator": {"@id": "dc:creator", "@type": "@id"}, "digestAlgorithm": "sec:digestAlgorithm", "digestValue": "sec:digestValue", "domain": "sec:domain", "encryptionKey": "sec:encryptionKey", "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, "initializationVector": "sec:initializationVector", "iterationCount": "sec:iterationCount", "nonce": "sec:nonce", "normalizationAlgorithm": "sec:normalizationAlgorithm", "owner": {"@id": "sec:owner", "@type": "@id"}, "password": "sec:password", "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, "privateKeyPem": "sec:privateKeyPem", "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, "publicKeyBase58": "sec:publicKeyBase58", "publicKeyPem": "sec:publicKeyPem", "publicKeyWif": "sec:publicKeyWif", "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, "salt": "sec:salt", "signature": "sec:signature", "signatureAlgorithm": "sec:signingAlgorithm", "signatureValue": "sec:signatureValue"}}}, "https://idhub.pangea.org/context/federation-membership.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/federation-membership.jsonld", "document": {"@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"}}}, "https://idhub.pangea.org/context/membership-card.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/membership-card.jsonld", "document": {"@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"}}}, "https://idhub.pangea.org/context/financial-vulnerability.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/financial-vulnerability.jsonld", "document": {"@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"}}}, "https://idhub.pangea.org/context/e-operator-claim.jsonld": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://idhub.pangea.org/context/e-operator-claim.jsonld", "document": {"@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"}}}} \ No newline at end of file diff --git a/context/base.jsonld b/context/base.jsonld new file mode 100644 index 0000000..f00284b --- /dev/null +++ b/context/base.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/context/course-credential.jsonld b/context/course-credential.jsonld new file mode 100644 index 0000000..53089c8 --- /dev/null +++ b/context/course-credential.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/context/e-operator-claim.jsonld b/context/e-operator-claim.jsonld new file mode 100644 index 0000000..f53b5db --- /dev/null +++ b/context/e-operator-claim.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/context/federation-membership.jsonld b/context/federation-membership.jsonld new file mode 100644 index 0000000..b48b425 --- /dev/null +++ b/context/federation-membership.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/context/financial-vulnerability.jsonld b/context/financial-vulnerability.jsonld new file mode 100644 index 0000000..345769b --- /dev/null +++ b/context/financial-vulnerability.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/context/membership-card.jsonld b/context/membership-card.jsonld new file mode 100644 index 0000000..7db0236 --- /dev/null +++ b/context/membership-card.jsonld @@ -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" + } +} \ No newline at end of file diff --git a/idhub/models.py b/idhub/models.py index ff952fa..e68c905 100644 --- a/idhub/models.py +++ b/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()) diff --git a/idhub/templates/credentials/course-credential.json b/idhub/templates/credentials/course-credential.json index e7ac036..3475727 100644 --- a/idhub/templates/credentials/course-credential.json +++ b/idhub/templates/credentials/course-credential.json @@ -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" + } +} diff --git a/idhub/templates/credentials/e-operator-claim.json b/idhub/templates/credentials/e-operator-claim.json index 71c46f5..9b4496d 100644 --- a/idhub/templates/credentials/e-operator-claim.json +++ b/idhub/templates/credentials/e-operator-claim.json @@ -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" } +} diff --git a/idhub/templates/credentials/federation-membership.json b/idhub/templates/credentials/federation-membership.json index 9a382a1..b2ecb8b 100644 --- a/idhub/templates/credentials/federation-membership.json +++ b/idhub/templates/credentials/federation-membership.json @@ -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" } +} diff --git a/idhub/templates/credentials/financial-vulnerability.json b/idhub/templates/credentials/financial-vulnerability.json index cfe2495..14a74ca 100644 --- a/idhub/templates/credentials/financial-vulnerability.json +++ b/idhub/templates/credentials/financial-vulnerability.json @@ -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": [ diff --git a/idhub/templates/credentials/membership-card.json b/idhub/templates/credentials/membership-card.json index 0ded5f5..896b50c 100644 --- a/idhub/templates/credentials/membership-card.json +++ b/idhub/templates/credentials/membership-card.json @@ -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", diff --git a/idhub/urls.py b/idhub/urls.py index cae6aa6..9630215 100644 --- a/idhub/urls.py +++ b/idhub/urls.py @@ -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//did.json', serve_did) + path('did-registry//did.json', ServeDidView, name="serve_did") # path('verification_portal/verify/', views_verification_portal.verify, # name="verification_portal_verify") diff --git a/idhub/views.py b/idhub/views.py index bda6e91..5a43570 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -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) diff --git a/oidc4vp/forms.py b/oidc4vp/forms.py index 1c4b19a..84d1d99 100644 --- a/oidc4vp/forms.py +++ b/oidc4vp/forms.py @@ -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) + diff --git a/oidc4vp/models.py b/oidc4vp/models.py index e6a164a..5b1b580 100644 --- a/oidc4vp/models.py +++ b/oidc4vp/models.py @@ -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: diff --git a/oidc4vp/templates/credentials/verifiable_presentation.json b/oidc4vp/templates/credentials/verifiable_presentation.json index a55b769..aa5ace8 100644 --- a/oidc4vp/templates/credentials/verifiable_presentation.json +++ b/oidc4vp/templates/credentials/verifiable_presentation.json @@ -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": "" } diff --git a/oidc4vp/views.py b/oidc4vp/views.py index dd06cbe..0b0360b 100644 --- a/oidc4vp/views.py +++ b/oidc4vp/views.py @@ -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) diff --git a/promotion/forms.py b/promotion/forms.py index b9f9e1a..c1e4541 100644 --- a/promotion/forms.py +++ b/promotion/forms.py @@ -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() - diff --git a/requirements.txt b/requirements.txt index 3520273..0a75cdd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/utils/idhub_ssikit/README.md b/utils/idhub_ssikit/README.md deleted file mode 100644 index 8443717..0000000 --- a/utils/idhub_ssikit/README.md +++ /dev/null @@ -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 -) -``` \ No newline at end of file diff --git a/utils/idhub_ssikit/TENANT_CFG_TEMPLATE b/utils/idhub_ssikit/TENANT_CFG_TEMPLATE deleted file mode 100644 index 00b3372..0000000 --- a/utils/idhub_ssikit/TENANT_CFG_TEMPLATE +++ /dev/null @@ -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" - } - } -} \ No newline at end of file diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py deleted file mode 100644 index 562ef25..0000000 --- a/utils/idhub_ssikit/__init__.py +++ /dev/null @@ -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()) -