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

feat: auto accept proofs #367

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9dd797d
feat: propose proof always is auto accept
berendsliedrecht Jun 30, 2021
8632876
fixed for testing
berendsliedrecht Jun 30, 2021
8c0a1c7
fix for testing
berendsliedrecht Jun 30, 2021
73dfdfa
feat: auto accept proof always working
berendsliedrecht Jun 30, 2021
0826f19
improved testing and added contentApproved
berendsliedrecht Jun 30, 2021
f7d194e
revert changed from auto accept credential
berendsliedrecht Jun 30, 2021
456fce7
removed autoAcceptCredential
berendsliedrecht Jun 30, 2021
1a9627d
Removed tags
berendsliedrecht Jun 30, 2021
6c73742
Copied from credentialresponsecoordinator
berendsliedrecht Jul 5, 2021
8d383ee
minified tests
berendsliedrecht Jul 5, 2021
b46eff5
Consistent logging
berendsliedrecht Jul 6, 2021
5849035
test: more in sync with other tests
berendsliedrecht Jul 6, 2021
2a1fad0
added functionality in the README
berendsliedrecht Jul 6, 2021
bc5aa3e
Added better documentation
berendsliedrecht Jul 6, 2021
1596a25
Fix: functions were not async
berendsliedrecht Jul 7, 2021
8e3c139
Merge branch 'main' into feature/auto-accept-proof
berendsliedrecht Jul 9, 2021
659b17c
Merge remote-tracking branch 'origin/main' into feature/auto-accept-p…
berendsliedrecht Jul 13, 2021
44cebf0
merged
berendsliedrecht Jul 13, 2021
938402f
Merge remote-tracking branch 'origin/main' into feature/auto-accept-p…
berendsliedrecht Jul 14, 2021
23f1d86
Fix: feedback from auto-accept-credential
berendsliedrecht Jul 14, 2021
7f39d2a
Merge remote-tracking branch 'origin/main' into feature/auto-accept-p…
berendsliedrecht Jul 16, 2021
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ Some features are not yet supported, but are on our roadmap. Check [the roadmap]
- ✅ Present Proof Protocol ([RFC 0037](https://github.com/hyperledger/aries-rfcs/tree/master/features/0037-present-proof/README.md))
- ✅ Connection Protocol ([RFC 0160](https://github.com/hyperledger/aries-rfcs/blob/master/features/0160-connection-protocol/README.md))
- ✅ Basic Message Protocol ([RFC 0095](https://github.com/hyperledger/aries-rfcs/blob/master/features/0095-basic-message/README.md))
- ✅ Mediator Coordination Protocol ([RFC 0211](https://github.com/hyperledger/aries-rfcs/blob/master/features/0211-route-coordination/README.md))
- ✅ Indy Credentials (with `did:sov` support)
- ✅ HTTP Transport
- ✅ Mediator Coordination Protocol ([RFC 0211](https://github.com/hyperledger/aries-rfcs/blob/master/features/0211-route-coordination/README.md))
- ✅ Auto accept proofs
- 🚧 Revocation of Indy Credentials
- 🚧 Electron
- 🚧 WebSocket Transport
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DID_COMM_TRANSPORT_QUEUE } from '../constants'
import { AriesFrameworkError } from '../error'
import { ConsoleLogger, LogLevel } from '../logger'
import { AutoAcceptCredential } from '../modules/credentials/CredentialAutoAcceptType'
import { AutoAcceptProof } from '../modules/proofs/ProofAutoAcceptType'
import { MediatorPickupStrategy } from '../modules/routing/MediatorPickupStrategy'
import { DidCommMimeType } from '../types'

Expand Down Expand Up @@ -69,6 +70,10 @@ export class AgentConfig {
return this.initConfig.autoAcceptConnections ?? false
}

public get autoAcceptProofs() {
return this.initConfig.autoAcceptProofs ?? AutoAcceptProof.Never
}

public get autoAcceptCredentials() {
return this.initConfig.autoAcceptCredentials ?? AutoAcceptCredential.Never
}
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/modules/proofs/ProofAutoAcceptType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Typing of the state for auto acceptance
*/
export enum AutoAcceptProof {
// Always auto accepts the proof no matter if it changed in subsequent steps
Always = 'always',

// Needs one acceptation and the rest will be automated if nothing changes
ContentApproved = 'contentApproved',

// DEFAULT: Never auto accept a proof
Never = 'never',
}
86 changes: 86 additions & 0 deletions packages/core/src/modules/proofs/ProofResponseCoordinator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { ProofRecord } from './repository'

import { scoped, Lifecycle } from 'tsyringe'

import { AgentConfig } from '../../agent/AgentConfig'

import { AutoAcceptProof } from './ProofAutoAcceptType'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have two types, like AutoAcceptProof for the holder and AutoAcceptProofRequest request for the issuer. Similarly with credentials. But that's of course more of a future improvement idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for this? Maybe it allows for some use cases that I don't know of.

Copy link
Contributor

@jakubkoci jakubkoci Jul 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 2 reasons:
The first is expressiveness. AutoAcceptProof means auto acceptance proof from the verifier point of view but also auto acceptance of the proof request from the holder's perspective. Even now, I'm not sure if I wrote it correctly.

The second is a potential use case when someone uses an agent for more than one role. Let's say I want to accept proofs as a verifier but don't want to accept proof requests as a holder.


/**
* This class handles all the automation with all the messages in the present proof protocol
* Every function returns `true` if it should automate the flow and `false` if not
*/
@scoped(Lifecycle.ContainerScoped)
export class ProofResponseCoordinator {
private agentConfig: AgentConfig

public constructor(agentConfig: AgentConfig) {
this.agentConfig = agentConfig
}

/**
* Returns the proof auto accept config based on priority:
* - The record config takes first priority
* - Otherwise the agent config
* - Otherwise {@link AutoAcceptProof.Never} is returned
*/
private static composeAutoAccept(
recordConfig: AutoAcceptProof | undefined,
agentConfig: AutoAcceptProof | undefined
) {
return recordConfig ?? agentConfig ?? AutoAcceptProof.Never
}

/**
* Checks whether it should automatically respond to a proposal
*/
public shoudlAutoRespondToProposal(proofRecord: ProofRecord) {
const autoAccept = ProofResponseCoordinator.composeAutoAccept(
proofRecord.autoAcceptProof,
this.agentConfig.autoAcceptProofs
)

if (autoAccept === AutoAcceptProof.Always) {
return true
}
return false
}

/**
* Checks whether it should automatically respond to a request
*/
public shouldAutoRespondToRequest(proofRecord: ProofRecord) {
const autoAccept = ProofResponseCoordinator.composeAutoAccept(
proofRecord.autoAcceptProof,
this.agentConfig.autoAcceptProofs
)

if (
autoAccept === AutoAcceptProof.Always ||
(autoAccept === AutoAcceptProof.ContentApproved && proofRecord.proposalMessage)
) {
return true
}

return false
}

/**
* Checks whether it should automatically respond to a presention of proof
*/
public shouldAutoRespondToPresentation(proofRecord: ProofRecord) {
const autoAccept = ProofResponseCoordinator.composeAutoAccept(
proofRecord.autoAcceptProof,
this.agentConfig.autoAcceptProofs
)

if (
autoAccept === AutoAcceptProof.Always ||
(autoAccept === AutoAcceptProof.ContentApproved && proofRecord.requestMessage)
) {
return true
}

return false
}
}
22 changes: 18 additions & 4 deletions packages/core/src/modules/proofs/ProofsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import type { ProofRecord } from './repository/ProofRecord'

import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../../agent/AgentConfig'
import { Dispatcher } from '../../agent/Dispatcher'
import { MessageSender } from '../../agent/MessageSender'
import { createOutboundMessage } from '../../agent/helpers'
import { AriesFrameworkError } from '../../error'
import { ConnectionService } from '../connections/services/ConnectionService'

import { ProofResponseCoordinator } from './ProofResponseCoordinator'
import {
ProposePresentationHandler,
RequestPresentationHandler,
Expand All @@ -24,16 +26,22 @@ export class ProofsModule {
private proofService: ProofService
private connectionService: ConnectionService
private messageSender: MessageSender
private agentConfig: AgentConfig
private proofResponseCoordinator: ProofResponseCoordinator

public constructor(
dispatcher: Dispatcher,
proofService: ProofService,
connectionService: ConnectionService,
messageSender: MessageSender
agentConfig: AgentConfig,
messageSender: MessageSender,
proofResponseCoordinator: ProofResponseCoordinator
) {
this.proofService = proofService
this.connectionService = connectionService
this.messageSender = messageSender
this.agentConfig = agentConfig
this.proofResponseCoordinator = proofResponseCoordinator
this.registerHandlers(dispatcher)
}

Expand Down Expand Up @@ -270,9 +278,15 @@ export class ProofsModule {
}

private registerHandlers(dispatcher: Dispatcher) {
dispatcher.registerHandler(new ProposePresentationHandler(this.proofService))
dispatcher.registerHandler(new RequestPresentationHandler(this.proofService))
dispatcher.registerHandler(new PresentationHandler(this.proofService))
dispatcher.registerHandler(
new ProposePresentationHandler(this.proofService, this.agentConfig, this.proofResponseCoordinator)
)
dispatcher.registerHandler(
new RequestPresentationHandler(this.proofService, this.agentConfig, this.proofResponseCoordinator)
)
dispatcher.registerHandler(
new PresentationHandler(this.proofService, this.agentConfig, this.proofResponseCoordinator)
)
dispatcher.registerHandler(new PresentationAckHandler(this.proofService))
}
}
35 changes: 33 additions & 2 deletions packages/core/src/modules/proofs/handlers/PresentationHandler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
import type { AgentConfig } from '../../../agent/AgentConfig'
import type { Handler, HandlerInboundMessage } from '../../../agent/Handler'
import type { ProofResponseCoordinator } from '../ProofResponseCoordinator'
import type { ProofRecord } from '../repository'
import type { ProofService } from '../services'

import { createOutboundMessage } from '../../../agent/helpers'
import { PresentationMessage } from '../messages'

export class PresentationHandler implements Handler {
private proofService: ProofService
private agentConfig: AgentConfig
private proofResponseCoordinator: ProofResponseCoordinator
public supportedMessages = [PresentationMessage]

public constructor(proofService: ProofService) {
public constructor(
proofService: ProofService,
agentConfig: AgentConfig,
proofResponseCoordinator: ProofResponseCoordinator
) {
this.proofService = proofService
this.agentConfig = agentConfig
this.proofResponseCoordinator = proofResponseCoordinator
}

public async handle(messageContext: HandlerInboundMessage<PresentationHandler>) {
await this.proofService.processPresentation(messageContext)
const proofRecord = await this.proofService.processPresentation(messageContext)

if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(proofRecord)) {
return await this.createAck(proofRecord, messageContext)
}
}

private async createAck(proofRecord: ProofRecord, messageContext: HandlerInboundMessage<PresentationHandler>) {
this.agentConfig.logger.info(
`Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}`
)

if (!messageContext.connection) {
this.agentConfig.logger.error('No connection on the messageContext')
return
}

const { message } = await this.proofService.createAck(proofRecord)

return createOutboundMessage(messageContext.connection, message)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,62 @@
import type { AgentConfig } from '../../../agent/AgentConfig'
import type { Handler, HandlerInboundMessage } from '../../../agent/Handler'
import type { ProofResponseCoordinator } from '../ProofResponseCoordinator'
import type { ProofRecord } from '../repository'
import type { ProofService } from '../services'

import { createOutboundMessage } from '../../../agent/helpers'
import { ProposePresentationMessage } from '../messages'

export class ProposePresentationHandler implements Handler {
private proofService: ProofService
private agentConfig: AgentConfig
private proofResponseCoordinator: ProofResponseCoordinator
public supportedMessages = [ProposePresentationMessage]

public constructor(proofService: ProofService) {
public constructor(
proofService: ProofService,
agentConfig: AgentConfig,
proofResponseCoordinator: ProofResponseCoordinator
) {
this.proofService = proofService
this.agentConfig = agentConfig
this.proofResponseCoordinator = proofResponseCoordinator
}

public async handle(messageContext: HandlerInboundMessage<ProposePresentationHandler>) {
await this.proofService.processProposal(messageContext)
const proofRecord = await this.proofService.processProposal(messageContext)

if (this.proofResponseCoordinator.shoudlAutoRespondToProposal(proofRecord)) {
return await this.createRequest(proofRecord, messageContext)
}
}

private async createRequest(
proofRecord: ProofRecord,
messageContext: HandlerInboundMessage<ProposePresentationHandler>
) {
this.agentConfig.logger.info(
`Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}`
)

if (!messageContext.connection) {
this.agentConfig.logger.error('No connection on the messageContext')
return
}
if (!proofRecord.proposalMessage) {
this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`)
return
}
const proofRequest = await this.proofService.createProofRequestFromProposal(
proofRecord.proposalMessage.presentationProposal,
{
name: 'proof-request',
version: '1.0',
}
)

const { message } = await this.proofService.createRequestAsResponse(proofRecord, proofRequest)

return createOutboundMessage(messageContext.connection, message)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
import type { AgentConfig } from '../../../agent/AgentConfig'
import type { Handler, HandlerInboundMessage } from '../../../agent/Handler'
import type { ProofResponseCoordinator } from '../ProofResponseCoordinator'
import type { ProofRecord } from '../repository'
import type { ProofService } from '../services'

import { createOutboundMessage } from '../../../agent/helpers'
import { RequestPresentationMessage } from '../messages'

export class RequestPresentationHandler implements Handler {
private proofService: ProofService
private agentConfig: AgentConfig
private proofResponseCoordinator: ProofResponseCoordinator
public supportedMessages = [RequestPresentationMessage]

public constructor(proofService: ProofService) {
public constructor(
proofService: ProofService,
agentConfig: AgentConfig,
proofResponseCoordinator: ProofResponseCoordinator
) {
this.proofService = proofService
this.agentConfig = agentConfig
this.proofResponseCoordinator = proofResponseCoordinator
}

public async handle(messageContext: HandlerInboundMessage<RequestPresentationHandler>) {
await this.proofService.processRequest(messageContext)
const proofRecord = await this.proofService.processRequest(messageContext)

if (this.proofResponseCoordinator.shouldAutoRespondToRequest(proofRecord)) {
return await this.createPresentation(proofRecord, messageContext)
}
}

private async createPresentation(
proofRecord: ProofRecord,
messageContext: HandlerInboundMessage<RequestPresentationHandler>
) {
const indyProofRequest = proofRecord.requestMessage?.indyProofRequest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the handler kind of Indy specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix this when we support multiple credential types.


this.agentConfig.logger.info(
`Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}`
)

if (!messageContext.connection) {
this.agentConfig.logger.error('No connection on the messageContext')
return
}

if (!indyProofRequest) {
return
}

const retrievedCredentials = await this.proofService.getRequestedCredentialsForProofRequest(
indyProofRequest,
proofRecord.proposalMessage?.presentationProposal
)

const requestedCredentials = this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials)

const { message } = await this.proofService.createPresentation(proofRecord, requestedCredentials)

return createOutboundMessage(messageContext.connection, message)
}
}
1 change: 1 addition & 0 deletions packages/core/src/modules/proofs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './ProofState'
export * from './repository'
export * from './ProofEvents'
export * from './ProofsModule'
export * from './ProofAutoAcceptType'
Loading