Skip to content

Commit

Permalink
fix(oob): support oob with connection and messages (openwallet-founda…
Browse files Browse the repository at this point in the history
…tion#1558)

Signed-off-by: Timo Glastra <timo@animo.id>
Signed-off-by: Martin Auer <martin.auer97@gmail.com>
  • Loading branch information
TimoGlastra authored and auer-martin committed Nov 15, 2023
1 parent 595455c commit d917aaa
Show file tree
Hide file tree
Showing 21 changed files with 384 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export class V1CredentialProtocol
attachments: credentialRecord.linkedAttachments,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

credentialRecord.credentialAttributes = message.credentialPreview.attributes
credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential
Expand Down Expand Up @@ -384,7 +384,7 @@ export class V1CredentialProtocol
}),
attachments: credentialRecord.linkedAttachments,
})
message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

credentialRecord.credentialAttributes = message.credentialPreview.attributes
credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential
Expand Down Expand Up @@ -541,6 +541,7 @@ export class V1CredentialProtocol
credentialRecord = new CredentialExchangeRecord({
connectionId: connection?.id,
threadId: offerMessage.threadId,
parentThreadId: offerMessage.thread?.parentThreadId,
state: CredentialState.OfferReceived,
protocolVersion: 'v1',
})
Expand Down Expand Up @@ -612,7 +613,7 @@ export class V1CredentialProtocol
requestAttachments: [attachment],
attachments: offerMessage.appendedAttachments?.filter((attachment) => isLinkedAttachment(attachment)),
})
requestMessage.setThread({ threadId: credentialRecord.threadId })
requestMessage.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

credentialRecord.credentialAttributes = offerMessage.credentialPreview.attributes
credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential
Expand Down Expand Up @@ -691,7 +692,7 @@ export class V1CredentialProtocol
comment,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -731,11 +732,7 @@ export class V1CredentialProtocol

agentContext.config.logger.debug(`Processing credential request with id ${requestMessage.id}`)

const credentialRecord = await this.getByThreadAndConnectionId(
messageContext.agentContext,
requestMessage.threadId,
connection?.id
)
const credentialRecord = await this.getByThreadAndConnectionId(messageContext.agentContext, requestMessage.threadId)
agentContext.config.logger.trace('Credential record found when processing credential request', credentialRecord)

const proposalMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, {
Expand All @@ -755,6 +752,15 @@ export class V1CredentialProtocol
lastSentMessage: offerMessage ?? undefined,
})

// This makes sure that the sender of the incoming message is authorized to do so.
if (!credentialRecord.connectionId) {
await connectionService.matchIncomingMessageToRequestMessageInOutOfBandExchange(messageContext, {
expectedConnectionId: credentialRecord.connectionId,
})

credentialRecord.connectionId = connection?.id
}

const requestAttachment = requestMessage.getRequestAttachmentById(INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID)

if (!requestAttachment) {
Expand Down Expand Up @@ -833,7 +839,7 @@ export class V1CredentialProtocol
attachments: credentialRecord.linkedAttachments,
})

issueMessage.setThread({ threadId: credentialRecord.threadId })
issueMessage.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })
issueMessage.setPleaseAck()

await didCommMessageRepository.saveAgentMessage(agentContext, {
Expand Down Expand Up @@ -938,6 +944,8 @@ export class V1CredentialProtocol
threadId: credentialRecord.threadId,
})

ackMessage.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await this.updateState(agentContext, credentialRecord, CredentialState.Done)

return { message: ackMessage, credentialRecord }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@ describe('V1CredentialProtocol', () => {
// then
expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, {
threadId: 'somethreadid',
connectionId: connection.id,
})
expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1)
expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived)
Expand All @@ -360,7 +359,6 @@ describe('V1CredentialProtocol', () => {
// then
expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, {
threadId: 'somethreadid',
connectionId: connection.id,
})
expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived)
})
Expand Down
25 changes: 18 additions & 7 deletions packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
})
requestPresentationMessage.setThread({
threadId: proofRecord.threadId,
parentThreadId: proofRecord.parentThreadId,
})

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
Expand Down Expand Up @@ -523,7 +524,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
comment,
presentationProposal,
})
message.setThread({ threadId: proofRecord.threadId })
message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -600,7 +601,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
comment,
presentationAttachments: [attachment],
})
message.setThread({ threadId: proofRecord.threadId })
message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId })

