Skip to content

Commit

Permalink
feat: handle credential revocations of credentials that aren't accept…
Browse files Browse the repository at this point in the history
…ed yet in IPEX
  • Loading branch information
Sotatek-BaoHoanga committed Nov 7, 2024
1 parent 94c4b12 commit 1895d25
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 25 deletions.
16 changes: 16 additions & 0 deletions src/core/__fixtures__/agent/ipexCommunicationFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,21 @@ const ipexSubmitAdmitSig = [
const ipexSubmitAdmitEnd =
"-LA35AACAA-e-exn-FABEB6wkTnyxwgEgwgPv23OM-bWUSB_jdnlMIab9Q0JUNac0AAAAAAAAAAAAAAAAAAAAAAAEB6wkTnyxwgEgwgPv23OM-bWUSB_jdnlMIab9Q0JUNac-AABABDwPi6ZSD6AMwz-1VDbgGtVWMLUZKmbD6GHXqgYRdgklSO8x_qEwheY16XQvDz9uwpukMg2LyL9FBa64qu65xgE";

const credentialState = {
vn: [1, 0],
i: "EJd6GsxIhMXj1M8Ie0mq7oLgCcoEqp2p0YJIoh6wGa6M",
s: "0",
d: "EEFWKiBAQWh4RK2l_M8SxMZcPPsTaCG1-hIgl5Ve7Vy0",
ri: "EEewa3h_r6kU-VW9RC-CvP6-ZBXhXQzzBQIMnI1_-_GX",
ra: {},
a: {
s: 18,
d: "EHQp2tuAj4RygtixT0QsYUtP6YW5L_yzPThBMwmaARlC",
},
dt: "2024-11-07T08:32:34.943000+00:00",
et: "iss",
};

export {
QVISchema,
credentialRecordProps,
Expand Down Expand Up @@ -714,4 +729,5 @@ export {
ipexSubmitAdmitSerder,
ipexSubmitAdmitSig,
ipexSubmitAdmitEnd,
credentialState,
};
16 changes: 16 additions & 0 deletions src/core/__fixtures__/agent/keriaNotificationFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,21 @@ const notificationIpexApplyProp = {
},
};

const credentialState = {
vn: [1, 0],
i: "EJd6GsxIhMXj1M8Ie0mq7oLgCcoEqp2p0YJIoh6wGa6M",
s: "0",
d: "EEFWKiBAQWh4RK2l_M8SxMZcPPsTaCG1-hIgl5Ve7Vy0",
ri: "EEewa3h_r6kU-VW9RC-CvP6-ZBXhXQzzBQIMnI1_-_GX",
ra: {},
a: {
s: 18,
d: "EHQp2tuAj4RygtixT0QsYUtP6YW5L_yzPThBMwmaARlC",
},
dt: "2024-11-07T08:32:34.943000+00:00",
et: "iss",
};

export {
credentialMetadataMock,
grantForIssuanceExnMessage,
Expand All @@ -352,4 +367,5 @@ export {
notificationIpexGrantProp,
notificationIpexAgreeProp,
notificationIpexApplyProp,
credentialState,
};
55 changes: 33 additions & 22 deletions src/core/agent/services/ipexCommunicationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
ipexSubmitAdmitSerder,
ipexSubmitAdmitSig,
ipexSubmitAdmitEnd,
credentialState,
} from "../../__fixtures__/agent/ipexCommunicationFixture";
import { NotificationRoute } from "../agent.types";
import {
Expand Down Expand Up @@ -112,6 +113,7 @@ const multisigService = jest.mocked({

let credentialListMock = jest.fn();
let credentialGetMock = jest.fn();
const credentialStateMock = jest.fn();
const identifierListMock = jest.fn();
const identifiersMemberMock = jest.fn();
let identifiersGetMock = jest.fn();
Expand Down Expand Up @@ -217,6 +219,7 @@ const signifyClient = jest.mocked({
credentials: () => ({
list: credentialListMock,
get: credentialGetMock,
state: credentialStateMock,
}),
exchanges: () => ({
get: getExchangeMock,
Expand Down Expand Up @@ -725,16 +728,19 @@ describe("Ipex communication service of agent", () => {
grantForIssuanceExnMessage,
ConnectionHistoryType.CREDENTIAL_PRESENTED
);
expect(updateContactMock).toBeCalledWith(grantForIssuanceExnMessage.exn.rp, {
[`${KeriaContactKeyPrefix.HISTORY_IPEX}${grantForIssuanceExnMessage.exn.d}`]:
JSON.stringify({
id: grantForIssuanceExnMessage.exn.d,
dt: grantForIssuanceExnMessage.exn.dt,
credentialType: QVISchema.title,
connectionId: grantForIssuanceExnMessage.exn.rp,
historyType: ConnectionHistoryType.CREDENTIAL_PRESENTED,
}),
});
expect(updateContactMock).toBeCalledWith(
grantForIssuanceExnMessage.exn.rp,
{
[`${KeriaContactKeyPrefix.HISTORY_IPEX}${grantForIssuanceExnMessage.exn.d}`]:
JSON.stringify({
id: grantForIssuanceExnMessage.exn.d,
dt: grantForIssuanceExnMessage.exn.dt,
credentialType: QVISchema.title,
connectionId: grantForIssuanceExnMessage.exn.rp,
historyType: ConnectionHistoryType.CREDENTIAL_PRESENTED,
}),
}
);

expect(schemaGetMock).toBeCalledTimes(2);
expect(connections.resolveOobi).toBeCalledTimes(2);
Expand Down Expand Up @@ -769,16 +775,19 @@ describe("Ipex communication service of agent", () => {
grantForIssuanceExnMessage,
ConnectionHistoryType.CREDENTIAL_PRESENTED
);
expect(updateContactMock).toBeCalledWith(grantForIssuanceExnMessage.exn.rp, {
[`${KeriaContactKeyPrefix.HISTORY_IPEX}${grantForIssuanceExnMessage.exn.d}`]:
JSON.stringify({
id: grantForIssuanceExnMessage.exn.d,
dt: grantForIssuanceExnMessage.exn.dt,
credentialType: QVISchema.title,
connectionId: grantForIssuanceExnMessage.exn.rp,
historyType: ConnectionHistoryType.CREDENTIAL_PRESENTED,
}),
});
expect(updateContactMock).toBeCalledWith(
grantForIssuanceExnMessage.exn.rp,
{
[`${KeriaContactKeyPrefix.HISTORY_IPEX}${grantForIssuanceExnMessage.exn.d}`]:
JSON.stringify({
id: grantForIssuanceExnMessage.exn.d,
dt: grantForIssuanceExnMessage.exn.dt,
credentialType: QVISchema.title,
connectionId: grantForIssuanceExnMessage.exn.rp,
historyType: ConnectionHistoryType.CREDENTIAL_PRESENTED,
}),
}
);
expect(schemaGetMock).toBeCalledTimes(1);
expect(connections.resolveOobi).toBeCalledTimes(1);
});
Expand Down Expand Up @@ -2604,6 +2613,7 @@ describe("Ipex communication service of agent", () => {
test("Can get acdc detail", async () => {
getExchangeMock.mockReturnValueOnce(grantForIssuanceExnMessage);
schemaGetMock.mockResolvedValue(QVISchema);
credentialStateMock.mockResolvedValueOnce(credentialState);

identifierStorage.getIdentifierMetadata = jest
.fn()
Expand All @@ -2624,14 +2634,15 @@ describe("Ipex communication service of agent", () => {
attendeeName: "ccc",
},
s: QVISchema,
lastStatus: { s: "0", dt: "2024-07-30T04:19:55.348Z" },
lastStatus: { s: "1", dt: "2024-11-07T08:32:34.943Z" },
status: "pending",
identifierId: memberIdentifierRecord.id,
});
});

test("Can get acdc detail when the schema has not been resolved", async () => {
getExchangeMock.mockReturnValueOnce(grantForIssuanceExnMessage);
credentialStateMock.mockResolvedValueOnce(credentialState);
const error404 = new Error("Not Found - 404");
schemaGetMock.mockRejectedValueOnce(error404);
identifierStorage.getIdentifierMetadata = jest
Expand Down Expand Up @@ -2664,7 +2675,7 @@ describe("Ipex communication service of agent", () => {
attendeeName: "ccc",
},
s: QVISchema,
lastStatus: { s: "0", dt: "2024-07-30T04:19:55.348Z" },
lastStatus: { s: "1", dt: "2024-11-07T08:32:34.943Z" },
status: "pending",
identifierId: memberIdentifierRecord.id,
});
Expand Down
12 changes: 9 additions & 3 deletions src/core/agent/services/ipexCommunicationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,10 @@ class IpexCommunicationService extends AgentService {
historyType: ConnectionHistoryType
): Promise<void> {
let schemaSaid;
const connectionId = historyType === ConnectionHistoryType.CREDENTIAL_PRESENTED ? message.exn.rp : message.exn.i;
const connectionId =
historyType === ConnectionHistoryType.CREDENTIAL_PRESENTED
? message.exn.rp
: message.exn.i;
if (message.exn.r === ExchangeRoute.IpexGrant) {
schemaSaid = message.exn.e.acdc.s;
} else if (message.exn.r === ExchangeRoute.IpexApply) {
Expand Down Expand Up @@ -971,6 +974,9 @@ class IpexCommunicationService extends AgentService {
said: string
): Promise<Omit<ACDCDetails, "identifierType">> {
const exchange = await this.props.signifyClient.exchanges().get(said);
const credentialState = await this.props.signifyClient
.credentials()
.state(exchange.exn.e.acdc.ri, exchange.exn.e.acdc.d);
const schemaSaid = exchange.exn.e.acdc.s;
const schema = await this.props.signifyClient
.schemas()
Expand All @@ -997,8 +1003,8 @@ class IpexCommunicationService extends AgentService {
version: schema.version,
},
lastStatus: {
s: exchange.exn.e.iss.s,
dt: new Date(exchange.exn.e.iss.dt).toISOString(),
s: credentialState.et === "iss" ? "1" : "0",
dt: new Date(credentialState.dt).toISOString(),
},
status: CredentialStatus.PENDING,
identifierId: exchange.exn.a.i,
Expand Down
44 changes: 44 additions & 0 deletions src/core/agent/services/keriaNotificationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
grantForIssuanceExnMessage,
applyForPresentingExnMessage,
agreeForPresentingExnMessage,
credentialState,
} from "../../__fixtures__/agent/keriaNotificationFixture";
import { ConnectionHistoryType } from "./connectionService.types";

Expand All @@ -38,6 +39,7 @@ const oobiResolveMock = jest.fn();
const queryKeyStateMock = jest.fn();
const markNotificationMock = jest.fn();
const getCredentialMock = jest.fn();
const credentialStateMock = jest.fn();
const admitMock = jest.fn();
const submitAdmitMock = jest.fn();
const listNotificationsMock = jest.fn();
Expand Down Expand Up @@ -98,6 +100,7 @@ const signifyClient = jest.mocked({
credentials: () => ({
get: getCredentialMock,
list: jest.fn(),
state: credentialStateMock,
}),
exchanges: () => ({
get: exchangesGetMock,
Expand Down Expand Up @@ -260,6 +263,8 @@ describe("Signify notification service of agent", () => {

test("emit new event for new notification", async () => {
exchangesGetMock.mockResolvedValue(grantForIssuanceExnMessage);
getCredentialMock.mockResolvedValue(getCredentialResponse);
credentialStateMock.mockResolvedValueOnce(credentialState);
multiSigs.hasMultisig = jest.fn().mockResolvedValue(false);
notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([]);
const notes = [
Expand Down Expand Up @@ -318,6 +323,8 @@ describe("Signify notification service of agent", () => {

test("Should admit if there is an existing credential", async () => {
exchangesGetMock.mockResolvedValue(grantForIssuanceExnMessage);
getCredentialMock.mockResolvedValue(getCredentialResponse);
credentialStateMock.mockResolvedValueOnce(credentialState);
multiSigs.hasMultisig = jest.fn().mockResolvedValue(false);
notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([]);
const notes = [notificationIpexGrantProp];
Expand Down Expand Up @@ -523,6 +530,7 @@ describe("Signify notification service of agent", () => {

test("Should call createLinkedIpexMessageRecord with CREDENTIAL_REVOKED", async () => {
exchangesGetMock.mockResolvedValue(grantForIssuanceExnMessage);
credentialStateMock.mockResolvedValueOnce(credentialState);
notificationStorage.save = jest
.fn()
.mockReturnValue({ id: "id", createdAt: new Date(), content: {} });
Expand Down Expand Up @@ -553,6 +561,38 @@ describe("Signify notification service of agent", () => {
expect(markNotificationMock).toBeCalledTimes(1);
});

test("Should call createLinkedIpexMessageRecord with TEL status is revoke and credential exist in cloud", async () => {
exchangesGetMock.mockResolvedValue(grantForIssuanceExnMessage);
getCredentialMock.mockResolvedValue(getCredentialResponse);
credentialStateMock.mockResolvedValueOnce({
...credentialState,
et: "rev",
});
const credentialIdMock = "credentialId";
notificationStorage.save = jest
.fn()
.mockReturnValue({ id: "id", createdAt: new Date(), content: {} });
credentialStorage.getCredentialMetadata.mockResolvedValue(
credentialMetadataMock
);
identifierStorage.getIdentifierMetadata = jest.fn().mockResolvedValue({
id: "id",
});

await keriaNotificationService.processNotification(
notificationIpexGrantProp
);
expect(credentialService.markAcdc).toBeCalledWith(
grantForIssuanceExnMessage.exn.e.acdc.d,
CredentialStatus.REVOKED
);
expect(ipexCommunications.createLinkedIpexMessageRecord).toBeCalledWith(
grantForIssuanceExnMessage,
ConnectionHistoryType.CREDENTIAL_REVOKED
);
expect(markNotificationMock).toBeCalledTimes(1);
});

test("Should skip if notification route is /multisig/exn and `e.exn.r` is not ipex/admit, ipex/offer, ipex/grant", async () => {
multiSigs.hasMultisig = jest.fn().mockResolvedValue(false);
notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([]);
Expand Down Expand Up @@ -1416,6 +1456,8 @@ describe("Signify notification service of agent", () => {
e: { acdc: { d: "d" } },
},
});
getCredentialMock.mockResolvedValue(getCredentialResponse);
credentialStateMock.mockResolvedValueOnce(credentialState);
credentialStorage.getCredentialMetadata.mockResolvedValueOnce(
credentialMetadataMock
);
Expand All @@ -1440,6 +1482,8 @@ describe("Signify notification service of agent", () => {
e: { acdc: { d: "d" } },
},
});
getCredentialMock.mockResolvedValue(getCredentialResponse);
credentialStateMock.mockResolvedValueOnce(credentialState);
credentialStorage.getCredentialMetadata.mockResolvedValueOnce(
credentialMetadataMock
);
Expand Down
21 changes: 21 additions & 0 deletions src/core/agent/services/keriaNotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ class KeriaNotificationService extends AgentService {
notif: Notification
): Promise<boolean> {
const exchange = await this.props.signifyClient.exchanges().get(notif.a.d);
const signifyCredential = await this.props.signifyClient
.credentials()
.get(exchange.exn.e.acdc.d)
.catch(() => undefined);
const credentialState = await this.props.signifyClient
.credentials()
.state(exchange.exn.e.acdc.ri, exchange.exn.e.acdc.d);
const telStatus = credentialState.et;
const existingCredential =
await this.credentialStorage.getCredentialMetadata(exchange.exn.e.acdc.d);
const ourIdentifier = await this.identifierStorage
Expand All @@ -317,6 +325,19 @@ class KeriaNotificationService extends AgentService {
await this.markNotification(notif.i);
return false;
}
if (signifyCredential && telStatus === "rev") {
await this.credentialService.markAcdc(
exchange.exn.e.acdc.d,
CredentialStatus.REVOKED
);
await this.ipexCommunications.createLinkedIpexMessageRecord(
exchange,
ConnectionHistoryType.CREDENTIAL_REVOKED
);
await this.markNotification(notif.i);
return false;
}

if (
existingCredential &&
existingCredential.status !== CredentialStatus.REVOKED
Expand Down

0 comments on commit 1895d25

Please sign in to comment.