diff --git a/lib/CredentialStatusWriter.js b/lib/CredentialStatusWriter.js index 08257995..487ddb55 100644 --- a/lib/CredentialStatusWriter.js +++ b/lib/CredentialStatusWriter.js @@ -276,7 +276,8 @@ export class CredentialStatusWriter { } _addStatusEntry({credential, statusList, statusListIndex}) { - const {type, baseUrl, statusPurpose, options} = this.statusListConfig; + const {type, baseUrl, options} = this.statusListConfig; + const {statusPurpose} = statusList; // set SLC ID to the ID of the status list as they are one in the same const statusListCredential = statusList.id; diff --git a/test/mocha/20-credentials.js b/test/mocha/20-credentials.js index a8e43af0..7f9f4339 100644 --- a/test/mocha/20-credentials.js +++ b/test/mocha/20-credentials.js @@ -116,6 +116,11 @@ describe('issue APIs', () => { let bslSuspensionRootZcap; let bslSuspensionStatusId; let bslSuspensionStatusRootZcap; + let bslRevocationSuspensionIssuerConfig; + let bslRevocationSuspensionIssuerId; + let bslRevocationSuspensionRootZcap; + let bslRevocationSuspensionStatusId; + let bslRevocationSuspensionStatusRootZcap; let smallBslIssuerConfig; let smallBslIssuerId; let smallBslRootZcap; @@ -248,6 +253,36 @@ describe('issue APIs', () => { `urn:zcap:root:${encodeURIComponent(statusConfig.id)}`; } + // create issuer instance w/ bitstring status list options + // w/ both revocation and suspension status purposes + { + const { + statusConfig, + issuerCreateStatusListZcap + } = await helpers.provisionDependencies(depOptions); + const statusListOptions = [{ + type: 'BitstringStatusList', + statusPurpose: ['revocation', 'suspension'], + zcapReferenceIds: { + createCredentialStatusList: 'createCredentialStatusList' + } + }]; + const newZcaps = { + ...zcaps, + createCredentialStatusList: issuerCreateStatusListZcap + }; + const issuerConfig = await helpers.createIssuerConfig({ + capabilityAgent, zcaps: newZcaps, statusListOptions, suiteName + }); + bslRevocationSuspensionIssuerConfig = issuerConfig; + bslRevocationSuspensionIssuerId = issuerConfig.id; + bslRevocationSuspensionRootZcap = + `urn:zcap:root:${encodeURIComponent(issuerConfig.id)}`; + bslRevocationSuspensionStatusId = statusConfig.id; + bslRevocationSuspensionStatusRootZcap = + `urn:zcap:root:${encodeURIComponent(statusConfig.id)}`; + } + // create issuer instance w/ small status list { const { @@ -1035,6 +1070,97 @@ describe('issue APIs', () => { {verifiableCredential})); status.should.equal(true); }); + it('updates BitstringStatusList revocation+suspension status', + async () => { + // first issue VC + const credential = klona(mockCredential); + const zcapClient = helpers.createZcapClient({capabilityAgent}); + const {data: {verifiableCredential}} = await zcapClient.write({ + url: `${bslRevocationSuspensionIssuerId}/credentials/issue`, + capability: bslRevocationSuspensionRootZcap, + json: {credential, options: issueOptions} + }); + + { + // get VC suspension status + const statusInfo = await helpers.getCredentialStatus( + {verifiableCredential, statusPurpose: 'suspension'}); + let {status} = statusInfo; + status.should.equal(false); + + // then suspend VC + let error; + try { + const {statusListOptions: [{indexAllocator}]} = + bslRevocationSuspensionIssuerConfig; + await zcapClient.write({ + url: `${bslRevocationSuspensionStatusId}/credentials/status`, + capability: bslRevocationSuspensionStatusRootZcap, + json: { + credentialId: verifiableCredential.id, + indexAllocator, + credentialStatus: statusInfo.credentialStatus, + status: true + } + }); + } catch(e) { + error = e; + } + assertNoError(error); + + // force refresh of new SLC + await zcapClient.write({ + url: `${statusInfo.statusListCredential}?refresh=true`, + capability: bslRevocationSuspensionStatusRootZcap, + json: {} + }); + + // check status of VC has changed + ({status} = await helpers.getCredentialStatus( + {verifiableCredential, statusPurpose: 'suspension'})); + status.should.equal(true); + } + + { + // get VC revocation status + const statusInfo = await helpers.getCredentialStatus( + {verifiableCredential, statusPurpose: 'revocation'}); + let {status} = statusInfo; + status.should.equal(false); + + // then revoke VC + let error; + try { + const {statusListOptions: [{indexAllocator}]} = + bslRevocationSuspensionIssuerConfig; + await zcapClient.write({ + url: `${bslRevocationSuspensionStatusId}/credentials/status`, + capability: bslRevocationSuspensionStatusRootZcap, + json: { + credentialId: verifiableCredential.id, + indexAllocator, + credentialStatus: statusInfo.credentialStatus, + status: true + } + }); + } catch(e) { + error = e; + } + assertNoError(error); + + // force refresh of new SLC + await zcapClient.write({ + url: `${statusInfo.statusListCredential}?refresh=true`, + capability: bslRevocationSuspensionStatusRootZcap, + json: {} + }); + + // check status of VC has changed + ({status} = await helpers.getCredentialStatus( + {verifiableCredential, statusPurpose: 'revocation'})); + status.should.equal(true); + } + }); it('updates multiple TerseBitstringStatusList statuses', async () => { diff --git a/test/mocha/helpers.js b/test/mocha/helpers.js index 391a3975..970f0102 100644 --- a/test/mocha/helpers.js +++ b/test/mocha/helpers.js @@ -336,9 +336,16 @@ export async function getCredentialStatus({ verifiableCredential, statusPurpose, listLength }) { // get SLC for the VC - const {credentialStatus} = verifiableCredential; + let {credentialStatus} = verifiableCredential; if(Array.isArray(credentialStatus)) { - throw new Error('Multiple credential statuses not supported.'); + // find matching status purpose + credentialStatus = credentialStatus.find( + cs => cs.statusPurpose === statusPurpose); + if(!credentialStatus) { + throw new Error( + `Credential status with matching status purpose "${statusPurpose}" ` + + 'not found.'); + } } let {statusListCredential} = credentialStatus; let statusListIndex; @@ -379,7 +386,9 @@ export async function getCredentialStatus({ list = await decodeList({encodedList}); } const status = list.getStatus(statusListIndex); - return {status, statusListCredential, expandedCredentialStatus}; + return { + status, statusListCredential, expandedCredentialStatus, credentialStatus + }; } export async function revokeDelegatedCapability({