Skip to content

Commit

Permalink
fix: propose payload attachment in in snake_case JSON format (#775)
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Richardson <[email protected]>
  • Loading branch information
NB-MikeRichardson authored May 18, 2022
1 parent e617496 commit 6c2dfdb
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export abstract class CredentialFormatService {
this.eventEmitter = eventEmitter
}

abstract createProposal(options: ProposeCredentialOptions): FormatServiceProposeAttachmentFormats
abstract createProposal(options: ProposeCredentialOptions): Promise<FormatServiceProposeAttachmentFormats>

abstract processProposal(
options: ServiceAcceptProposalOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type {
} from '../../protocol'
import type { V1CredentialPreview } from '../../protocol/v1/V1CredentialPreview'
import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord'
import type { CredPropose } from '../models/CredPropose'
import type {
FormatServiceCredentialAttachmentFormats,
CredentialFormatSpec,
Expand All @@ -34,6 +33,7 @@ import { Lifecycle, scoped } from 'tsyringe'
import { AgentConfig } from '../../../../agent/AgentConfig'
import { EventEmitter } from '../../../../agent/EventEmitter'
import { AriesFrameworkError } from '../../../../error'
import { JsonTransformer } from '../../../../utils/JsonTransformer'
import { MessageValidator } from '../../../../utils/MessageValidator'
import { uuid } from '../../../../utils/uuid'
import { IndyHolderService, IndyIssuerService } from '../../../indy'
Expand All @@ -47,6 +47,7 @@ import { V2CredentialPreview } from '../../protocol/v2/V2CredentialPreview'
import { CredentialMetadataKeys } from '../../repository/CredentialMetadataTypes'
import { CredentialRepository } from '../../repository/CredentialRepository'
import { CredentialFormatService } from '../CredentialFormatService'
import { CredPropose } from '../models/CredPropose'

@scoped(Lifecycle.ContainerScoped)
export class IndyCredentialFormatService extends CredentialFormatService {
Expand Down Expand Up @@ -80,7 +81,7 @@ export class IndyCredentialFormatService extends CredentialFormatService {
* @returns object containing associated attachment, formats and filtersAttach elements
*
*/
public createProposal(options: ProposeCredentialOptions): FormatServiceProposeAttachmentFormats {
public async createProposal(options: ProposeCredentialOptions): Promise<FormatServiceProposeAttachmentFormats> {
const formats: CredentialFormatSpec = {
attachId: this.generateId(),
format: 'hlindy/[email protected]',
Expand All @@ -89,7 +90,19 @@ export class IndyCredentialFormatService extends CredentialFormatService {
throw new AriesFrameworkError('Missing payload in createProposal')
}

const attachment: Attachment = this.getFormatData(options.credentialFormats.indy?.payload, formats.attachId)
// Use class instance instead of interface, otherwise this causes interoperability problems
let proposal = new CredPropose(options.credentialFormats.indy?.payload)

try {
await MessageValidator.validate(proposal)
} catch (error) {
throw new AriesFrameworkError(`Invalid credPropose class instance: ${proposal} in Indy Format Service`)
}

proposal = JsonTransformer.toJSON(proposal)

const attachment = this.getFormatData(proposal, formats.attachId)

const { previewWithAttachments } = this.getCredentialLinkedAttachments(options)

return { format: formats, attachment, preview: previewWithAttachments }
Expand All @@ -99,7 +112,9 @@ export class IndyCredentialFormatService extends CredentialFormatService {
options: ServiceAcceptProposalOptions,
credentialRecord: CredentialExchangeRecord
): Promise<void> {
const credPropose = options.proposalAttachment?.getDataAsJson<CredPropose>()
let credPropose = options.proposalAttachment?.getDataAsJson<CredPropose>()
credPropose = JsonTransformer.fromJSON(credPropose, CredPropose)

if (!credPropose) {
throw new AriesFrameworkError('Missing indy credential proposal data payload')
}
Expand Down Expand Up @@ -246,17 +261,20 @@ export class IndyCredentialFormatService extends CredentialFormatService {
})
}

if (!options.credentialFormats.indy.attributes) {
throw new AriesFrameworkError('Missing attributes from credential proposal')
}

if (options.credentialFormats.indy && options.credentialFormats.indy.linkedAttachments) {
// there are linked attachments so transform into the attribute field of the CredentialPreview object for
// this proposal
if (options.credentialFormats.indy.attributes) {
previewWithAttachments = CredentialUtils.createAndLinkAttachmentsToPreview(
options.credentialFormats.indy.linkedAttachments,
new V2CredentialPreview({
attributes: options.credentialFormats.indy.attributes,
})
)
}
previewWithAttachments = CredentialUtils.createAndLinkAttachmentsToPreview(
options.credentialFormats.indy.linkedAttachments,
new V2CredentialPreview({
attributes: options.credentialFormats.indy.attributes,
})
)

attachments = options.credentialFormats.indy.linkedAttachments.map(
(linkedAttachment) => linkedAttachment.attachment
)
Expand Down Expand Up @@ -364,7 +382,7 @@ export class IndyCredentialFormatService extends CredentialFormatService {
}

const attachmentId = options.attachId ? options.attachId : formats.attachId
const issueAttachment: Attachment = this.getFormatData(credential, attachmentId)
const issueAttachment = this.getFormatData(credential, attachmentId)
return { format: formats, attachment: issueAttachment }
}
/**
Expand Down Expand Up @@ -502,11 +520,11 @@ export class IndyCredentialFormatService extends CredentialFormatService {

private areProposalAndOfferDefinitionIdEqual(proposalAttachment?: Attachment, offerAttachment?: Attachment) {
const credOffer = offerAttachment?.getDataAsJson<CredOffer>()
const credPropose = proposalAttachment?.getDataAsJson<CredPropose>()
let credPropose = proposalAttachment?.getDataAsJson<CredPropose>()
credPropose = JsonTransformer.fromJSON(credPropose, CredPropose)

const proposalCredentialDefinitionId = credPropose?.credentialDefinitionId
const offerCredentialDefinitionId = credOffer?.cred_def_id

return proposalCredentialDefinitionId === offerCredentialDefinitionId
}

Expand Down Expand Up @@ -561,7 +579,8 @@ export class IndyCredentialFormatService extends CredentialFormatService {
proposeAttachment?: Attachment
) {
const indyCredentialRequest = requestAttachment?.getDataAsJson<CredReq>()
const indyCredentialProposal = proposeAttachment?.getDataAsJson<CredPropose>()
let indyCredentialProposal = proposeAttachment?.getDataAsJson<CredPropose>()
indyCredentialProposal = JsonTransformer.fromJSON(indyCredentialProposal, CredPropose)

const indyCredentialOffer = offerAttachment?.getDataAsJson<CredOffer>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class V1CredentialService extends CredentialService {

const options = { ...config }

const { attachment: filtersAttach } = this.formatService.createProposal(proposal)
const { attachment: filtersAttach } = await this.formatService.createProposal(proposal)

if (!filtersAttach) {
throw new AriesFrameworkError('Missing filters attach in Proposal')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export class CredentialMessageBuilder {
* @param _threadId optional thread id for this message service
* @return a version 2.0 credential propose message see {@link V2ProposeCredentialMessage}
*/
public createProposal(
public async createProposal(
formatServices: CredentialFormatService[],
proposal: ProposeCredentialOptions
): CredentialProtocolMsgReturnType<V2ProposeCredentialMessage> {
): Promise<CredentialProtocolMsgReturnType<V2ProposeCredentialMessage>> {
if (formatServices.length === 0) {
throw new AriesFrameworkError('no format services provided to createProposal')
}
Expand All @@ -58,7 +58,7 @@ export class CredentialMessageBuilder {
const filtersAttachArray: Attachment[] | undefined = []
let previewAttachments: V2CredentialPreview | undefined
for (const formatService of formatServices) {
const { format: formats, attachment, preview } = formatService.createProposal(proposal)
const { format: formats, attachment, preview } = await formatService.createProposal(proposal)
if (attachment) {
filtersAttachArray.push(attachment)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class V2CredentialService extends CredentialService {
if (!formats || formats.length === 0) {
throw new AriesFrameworkError(`Unable to create proposal. No supported formats`)
}
const { message: proposalMessage, credentialRecord } = this.credentialMessageBuilder.createProposal(
const { message: proposalMessage, credentialRecord } = await this.credentialMessageBuilder.createProposal(
formats,
proposal
)
Expand Down Expand Up @@ -316,7 +316,7 @@ export class V2CredentialService extends CredentialService {
if (!formats || formats.length === 0) {
throw new AriesFrameworkError(`Unable to negotiate offer. No supported formats`)
}
const { message: credentialProposalMessage } = this.credentialMessageBuilder.createProposal(formats, options)
const { message: credentialProposalMessage } = await this.credentialMessageBuilder.createProposal(formats, options)
credentialProposalMessage.setThread({ threadId: credentialRecord.threadId })

// Update record
Expand Down Expand Up @@ -1078,7 +1078,6 @@ export class V2CredentialService extends CredentialService {
*/
public getFormatsFromMessage(messageFormats: CredentialFormatSpec[]): CredentialFormatService[] {
const formats: CredentialFormatService[] = []

for (const msg of messageFormats) {
if (msg.format.includes('indy')) {
formats.push(this.getFormatService(CredentialFormatType.Indy))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,27 +81,27 @@ describe('V2 Credential Architecture', () => {
expect(type).toEqual('IndyCredentialFormatService')
})

test('propose credential format service returns correct format and filters~attach', () => {
test('propose credential format service returns correct format and filters~attach', async () => {
const version: CredentialProtocolVersion = CredentialProtocolVersion.V2
const service: CredentialService = api.getService(version)
const formatService: CredentialFormatService = service.getFormatService(CredentialFormatType.Indy)
const { format: formats, attachment: filtersAttach } = formatService.createProposal(proposal)
const { format: formats, attachment: filtersAttach } = await formatService.createProposal(proposal)

expect(formats.attachId.length).toBeGreaterThan(0)
expect(formats.format).toEqual('hlindy/[email protected]')
expect(filtersAttach).toBeTruthy()
})
test('propose credential format service transforms and validates CredPropose payload correctly', () => {
test('propose credential format service transforms and validates CredPropose payload correctly', async () => {
const version: CredentialProtocolVersion = CredentialProtocolVersion.V2
const service: CredentialService = api.getService(version)
const formatService: CredentialFormatService = service.getFormatService(CredentialFormatType.Indy)
const { format: formats, attachment: filtersAttach } = formatService.createProposal(proposal)
const { format: formats, attachment: filtersAttach } = await formatService.createProposal(proposal)

expect(formats.attachId.length).toBeGreaterThan(0)
expect(formats.format).toEqual('hlindy/[email protected]')
expect(filtersAttach).toBeTruthy()
})
test('propose credential format service creates message with multiple formats', () => {
test('propose credential format service creates message with multiple formats', async () => {
const version: CredentialProtocolVersion = CredentialProtocolVersion.V2
const service: CredentialService = api.getService(version)

Expand All @@ -111,7 +111,7 @@ describe('V2 Credential Architecture', () => {
expect(formats.length).toBe(1) // for now will be added to with jsonld
const messageBuilder: CredentialMessageBuilder = new CredentialMessageBuilder()

const v2Proposal = messageBuilder.createProposal(formats, multiFormatProposal)
const v2Proposal = await messageBuilder.createProposal(formats, multiFormatProposal)

expect(v2Proposal.message.formats.length).toBe(1)
expect(v2Proposal.message.formats[0].format).toEqual('hlindy/[email protected]')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ describe('credentials', () => {

const testAttributes = {
attributes: credentialPreview.attributes,
credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag',
payload: {
schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
schemaName: 'ahoy',
schemaVersion: '1.0',
schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0',
issuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag',
},
}
testLogger.test('Alice sends (v1) credential proposal to Faber')
Expand Down Expand Up @@ -227,18 +227,13 @@ describe('credentials', () => {
})
const testAttributes = {
attributes: credentialPreview.attributes,
schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
schemaName: 'ahoy',
schemaVersion: '1.0',
schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0',
issuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag',
payload: {
schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
schemaName: 'ahoy',
schemaVersion: '1.0',
schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0',
issuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag',
},
}
testLogger.test('Alice sends (v2) credential proposal to Faber')
Expand Down Expand Up @@ -382,6 +377,36 @@ describe('credentials', () => {
}
})

test('Ensure missing attributes are caught if absent from in V2 (Indy) Proposal Message', async () => {
// Note missing attributes...
const testAttributes = {
payload: {
schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
schemaName: 'ahoy',
schemaVersion: '1.0',
schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0',
issuerDid: 'GMm4vMw8LLrLJjp81kRRLp',
credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag',
},
}
testLogger.test('Alice sends (v2) credential proposal to Faber')
// set the propose options
// we should set the version to V1.0 and V2.0 in separate tests, one as a regression test
const proposeOptions: ProposeCredentialOptions = {
connectionId: aliceConnection.id,
protocolVersion: CredentialProtocolVersion.V2,
credentialFormats: {
indy: testAttributes,
},
comment: 'v2 propose credential test',
}
testLogger.test('Alice sends (v2, Indy) credential proposal to Faber')

await expect(aliceAgent.credentials.proposeCredential(proposeOptions)).rejects.toThrow(
'Missing attributes from credential proposal'
)
})

test('Faber Issues Credential which is then deleted from Alice`s wallet', async () => {
const credentialPreview = V2CredentialPreview.fromRecord({
name: 'John',
Expand Down

0 comments on commit 6c2dfdb

Please sign in to comment.