await didCommMessageRepository.saveAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -745,11 +746,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
// only depends on the public api, rather than the internal API (this helps with breaking changes)
const connectionService = agentContext.dependencyManager.resolve(ConnectionService)

const proofRecord = await this.getByThreadAndConnectionId(
agentContext,
presentationMessage.threadId,
connection?.id
)
const proofRecord = await this.getByThreadAndConnectionId(agentContext, presentationMessage.threadId)

const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, {
associatedRecordId: proofRecord.id,
Expand All @@ -769,6 +766,15 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
lastSentMessage: requestMessage,
})

// This makes sure that the sender of the incoming message is authorized to do so.
if (!proofRecord.connectionId) {
await connectionService.matchIncomingMessageToRequestMessageInOutOfBandExchange(messageContext, {
expectedConnectionId: proofRecord.connectionId,
})

proofRecord.connectionId = connection?.id
}

const presentationAttachment = presentationMessage.getPresentationAttachmentById(INDY_PROOF_ATTACHMENT_ID)
if (!presentationAttachment) {
throw new AriesFrameworkError('Missing indy proof attachment in processPresentation')
Expand Down Expand Up @@ -814,6 +820,11 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<
threadId: proofRecord.threadId,
})

ackMessage.setThread({
threadId: proofRecord.threadId,
parentThreadId: proofRecord.parentThreadId,
})

// Update record
await this.updateState(agentContext, proofRecord, ProofState.Done)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the
"credentialIds": [
"f54d231b-ef4f-4da5-adad-b10a1edaeb18",
],
"parentThreadId": undefined,
"state": "done",
"threadId": "c5fc78be-b355-4411-86f3-3d97482b9841",
},
Expand Down Expand Up @@ -209,6 +210,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the
"tags": {
"connectionId": undefined,
"credentialIds": [],
"parentThreadId": undefined,
"state": "offer-received",
"threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b",
},
Expand Down Expand Up @@ -511,6 +513,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the
"tags": {
"connectionId": undefined,
"credentialIds": [],
"parentThreadId": undefined,
"state": "offer-sent",
"threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b",
},
Expand Down Expand Up @@ -604,6 +607,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the
"tags": {
"connectionId": undefined,
"credentialIds": [],
"parentThreadId": undefined,
"state": "done",
"threadId": "c5fc78be-b355-4411-86f3-3d97482b9841",
},
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/agent/getOutboundMessageContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import type { ResolvedDidCommService } from '../modules/didcomm'
import type { OutOfBandRecord } from '../modules/oob'
import type { BaseRecordAny } from '../storage/BaseRecord'

import { Agent } from 'http'

