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: negotiation and auto accept credentials #336

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ce46e2e
Auto accept credential implemented for 'always'
berendsliedrecht Jun 15, 2021
dbd2713
Always accept now works on agentconfig aswell
berendsliedrecht Jun 23, 2021
bec392b
Attributes not changed works.
berendsliedrecht Jun 23, 2021
8ae9fcd
Issue credential handler now checks for changes
berendsliedrecht Jun 23, 2021
c916870
Added checkValuesMatch as a wrapper to assertValuesMatch
berendsliedrecht Jun 23, 2021
4d71aa5
Added singleAccept and improved testing
berendsliedrecht Jun 29, 2021
2a4149c
Merge branch 'main' into feature/auto-accept-credential
berendsliedrecht Jun 29, 2021
9636336
Removed attribute not changed state
berendsliedrecht Jun 29, 2021
9c8800d
refactored testing
berendsliedrecht Jun 29, 2021
5d88a0f
Refactored credential handlers
berendsliedrecht Jun 29, 2021
c728ea0
Added credentialDefinitionId to contentApproved
berendsliedrecht Jun 29, 2021
a36c41d
Resolved feedback
berendsliedrecht Jul 2, 2021
37777da
Split the AutoResponseHandler into each handler
berendsliedrecht Jul 2, 2021
06ac5a0
Added auto Accept credential to the issueCredential helper
berendsliedrecht Jul 5, 2021
8a0901e
added dependency injection and renamed class
berendsliedrecht Jul 5, 2021
8311a38
renamed to CredentialResponseCoordinator
berendsliedrecht Jul 5, 2021
0960bcb
fix: resolved feedback
berendsliedrecht Jul 7, 2021
d345ddb
fix: changed typing agentConfig
berendsliedrecht Jul 7, 2021
3c741e2
Merge remote-tracking branch 'origin/main' into feature/auto-accept-c…
berendsliedrecht Jul 13, 2021
c988e5b
fix: resolved feedback
berendsliedrecht Jul 14, 2021
d56c60f
Merged and resolved async issues
berendsliedrecht Jul 14, 2021
a33e32a
fixed proof testing
berendsliedrecht Jul 14, 2021
fcff94f
test: increased testTimeout due to heavy e2e tests
berendsliedrecht Jul 14, 2021
41d9880
test: fixed e2e test
berendsliedrecht Jul 14, 2021
19a9ed0
fix: spelling
berendsliedrecht Jul 15, 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
489 changes: 489 additions & 0 deletions src/__tests__/credentials-autoAccept.test.ts

Large diffs are not rendered by default.

62 changes: 59 additions & 3 deletions src/__tests__/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { Agent } from '../agent/Agent'
import type { BasicMessage, BasicMessageReceivedEvent } from '../modules/basic-messages'
import type { ConnectionRecordProps } from '../modules/connections'
import type { CredentialRecord, CredentialOfferTemplate, CredentialStateChangedEvent } from '../modules/credentials'
import type { SchemaTemplate, CredentialDefinitionTemplate } from '../modules/ledger'
import type { ProofRecord, ProofState, ProofStateChangedEvent } from '../modules/proofs'
import type { InboundTransporter, OutboundTransporter } from '../transport'
import type { InitConfig, OutboundPackage, WireMessage } from '../types'
import type { AutoAcceptCredential, InitConfig, OutboundPackage, WireMessage } from '../types'
import type { Schema, CredDef, Did } from 'indy-sdk'
import type { Subject } from 'rxjs'

import indy from 'indy-sdk'
import path from 'path'
import { Subject } from 'rxjs'

