From c4390b1c41ceca5970231ada1618803a290338b2 Mon Sep 17 00:00:00 2001 From: whitneypurdum Date: Mon, 11 Jul 2022 12:24:43 +0100 Subject: [PATCH] feat(verifyVc): add unit tests --- e2e/claims.service.e2e.ts | 182 ++++++++++++++++++++++++++- src/errors/error-messages.ts | 2 +- src/modules/claims/claims.service.ts | 102 +++++++++------ 3 files changed, 242 insertions(+), 44 deletions(-) diff --git a/e2e/claims.service.e2e.ts b/e2e/claims.service.e2e.ts index 4f49751c..fab446d9 100644 --- a/e2e/claims.service.e2e.ts +++ b/e2e/claims.service.e2e.ts @@ -1,10 +1,12 @@ import { IRoleDefinitionV2, + IssuerFields, PreconditionType, } from '@energyweb/credential-governance'; import { Methods, Chain } from '@ew-did-registry/did'; import { addressOf } from '@ew-did-registry/did-ethr-resolver'; import { KeyTags } from '@ew-did-registry/did-resolver-interface'; +import {} from '../src'; import { CredentialStatusPurpose, VerifiablePresentation, @@ -28,6 +30,9 @@ import { SignerT, Claim, RoleCredentialSubject, + CacheClient, + VerifiableCredentialsServiceBase, + getVerifiableCredentialsService, } from '../src'; import { replenish, root, rpcUrl, setupENS } from './utils/setup-contracts'; import { ClaimManager__factory } from '../ethers/factories/ClaimManager__factory'; @@ -50,6 +55,10 @@ const roleName2 = 'myrole2'; const roleName3 = 'myrole3'; const roleName4 = 'myrole4'; const roleName5 = 'myrole5'; +const verifyVcRole = 'verifyVcRole'; +const verifyVcRole2 = 'verifyVcRole2'; +const verifyOffChainClaimRole = 'verifyOnChain'; +const resolveVC = 'resolvevc'; const namespace = root; const version = 1; const baseRoleDef = { @@ -86,6 +95,26 @@ const roles: Record = { roleName: roleName5, defaultValidityPeriod: 1000, }, + [`${verifyVcRole}.${root}`]: { + ...baseRoleDef, + roleName: verifyVcRole, + issuer: { issuerType: 'DID', did: [rootOwnerDID] }, + }, + [`${resolveVC}.${root}`]: { + ...baseRoleDef, + roleName: resolveVC, + issuer: { issuerType: 'DID', did: [rootOwnerDID] }, + }, + [`${verifyVcRole2}.${root}`]: { + ...baseRoleDef, + roleName: verifyVcRole2, + issuer: { issuerType: 'DID', did: [staticIssuerDID] }, + }, + [`${verifyOffChainClaimRole}.${root}`]: { + ...baseRoleDef, + roleName: verifyOffChainClaimRole, + issuer: { issuerType: 'DID', did: [staticIssuerDID] }, + }, }; const mockGetRoleDefinition = jest .fn() @@ -156,11 +185,12 @@ jest.mock('../src/modules/messaging/messaging.service', () => { describe('Сlaim tests', () => { let claimsService: ClaimsService; - let signerService: SignerService; + let signerService: SignerService; // let assetsService: AssetsService; let domainsService: DomainsService; let claimManager: ClaimManager; - let didRegistry: DidRegistry; + let didRegistry: DidRegistry; // + let verifiableCredentialsService: VerifiableCredentialsServiceBase; beforeEach(async () => { jest.clearAllMocks(); @@ -184,6 +214,7 @@ describe('Сlaim tests', () => { ({ domainsService, connectToDidRegistry, assetsService } = await connectToCacheServer()); await domainsService.createRole({ + //USE THIS TO CREATE ROLE!!!! roleName: roleName1, namespace, data: roles[`${roleName1}.${root}`], @@ -201,6 +232,30 @@ describe('Сlaim tests', () => { data: roles[`${roleName5}.${root}`], returnSteps: false, }); + await domainsService.createRole({ + roleName: verifyVcRole, + namespace, + data: roles[`${verifyVcRole}.${root}`], + returnSteps: false, + }); + await domainsService.createRole({ + roleName: verifyVcRole2, + namespace, + data: roles[`${verifyVcRole2}.${root}`], + returnSteps: false, + }); + await domainsService.createRole({ + roleName: verifyOffChainClaimRole, + namespace, + data: roles[`${verifyOffChainClaimRole}.${root}`], + returnSteps: false, + }); + await domainsService.createRole({ + roleName: resolveVC, + namespace, + data: roles[`${resolveVC}.${root}`], + returnSteps: false, + }); ({ didRegistry, claimsService } = await connectToDidRegistry()); mockGetAllowedRoles.mockImplementation(async (issuer) => { const roleDefs = Object.values(roles); @@ -222,8 +277,13 @@ describe('Сlaim tests', () => { return allowedRoles; }); - Reflect.set(didRegistry, '_cacheClient', undefined); + Reflect.set(didRegistry, '_staticIssuerDIDnt', undefined); setLogger(new ConsoleLogger(LogLevel.warn)); + const cacheClient = new CacheClient(signerService); + verifiableCredentialsService = await getVerifiableCredentialsService( + signerService, + cacheClient + ); }); describe('Role claim tests', () => { @@ -249,6 +309,7 @@ describe('Сlaim tests', () => { await signerService.connect(requestSigner, ProviderType.PrivateKey); const requesterDID = signerService.did; const requestorFields = [{ key: 'temperature', value: 36 }]; + //CREATE CLAIM REQUEST AND ISSUE CLAIM RESQUEST await claimsService.createClaimRequest({ claim: { claimType, @@ -781,6 +842,7 @@ describe('Сlaim tests', () => { { document: { id: assetDID }, id: assetDID }, ]); const claimType = 'test claim'; + //CLAIM URL!!!! const claimUrl = await claimsService.createSelfSignedClaim({ data: { claimType }, subject: assetDID, @@ -808,4 +870,118 @@ describe('Сlaim tests', () => { ).toBeDefined(); }); }); + + describe('Verification Tests', () => { + const createExampleSignedCredential = async ( + issuerFields: IssuerFields[], + namespace: string, + expirationDate?: Date + ) => { + return await verifiableCredentialsService.createRoleVC({ + id: rootOwnerDID, + namespace: namespace, + version: '1', + issuerFields, + expirationDate, + }); + }; + + test('verifyVc should verify a credential with no errors if the Issuer is authorized', async () => { + await signerService.connect(rootOwner, ProviderType.PrivateKey); + await domainsService.createRole({ + roleName: verifyVcRole, + namespace, + data: roles[`${verifyVcRole}.${root}`], + returnSteps: false, + }); + const vc = await createExampleSignedCredential( + [], + `${verifyVcRole}.${root}` + ); + const result = await claimsService.verifyVc(vc); + expect(result.errors).toHaveLength(0); + expect(result.isVerified).toBeTruthy; + }); + test('verifyVc should: not verify a credential and return an issuer error if the issuer is not authorized', async () => { + const roleName = `${verifyVcRole2}.${root}`; + await domainsService.createRole({ + roleName: verifyVcRole2, + namespace, + data: roles[roleName], + returnSteps: false, + }); + const vc = await createExampleSignedCredential([], roleName); + const result = await claimsService.verifyVc(vc); + expect(result.errors).toHaveLength(1); + expect(result.errors).toContain( + `Issuer ${rootOwnerDID} is not authorized to issue ${roleName}: issuer is not in DID list` + ); + expect(result.isVerified).toBeFalsy; + }); + test('resolveCredentialAndVerify should resolve a Verifiable Credential and call correct verification method (verifyVC)', async () => { + const roleName = `${resolveVC}.${root}`; + console.log(rootOwnerDID, 'CHECK THE DID'); + await signerService.connect(rootOwner, ProviderType.PrivateKey); + await domainsService.createRole({ + roleName: resolveVC, + namespace, + data: roles[roleName], + returnSteps: false, + }); + await createExampleSignedCredential([], roleName); + const result = await claimsService.resolveCredentialAndVerify( + rootOwnerDID, + roleName + ); + console.log(result, 'THE RESOLVE RESULT'); + }); + test('verifyOffChainClaim should verify an issued off chain claim', async () => { + const roleName = `${verifyOffChainClaimRole}.${root}`; + await signerService.connect(rootOwner, ProviderType.PrivateKey); + const requestorFields = [{ key: 'temperature', value: 36 }]; + //CREATE CLAIM REQUEST AND ISSUE CLAIM RESQUEST + await domainsService.createRole({ + roleName: verifyOffChainClaimRole, + namespace, + data: roles[roleName], + returnSteps: false, + }); + const claimRequest = await claimsService.createClaimRequest({ + claim: { + claimType: roleName, + claimTypeVersion: version, + requestorFields, + }, + registrationTypes: [ + RegistrationTypes.OffChain, + RegistrationTypes.OnChain, + ], + subject: rootOwnerDID, + }); + console.log(claimRequest, 'THE CLAIM REQUEST'); + const [message] = mockRequestClaim.mock.calls.pop(); + + message.claimIssuer = [staticIssuerDID]; + console.log(message, 'THE MESSAGE'); + console.log(staticIssuer, 'THE STATIC ISSUER'); + await signerService.connect(staticIssuer, ProviderType.PrivateKey); + const issuance = await claimsService.issueClaimRequest({ + publishOnChain: false, + issuerFields: [], + expirationTimestamp: undefined, + ...message, + }); + console.log(issuance, 'THE ISSUANCE'); + const [, issuedClaim] = <[string, Required]>( + mockIssueClaim.mock.calls.pop() + ); + console.log(issuedClaim, 'THE ISSUED CLAIM'); + //await signerService.connect(rootOwner, ProviderType.PrivateKey); + const result = await claimsService.verifyOffChainClaim( + rootOwnerDID, + roleName + ); + console.log(result, 'ON CHAIN CLAIM RESULT'); + }); + }); }); diff --git a/src/errors/error-messages.ts b/src/errors/error-messages.ts index 3e6fa0d6..a32b5755 100644 --- a/src/errors/error-messages.ts +++ b/src/errors/error-messages.ts @@ -34,5 +34,5 @@ export enum ERROR_MESSAGES { DID_DOCUMENT_NOT_UPDATED = 'DID Document was not updated', PROOF_NOT_VERIFIED = 'Proof not verified', OFFCHAIN_ISSUER_NOT_AUTHORIZED = 'Issuer of OffChain Claim is not authorized', - NO_CLAIM_RESOLVED = 'No claim found for given DID and role' + NO_CLAIM_RESOLVED = 'No claim found for given DID and role', } diff --git a/src/modules/claims/claims.service.ts b/src/modules/claims/claims.service.ts index c0a2e0c6..24ba7e9f 100644 --- a/src/modules/claims/claims.service.ts +++ b/src/modules/claims/claims.service.ts @@ -68,7 +68,7 @@ import { EthersProviderIssuerResolver, IpfsCredentialResolver, VCIssuerVerification, - ClaimIssuerVerification + ClaimIssuerVerification, } from '@energyweb/vc-verification'; import { DidRegistry } from '../did-registry/did-registry.service'; import { ClaimData } from '../did-registry/did.types'; @@ -104,7 +104,7 @@ export class ClaimsService { private _claimRevocation: ClaimRevocation; private _vcIssuerVerifier: VCIssuerVerification; private _issuerResolver: EthersProviderIssuerResolver; - private _credentialResolver: CredentialResolver + private _credentialResolver: CredentialResolver; constructor( private _signerService: SignerService, private _domainsService: DomainsService, @@ -1421,7 +1421,7 @@ export class ClaimsService { ): Promise { const errors: string[] = []; const issuerDID = this._signerService.did; - const proofVerified = await this._verifiableCredentialService.verify(vc) + const proofVerified = await this._verifiableCredentialService.verify(vc); if (!proofVerified) { errors.push(ERROR_MESSAGES.PROOF_NOT_VERIFIED); } @@ -1429,64 +1429,86 @@ export class ClaimsService { let issuerVerified = true; try { await this._vcIssuerVerifier.verifyIssuer(issuerDID, role); - } catch(e) { + } catch (e) { issuerVerified = false; - errors.push((e as Error).message) + errors.push((e as Error).message); } return { errors, - isVerified: proofVerified && issuerVerified - } + isVerified: proofVerified && issuerVerified, + }; } - /** + /** * Verifies: * - That off-chain claim was issued by authorized issuer * - That off-chain claim proof is valid * * @param subjectDID The DID to try to resolve a credential for - * @param roleNamesapce The role to try to get a credential for. Should be a full role namespace (for example, "myrole.roles.myorg.auth.ewc") + * @param roleNamesapce The role to try to get a credential for. Should be a full role namespace (for example, "myrole.roles.myorg.auth.ewc") * @return Boolean indicating if verified and array of error messages */ - async verifyOffChainClaim(subjectDID: string, roleNamespace: string): Promise { - const errors: string[] = []; - const issuerDID = this._signerService.did; - const claimIssuerVerifier = new ClaimIssuerVerification(this._signerService.provider, this._didRegistry.registrySettings, this._credentialResolver, this._issuerResolver); - const issuerVerified = await claimIssuerVerifier.verifyIssuer(issuerDID, roleNamespace); - if (!issuerVerified) { - errors.push(ERROR_MESSAGES.OFFCHAIN_ISSUER_NOT_AUTHORIZED) - } - let proofVerified = true; - try { - await claimIssuerVerifier.verifyIssuance(subjectDID, roleNamespace); - } catch (e) { - proofVerified = false; - errors.push((e as Error).message); - } - return { - errors: errors, - isVerified: proofVerified && issuerVerified - } + async verifyOffChainClaim( + subjectDID: string, + roleNamespace: string + ): Promise { + const errors: string[] = []; + const issuerDID = this._signerService.did; + const claimIssuerVerifier = new ClaimIssuerVerification( + this._signerService.provider, + this._didRegistry.registrySettings, + this._credentialResolver, + this._issuerResolver + ); + const issuerVerified = await claimIssuerVerifier.verifyIssuer( + issuerDID, + roleNamespace + ); + + if (!issuerVerified) { + errors.push(ERROR_MESSAGES.OFFCHAIN_ISSUER_NOT_AUTHORIZED); } + let proofVerified = true; + try { + await claimIssuerVerifier.verifyIssuance(subjectDID, roleNamespace); + } catch (e) { + proofVerified = false; + errors.push((e as Error).message); + } + return { + errors: errors, + isVerified: proofVerified && issuerVerified, + }; + } - /** + /** * Resolve a credential from storage and verify its proof/signature and its issuer's authority * * @param subjectDID The DID to try to resolve a credential for - * @param roleNamesapce The role to try to get a credential for. Should be a full role namespace (for example, "myrole.roles.myorg.auth.ewc") + * @param roleNamesapce The role to try to get a credential for. Should be a full role namespace (for example, "myrole.roles.myorg.auth.ewc") * @return void. Returns "Proof Not Verified" error if VC not verified. Returns error if issuer not verified */ - async resolveCredentialAndVerify(subjectDID: string, roleNamespace: string): Promise { - const resolvedCredential = await this._credentialResolver.getCredential(subjectDID, roleNamespace); - if (!resolvedCredential) { - return { - isVerified: false, - errors: [ERROR_MESSAGES.NO_CLAIM_RESOLVED] - } - } - const credentialIsOffChain = resolvedCredential?.issuedToken; - return credentialIsOffChain ? this.verifyOffChainClaim(subjectDID, roleNamespace) : this.verifyVc(resolvedCredential as VerifiableCredential); + async resolveCredentialAndVerify( + subjectDID: string, + roleNamespace: string + ): Promise { + const resolvedCredential = await this._credentialResolver.getCredential( + subjectDID, + roleNamespace + ); + if (!resolvedCredential) { + return { + isVerified: false, + errors: [ERROR_MESSAGES.NO_CLAIM_RESOLVED], + }; } + const credentialIsOffChain = resolvedCredential?.issuedToken; + return credentialIsOffChain + ? this.verifyOffChainClaim(subjectDID, roleNamespace) + : this.verifyVc( + resolvedCredential as VerifiableCredential + ); + } /** *