import { Key } from '../crypto'
import { ServiceDecorator } from '../decorators/service/ServiceDecorator'
import { AriesFrameworkError } from '../error'
import { OutOfBandService, OutOfBandRole, OutOfBandRepository } from '../modules/oob'
import { InvitationType, OutOfBandRepository, OutOfBandRole, OutOfBandService } from '../modules/oob'
import { OutOfBandRecordMetadataKeys } from '../modules/oob/repository/outOfBandRecordMetadataTypes'
import { RoutingService } from '../modules/routing'
import { DidCommMessageRepository, DidCommMessageRole } from '../storage'
Expand Down Expand Up @@ -297,8 +295,11 @@ async function addExchangeDataToMessage(
associatedRecord: BaseRecordAny
}
) {
const legacyInvitationMetadata = outOfBandRecord?.metadata.get(OutOfBandRecordMetadataKeys.LegacyInvitation)

// Set the parentThreadId on the message from the oob invitation
if (outOfBandRecord) {
// If connectionless is used, we should not add the parentThreadId
if (outOfBandRecord && legacyInvitationMetadata?.legacyInvitationType !== InvitationType.Connectionless) {
if (!message.thread) {
message.setThread({
parentThreadId: outOfBandRecord.outOfBandInvitation.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { OutOfBandService } from '../../oob/OutOfBandService'
import { OutOfBandRole } from '../../oob/domain/OutOfBandRole'
import { OutOfBandState } from '../../oob/domain/OutOfBandState'
import { OutOfBandRepository } from '../../oob/repository'
import { OutOfBandRecordMetadataKeys } from '../../oob/repository/outOfBandRecordMetadataTypes'
import { ConnectionEventTypes } from '../ConnectionEvents'
import { ConnectionProblemReportError, ConnectionProblemReportReason } from '../errors'
import { ConnectionRequestMessage, ConnectionResponseMessage, TrustPingMessage } from '../messages'
Expand Down Expand Up @@ -538,6 +539,86 @@ export class ConnectionService {
}
}

/**
* If knownConnectionId is passed, it will compare the incoming connection id with the knownConnectionId, and skip the other validation.
*
* If no known connection id is passed, it asserts that the incoming message is in response to an attached request message to an out of band invitation.
* If is the case, and the state of the out of band record is still await response, the state will be updated to done
*
*/
public async matchIncomingMessageToRequestMessageInOutOfBandExchange(
messageContext: InboundMessageContext,
{ expectedConnectionId }: { expectedConnectionId?: string }
) {
if (expectedConnectionId && messageContext.connection?.id === expectedConnectionId) {
throw new AriesFrameworkError(
`Expecting incoming message to have connection ${expectedConnectionId}, but incoming connection is ${
messageContext.connection?.id ?? 'undefined'
}`
)
}

const outOfBandRepository = messageContext.agentContext.dependencyManager.resolve(OutOfBandRepository)
const outOfBandInvitationId = messageContext.message.thread?.parentThreadId

// Find the out of band record that is associated with this request
const outOfBandRecord = await outOfBandRepository.findSingleByQuery(messageContext.agentContext, {
invitationId: outOfBandInvitationId,
role: OutOfBandRole.Sender,
invitationRequestsThreadIds: [messageContext.message.threadId],
})

// There is no out of band record
if (!outOfBandRecord) {
throw new AriesFrameworkError(
`No out of band record found for credential request message with thread ${messageContext.message.threadId}, out of band invitation id ${outOfBandInvitationId} and role ${OutOfBandRole.Sender}`
)
}

const legacyInvitationMetadata = outOfBandRecord.metadata.get(OutOfBandRecordMetadataKeys.LegacyInvitation)

// If the original invitation was a legacy connectionless invitation, it's okay if the message does not have a pthid.
if (
legacyInvitationMetadata?.legacyInvitationType !== 'connectionless' &&
outOfBandRecord.outOfBandInvitation.id !== outOfBandInvitationId
) {
throw new AriesFrameworkError(
'Response messages to out of band invitation requests MUST have a parent thread id that matches the out of band invitation id.'
)
}

// This should not happen, as it is not allowed to create reusable out of band invitations with attached messages
// But should that implementation change, we at least cover it here.
if (outOfBandRecord.reusable) {
throw new AriesFrameworkError(
'Receiving messages in response to reusable out of band invitations is not supported.'
)
}

if (outOfBandRecord.state === OutOfBandState.Done) {
if (!messageContext.connection) {
throw new AriesFrameworkError(
"Can't find connection associated with incoming message, while out of band state is done. State must be await response if no connection has been created"
)
}
if (messageContext.connection.outOfBandId !== outOfBandRecord.id) {
throw new AriesFrameworkError(
'Connection associated with incoming message is not associated with the out of band invitation containing the attached message.'
)
}

// We're good to go. Connection was created and points to the correct out of band record. And the message is in response to an attached request message from the oob invitation.
} else if (outOfBandRecord.state === OutOfBandState.AwaitResponse) {
// We're good to go. Waiting for a response. And the message is in response to an attached request message from the oob invitation.

// Now that we have received the first response message to our out of band invitation, we mark the out of band record as done
outOfBandRecord.state = OutOfBandState.Done
await outOfBandRepository.update(messageContext.agentContext, outOfBandRecord)
} else {
throw new AriesFrameworkError(`Out of band record is in incorrect state ${outOfBandRecord.state}`)
}
}

public async updateState(agentContext: AgentContext, connectionRecord: ConnectionRecord, newState: DidExchangeState) {
const previousState = connectionRecord.state
connectionRecord.state = newState
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/modules/credentials/CredentialsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export class CredentialsApi<CPs extends CredentialProtocol[]> implements Credent
})
message.setThread({
threadId: credentialRecord.threadId,
parentThreadId: credentialRecord.parentThreadId,
})
const outboundMessageContext = await getOutboundMessageContext(this.agentContext, {
message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
credentialPreview,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -182,7 +182,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
comment,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -254,7 +254,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
credentialPreview,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -345,7 +345,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
comment,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -399,7 +399,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
requestAttachments: requestAttachments,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
agentMessage: message,
Expand Down Expand Up @@ -498,7 +498,7 @@ export class CredentialFormatCoordinator<CFs extends CredentialFormatService[]>
comment,
})

message.setThread({ threadId: credentialRecord.threadId })
message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId })
message.setPleaseAck()

await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, {
Expand Down

0 comments on commit d917aaa

Please sign in to comment.