import { Agent } from '../agent/Agent'
import { BasicMessageEventTypes } from '../modules/basic-messages'
import {
ConnectionInvitationMessage,
Expand Down Expand Up @@ -324,3 +324,59 @@ export async function issueCredential({
export function mockFunction<T extends (...args: any[]) => any>(fn: T): jest.MockedFunction<T> {
return fn as jest.MockedFunction<T>
}

export async function setupCredentialTests(
faberName: string,
aliceName: string,
autoAcceptCredentials?: AutoAcceptCredential
) {
const faberMessages = new Subject()
const aliceMessages = new Subject()

const faberConfig = getBaseConfig(faberName, {
genesisPath,
autoAcceptCredentials,
})

const aliceConfig = getBaseConfig(aliceName, {
genesisPath,
autoAcceptCredentials,
})

const faberAgent = new Agent(faberConfig)
faberAgent.setInboundTransporter(new SubjectInboundTransporter(faberMessages, aliceMessages))
faberAgent.setOutboundTransporter(new SubjectOutboundTransporter(aliceMessages))
await faberAgent.init()

const aliceAgent = new Agent(aliceConfig)
aliceAgent.setInboundTransporter(new SubjectInboundTransporter(aliceMessages, faberMessages))
aliceAgent.setOutboundTransporter(new SubjectOutboundTransporter(faberMessages))
await aliceAgent.init()

const schemaTemplate = {
name: `test-schema-${Date.now()}`,
attributes: ['name', 'age', 'lastname'],
version: '1.0',
}
const schema = await registerSchema(faberAgent, schemaTemplate)
const schemaId = schema.id

const definitionTemplate = {
schema,
tag: 'TAG',
signatureType: 'CL' as const,
supportRevocation: false,
}
const credentialDefinition = await registerDefinition(faberAgent, definitionTemplate)
const credDefId = credentialDefinition.id

const publicDid = faberAgent.publicDid?.did

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await ensurePublicDidIsOnLedger(faberAgent, publicDid!)
const { agentAConnection, agentBConnection } = await makeConnection(faberAgent, aliceAgent)
const faberConnection = agentAConnection
const aliceConnection = agentBConnection

return { faberAgent, aliceAgent, credDefId, schemaId, faberConnection, aliceConnection }
}
6 changes: 5 additions & 1 deletion src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { InitConfig, InboundConnection } from '../types'

import { DID_COMM_TRANSPORT_QUEUE } from '../constants'
import { ConsoleLogger, LogLevel } from '../logger'
import { DidCommMimeType } from '../types'
import { AutoAcceptCredential, DidCommMimeType } from '../types'

export class AgentConfig {
private initConfig: InitConfig
Expand Down Expand Up @@ -63,6 +63,10 @@ export class AgentConfig {
return this.initConfig.autoAcceptConnections ?? false
}

public get autoAcceptCredentials() {
return this.initConfig.autoAcceptCredentials ?? AutoAcceptCredential.Never
}

public get didCommMimeType() {
return this.initConfig.didCommMimeType ?? DidCommMimeType.V0
}
Expand Down
15 changes: 15 additions & 0 deletions src/modules/credentials/CredentialUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ export class CredentialUtils {
}, {})
}

/**
* Check whether the values of two credentials match (using {@link assertValuesMatch})
*
* @returns a boolean whether the values are equal
*
*/
public static checkValuesMatch(firstValues: CredValues, secondValues: CredValues): boolean {
try {
this.assertValuesMatch(firstValues, secondValues)
return true
} catch {
return false
}
}

/**
* Assert two credential values objects match.
*
Expand Down
115 changes: 104 additions & 11 deletions src/modules/credentials/CredentialsModule.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { ProposeCredentialMessageOptions } from './messages'
import type { AutoAcceptCredential } from '../../types'
import type { CredentialPreview } from './messages'
import type { CredentialRecord } from './repository/CredentialRecord'
import type { CredentialOfferTemplate } from './services'
import type { CredentialOfferTemplate, CredentialProposeOptions } from './services'

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'
import { ConnectionService } from '../connections/services/ConnectionService'

import {
ProposeCredentialHandler,
Expand All @@ -24,16 +26,19 @@ export class CredentialsModule {
private connectionService: ConnectionService
private credentialService: CredentialService
private messageSender: MessageSender
private agentConfig: AgentConfig

public constructor(
dispatcher: Dispatcher,
connectionService: ConnectionService,
credentialService: CredentialService,
messageSender: MessageSender
messageSender: MessageSender,
agentConfig: AgentConfig
) {
this.connectionService = connectionService
this.credentialService = credentialService
this.messageSender = messageSender
this.agentConfig = agentConfig
this.registerHandlers(dispatcher)
}

Expand All @@ -45,7 +50,7 @@ export class CredentialsModule {
* @param config Additional configuration to use for the proposal
* @returns Credential record associated with the sent proposal message
*/
public async proposeCredential(connectionId: string, config?: Omit<ProposeCredentialMessageOptions, 'id'>) {
public async proposeCredential(connectionId: string, config?: CredentialProposeOptions) {
const connection = await this.connectionService.getById(connectionId)

const { message, credentialRecord } = await this.credentialService.createProposal(connection, config)
Expand All @@ -70,6 +75,7 @@ export class CredentialsModule {
config?: {
comment?: string
credentialDefinitionId?: string
autoAcceptCredential?: AutoAcceptCredential
}
) {
const credentialRecord = await this.credentialService.getById(credentialRecordId)
Expand All @@ -96,6 +102,58 @@ export class CredentialsModule {
preview: credentialProposalMessage.credentialProposal,
credentialDefinitionId,
comment: config?.comment,
autoAcceptCredential: config?.autoAcceptCredential,
})

const outboundMessage = createOutboundMessage(connection, message)
await this.messageSender.sendMessage(outboundMessage)

return credentialRecord
}

/**
* Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection
* associated with the credential record.
*
* @param credentialRecordId The id of the credential record for which to accept the proposal
* @param preview The new preview for negotiation
* @param config Additional configuration to use for the offer
* @returns Credential record associated with the credential offer
*
*/
public async negotiateProposal(
credentialRecordId: string,
preview: CredentialPreview,
config?: {
comment?: string
credentialDefinitionId?: string
autoAcceptCredential?: AutoAcceptCredential
}
) {
const credentialRecord = await this.credentialService.getById(credentialRecordId)
const connection = await this.connectionService.getById(credentialRecord.connectionId)

const credentialProposalMessage = credentialRecord.proposalMessage

if (!credentialProposalMessage?.credentialProposal) {
throw new AriesFrameworkError(
`Credential record with id ${credentialRecordId} is missing required credential proposal`
)
}

const credentialDefinitionId = config?.credentialDefinitionId ?? credentialProposalMessage.credentialDefinitionId

if (!credentialDefinitionId) {
throw new AriesFrameworkError(
'Missing required credential definition id. If credential proposal message contains no credential definition id it must be passed to config.'
)
}

const { message } = await this.credentialService.createOfferAsResponse(credentialRecord, {
preview,
credentialDefinitionId,
comment: config?.comment,
autoAcceptCredential: config?.autoAcceptCredential,
})

const outboundMessage = createOutboundMessage(connection, message)
Expand Down Expand Up @@ -135,7 +193,10 @@ export class CredentialsModule {
* @returns Credential record associated with the sent credential request message
*
*/
public async acceptOffer(credentialRecordId: string, config?: { comment?: string }) {
public async acceptOffer(
credentialRecordId: string,
config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential }
) {
const credentialRecord = await this.credentialService.getById(credentialRecordId)
const connection = await this.connectionService.getById(credentialRecord.connectionId)

Expand All @@ -147,6 +208,35 @@ export class CredentialsModule {
return credentialRecord
}

/**
* Negotiate a credential offer as holder (by sending a credential proposal message) to the connection
* associated with the credential record.
*
* @param credentialRecordId The id of the credential record for which to accept the offer
* @param preview The new preview for negotiation
* @param config Additional configuration to use for the request
* @returns Credential record associated with the sent credential request message
*
*/
public async negotiateOffer(
credentialRecordId: string,
preview: CredentialPreview,
config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential }
) {
const credentialRecord = await this.credentialService.getById(credentialRecordId)
const connection = await this.connectionService.getById(credentialRecord.connectionId)

const { message } = await this.credentialService.createProposalAsResponse(credentialRecord, {
...config,
credentialProposal: preview,
})

const outboundMessage = createOutboundMessage(connection, message)
await this.messageSender.sendMessage(outboundMessage)

return credentialRecord
}

/**
* Accept a credential request as issuer (by sending a credential message) to the connection
* associated with the credential record.
Expand All @@ -156,7 +246,10 @@ export class CredentialsModule {
* @returns Credential record associated with the sent presentation message
*
*/
public async acceptRequest(credentialRecordId: string, config?: { comment?: string }) {
public async acceptRequest(
credentialRecordId: string,
config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential }
) {
const credentialRecord = await this.credentialService.getById(credentialRecordId)
const connection = await this.connectionService.getById(credentialRecord.connectionId)

Expand Down Expand Up @@ -231,10 +324,10 @@ export class CredentialsModule {
}

private registerHandlers(dispatcher: Dispatcher) {
dispatcher.registerHandler(new ProposeCredentialHandler(this.credentialService))
dispatcher.registerHandler(new OfferCredentialHandler(this.credentialService))
dispatcher.registerHandler(new RequestCredentialHandler(this.credentialService))
dispatcher.registerHandler(new IssueCredentialHandler(this.credentialService))
dispatcher.registerHandler(new ProposeCredentialHandler(this.credentialService, this.agentConfig))
dispatcher.registerHandler(new OfferCredentialHandler(this.credentialService, this.agentConfig))
dispatcher.registerHandler(new RequestCredentialHandler(this.credentialService, this.agentConfig))
dispatcher.registerHandler(new IssueCredentialHandler(this.credentialService, this.agentConfig))
dispatcher.registerHandler(new CredentialAckHandler(this.credentialService))
}
}
Loading