Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiple credential status bug and add tests for it. #155

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/CredentialStatusWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
126 changes: 126 additions & 0 deletions test/mocha/20-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 () => {
Expand Down
15 changes: 12 additions & 3 deletions test/mocha/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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({
Expand Down
Loading