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

fix/SPRIND-91 #268

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 51 additions & 34 deletions packages/oid4vci-holder/src/agent/OID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ import {
IBasicIssuerLocaleBranding,
Identity,
IdentityOrigin,
IIssuerBranding,
IIssuerLocaleBranding,
NonPersistedIdentity,
Party,
Party
} from '@sphereon/ssi-sdk.data-store'
import {
CredentialMapper,
Expand Down Expand Up @@ -72,7 +72,7 @@ import { v4 as uuidv4 } from 'uuid'
import { OID4VCIMachine } from '../machine/oid4vciMachine'
import {
AddContactIdentityArgs,
AddIssuerBrandingArgs,
StoreIssuerBrandingArgs,
AssertValidCredentialsArgs,
Attribute,
createCredentialsToSelectFromArgs,
Expand Down Expand Up @@ -102,7 +102,8 @@ import {
StoreCredentialsArgs,
VerificationResult,
VerifyEBSICredentialIssuerArgs,
VerifyEBSICredentialIssuerResult
VerifyEBSICredentialIssuerResult,
GetIssuerBrandingArgs
} from '../types/IOID4VCIHolder'
import {
getBasicIssuerLocaleBranding,
Expand Down Expand Up @@ -216,6 +217,8 @@ export class OID4VCIHolder implements IAgentPlugin {
oid4vciHolderStoreCredentialBranding: this.oid4vciHolderStoreCredentialBranding.bind(this),
oid4vciHolderStoreCredentials: this.oid4vciHolderStoreCredentials.bind(this),
oid4vciHolderSendNotification: this.oid4vciHolderSendNotification.bind(this),
oid4vciHolderGetIssuerBranding: this.oid4vciHolderGetIssuerBranding.bind(this),
oid4vciHolderStoreIssuerBranding: this.oid4vciHolderStoreIssuerBranding.bind(this),
}

private readonly vcFormatPreferences: Array<string> = ['vc+sd-jwt', 'mso_mdoc', 'jwt_vc_json', 'jwt_vc', 'ldp_vc']
Expand Down Expand Up @@ -312,10 +315,10 @@ export class OID4VCIHolder implements IAgentPlugin {
),
createCredentialsToSelectFrom: (args: createCredentialsToSelectFromArgs) => this.oid4vciHoldercreateCredentialsToSelectFrom(args, context),
getContact: (args: GetContactArgs) => this.oid4vciHolderGetContact(args, context),
getCredentials: (args: GetCredentialsArgs) =>
this.oid4vciHolderGetCredentials({ accessTokenOpts: args.accessTokenOpts ?? opts.accessTokenOpts, ...args }, context),
getCredentials: (args: GetCredentialsArgs) => this.oid4vciHolderGetCredentials({ accessTokenOpts: args.accessTokenOpts ?? opts.accessTokenOpts, ...args }, context),
addContactIdentity: (args: AddContactIdentityArgs) => this.oid4vciHolderAddContactIdentity(args, context),
addIssuerBranding: (args: AddIssuerBrandingArgs) => this.oid4vciHolderAddIssuerBranding(args, context),
getIssuerBranding: (args: GetIssuerBrandingArgs) => this.oid4vciHolderGetIssuerBranding(args, context),
storeIssuerBranding: (args: StoreIssuerBrandingArgs) => this.oid4vciHolderStoreIssuerBranding(args, context),
assertValidCredentials: (args: AssertValidCredentialsArgs) => this.oid4vciHolderAssertValidCredentials(args, context),
storeCredentialBranding: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context),
storeCredentials: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context),
Expand Down Expand Up @@ -455,10 +458,7 @@ export class OID4VCIHolder implements IAgentPlugin {
}
}

private async oid4vciHoldercreateCredentialsToSelectFrom(
args: createCredentialsToSelectFromArgs,
context: RequiredContext,
): Promise<Array<CredentialToSelectFromResult>> {
private async oid4vciHoldercreateCredentialsToSelectFrom(args: createCredentialsToSelectFromArgs, context: RequiredContext): Promise<Array<CredentialToSelectFromResult>> {
const { credentialBranding, locale, selectedCredentials /*, openID4VCIClientState*/, credentialsSupported } = args

// const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState! }) // TODO see if we need the check openID4VCIClientState defined
Expand Down Expand Up @@ -730,33 +730,50 @@ export class OID4VCIHolder implements IAgentPlugin {
return context.agent.cmAddIdentity({ contactId: contact.id, identity })
}

private async oid4vciHolderAddIssuerBranding(args: AddIssuerBrandingArgs, context: RequiredContext): Promise<void> {
private async oid4vciHolderGetIssuerBranding(args: GetIssuerBrandingArgs, context: RequiredContext): Promise<Array<IIssuerLocaleBranding | IBasicIssuerLocaleBranding>> {
const { serverMetadata, contact } = args
if (!contact) {
return logger.warning('Missing contact in context, so cannot get issuer branding')
}

if (serverMetadata?.credentialIssuerMetadata?.display) {
const issuerCorrelationId: string =
contact.identities
.filter((identity) => identity.roles.includes(CredentialRole.ISSUER))
.map((identity) => identity.identifier.correlationId)[0] ?? undefined

const brandings: IIssuerBranding[] = await context.agent.ibGetIssuerBranding({ filter: [{ issuerCorrelationId }] })
// todo: Probably wise to look at last updated at and update in case it has been a while
if (!brandings || brandings.length === 0) {
const basicIssuerLocaleBrandings: IBasicIssuerLocaleBranding[] = await getBasicIssuerLocaleBranding({
display: serverMetadata.credentialIssuerMetadata.display,
context,
})
if (basicIssuerLocaleBrandings && basicIssuerLocaleBrandings.length > 0) {
await context.agent.ibAddIssuerBranding({
localeBranding: basicIssuerLocaleBrandings,
issuerCorrelationId,
})
}
// Here we are fetching issuer branding for a contact. If no contact is found that means we encounter this contact for the first time. This also means we do not have any branding for the contact.
const issuerCorrelationId = contact?.identities
.filter((identity) => identity.roles.includes(CredentialRole.ISSUER))
.map((identity) => identity.identifier.correlationId)[0]

if (issuerCorrelationId) {
const branding = await context.agent.ibGetIssuerBranding({ filter: [{ issuerCorrelationId }] })
if (branding.length > 0) {
return branding[0].localeBranding
}
}

// We should have serverMetadata in the context else something went wrong
if (!serverMetadata) {
return Promise.reject(Error('Missing serverMetadata in context'));
}

return getBasicIssuerLocaleBranding({
display: serverMetadata.credentialIssuerMetadata?.display ?? [],
context,
})
}

private async oid4vciHolderStoreIssuerBranding(args: StoreIssuerBrandingArgs, context: RequiredContext): Promise<void> {
const { issuerBranding, contact } = args
if (!issuerBranding || issuerBranding.length === 0 || (<Array<IIssuerLocaleBranding>>issuerBranding)[0].id) { // FIXME we need better separation between a contact(issuer) we encountered before and it's branding vs a new contact and it's branding
return
}

if (!contact) {
return Promise.reject(Error('Missing contact in context'));
}

const issuerCorrelationId = contact?.identities
.filter((identity) => identity.roles.includes(CredentialRole.ISSUER))
.map((identity) => identity.identifier.correlationId)[0]

await context.agent.ibAddIssuerBranding({
localeBranding: issuerBranding as Array<IBasicIssuerLocaleBranding>,
issuerCorrelationId,
})
}

private async oid4vciHolderAssertValidCredentials(args: AssertValidCredentialsArgs, context: RequiredContext): Promise<VerificationResult[]> {
Expand Down
4 changes: 2 additions & 2 deletions packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
GetIssuanceCryptoSuiteArgs,
GetIssuanceDidMethodArgs,
GetIssuanceOptsArgs,
GetIssuerBrandingArgs,
GetBasicIssuerLocaleBrandingArgs,
GetPreferredCredentialFormatsArgs,
IssuanceOpts,
MapCredentialToAcceptArgs,
Expand Down Expand Up @@ -81,7 +81,7 @@ export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Pr
return credentialBranding
}

export const getBasicIssuerLocaleBranding = async (args: GetIssuerBrandingArgs): Promise<Array<IBasicIssuerLocaleBranding>> => {
export const getBasicIssuerLocaleBranding = async (args: GetBasicIssuerLocaleBrandingArgs): Promise<Array<IBasicIssuerLocaleBranding>> => { //IBasicIssuerLocaleBranding
const { display, context } = args
return await Promise.all(
display.map(async (displayItem: MetadataDisplay): Promise<IBasicIssuerLocaleBranding> => {
Expand Down
36 changes: 28 additions & 8 deletions packages/oid4vci-holder/src/machine/oid4vciMachine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { AuthzFlowType, toAuthorizationResponsePayload } from '@sphereon/oid4vci-common'
import { Identity, Party } from '@sphereon/ssi-sdk.data-store'
import {
IBasicIssuerLocaleBranding,
Identity,
IIssuerLocaleBranding,
Party
} from '@sphereon/ssi-sdk.data-store'
import { assign, createMachine, DoneInvokeEvent, interpret } from 'xstate'
import { translate } from '../localization/Localization'
import {
Expand Down Expand Up @@ -158,7 +163,7 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
[OID4VCIMachineServices.getContact]: {
data: Party | undefined
}
[OID4VCIMachineServices.addIssuerBranding]: {
[OID4VCIMachineServices.storeIssuerBranding]: {
data: void
}
[OID4VCIMachineServices.getCredentials]: {
Expand All @@ -178,6 +183,9 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
}
[OID4VCIMachineServices.getFederationTrust]: {
data: Array<string>
},
[OID4VCIMachineServices.getIssuerBranding]: {
data: Array<IIssuerLocaleBranding | IBasicIssuerLocaleBranding>
}
},
},
Expand Down Expand Up @@ -238,7 +246,7 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
invoke: {
src: OID4VCIMachineServices.getContact,
onDone: {
target: OID4VCIMachineStates.transitionFromSetup,
target: OID4VCIMachineStates.getIssuerBranding,
actions: assign({ contact: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent<Party>) => _event.data }),
},
onError: {
Expand All @@ -253,6 +261,18 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
},
},
},
[OID4VCIMachineStates.getIssuerBranding]: {
id: OID4VCIMachineStates.getIssuerBranding,
invoke: {
src: OID4VCIMachineServices.getIssuerBranding,
onDone: {
target: OID4VCIMachineStates.transitionFromSetup,
actions: assign({
issuerBranding: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent<Array<IIssuerLocaleBranding | IBasicIssuerLocaleBranding>>) => _event.data
})
},
},
},
[OID4VCIMachineStates.transitionFromSetup]: {
id: OID4VCIMachineStates.transitionFromSetup,
always: [
Expand Down Expand Up @@ -334,16 +354,16 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
[OID4VCIMachineAddContactStates.idle]: {},
[OID4VCIMachineAddContactStates.next]: {
always: {
target: `#${OID4VCIMachineStates.addIssuerBranding}`,
target: `#${OID4VCIMachineStates.storeIssuerBranding}`,
cond: OID4VCIMachineGuards.hasContactGuard,
},
},
},
},
[OID4VCIMachineStates.addIssuerBranding]: {
id: OID4VCIMachineStates.addIssuerBranding,
[OID4VCIMachineStates.storeIssuerBranding]: {
id: OID4VCIMachineStates.storeIssuerBranding,
invoke: {
src: OID4VCIMachineServices.addIssuerBranding,
src: OID4VCIMachineServices.storeIssuerBranding,
onDone: {
target: OID4VCIMachineStates.transitionFromContactSetup,
},
Expand Down Expand Up @@ -540,7 +560,7 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach
[OID4VCIMachineStates.addIssuerBrandingAfterIdentity]: {
id: OID4VCIMachineStates.addIssuerBrandingAfterIdentity,
invoke: {
src: OID4VCIMachineServices.addIssuerBranding,
src: OID4VCIMachineServices.storeIssuerBranding,
onDone: {
target: OID4VCIMachineStates.reviewCredentials,
},
Expand Down
33 changes: 26 additions & 7 deletions packages/oid4vci-holder/src/types/IOID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
MetadataDisplay,
NotificationRequest,
} from '@sphereon/oid4vci-common'
import { CreateOrGetIdentifierOpts, IdentifierProviderOpts, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
import {
CreateOrGetIdentifierOpts,
IdentifierProviderOpts,
SupportedDidMethodEnum
} from '@sphereon/ssi-sdk-ext.did-utils'
import {
IIdentifierResolution,
ManagedIdentifierMethod,
Expand All @@ -22,7 +26,14 @@ import {
import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
import { IContactManager } from '@sphereon/ssi-sdk.contact-manager'
import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store'
import { DigitalCredential, IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding, Identity, Party } from '@sphereon/ssi-sdk.data-store'
import {
DigitalCredential,
IBasicCredentialLocaleBranding,
IBasicIssuerLocaleBranding,
Identity,
IIssuerLocaleBranding,
Party
} from '@sphereon/ssi-sdk.data-store'
import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
import { ImDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt'
Expand Down Expand Up @@ -72,7 +83,11 @@ export interface IOID4VCIHolder extends IPluginMethodMap {

oid4vciHolderAddContactIdentity(args: AddContactIdentityArgs, context: RequiredContext): Promise<Identity>

oid4vciHolderAssertValidCredentials(args: AssertValidCredentialsArgs, context: RequiredContext): Promise<VerificationResult[]>
oid4vciHolderAssertValidCredentials(args: AssertValidCredentialsArgs, context: RequiredContext): Promise<Array<VerificationResult>>

oid4vciHolderGetIssuerBranding(args: GetIssuerBrandingArgs, context: RequiredContext): Promise<Array<IIssuerLocaleBranding | IBasicIssuerLocaleBranding>>

oid4vciHolderStoreIssuerBranding(args: StoreIssuerBrandingArgs, context: RequiredContext): Promise<void>

oid4vciHolderStoreCredentialBranding(args: StoreCredentialBrandingArgs, context: RequiredContext): Promise<void>

Expand Down Expand Up @@ -135,7 +150,8 @@ export type GetCredentialsArgs = Pick<
'verificationCode' | 'openID4VCIClientState' | 'selectedCredentials' | 'didMethodPreferences' | 'issuanceOpt' | 'accessTokenOpts'
>
export type AddContactIdentityArgs = Pick<OID4VCIMachineContext, 'credentialsToAccept' | 'contact'>
export type AddIssuerBrandingArgs = Pick<OID4VCIMachineContext, 'serverMetadata' | 'contact'>
export type GetIssuerBrandingArgs = Pick<OID4VCIMachineContext, 'serverMetadata' | 'contact' >
export type StoreIssuerBrandingArgs = Pick<OID4VCIMachineContext, 'issuerBranding' | 'contact'>
export type AssertValidCredentialsArgs = Pick<OID4VCIMachineContext, 'credentialsToAccept' | 'issuanceOpt'>
export type StoreCredentialBrandingArgs = Pick<
OID4VCIMachineContext,
Expand Down Expand Up @@ -197,6 +213,7 @@ export type OID4VCIMachineContext = {
requestData?: RequestData // TODO WAL-673 fix type as this is not always a qr code (deeplink)
locale?: string
authorizationCodeURL?: string
issuerBranding?: Array<IIssuerLocaleBranding | IBasicIssuerLocaleBranding>
credentialBranding?: Record<string, Array<IBasicCredentialLocaleBranding>>
credentialsSupported: Record<string, CredentialConfigurationSupported>
serverMetadata?: EndpointMetadataResult
Expand All @@ -219,7 +236,8 @@ export enum OID4VCIMachineStates {
transitionFromSetup = 'transitionFromSetup',
getFederationTrust = 'getFederationTrust',
addContact = 'addContact',
addIssuerBranding = 'addIssuerBranding',
getIssuerBranding = 'getIssuerBranding',
storeIssuerBranding = 'storeIssuerBranding',
addIssuerBrandingAfterIdentity = 'addIssuerBrandingAfterIdentity',
transitionFromContactSetup = 'transitionFromContactSetup',
selectCredentials = 'selectCredentials',
Expand Down Expand Up @@ -354,7 +372,8 @@ export enum OID4VCIMachineServices {
getFederationTrust = 'getFederationTrust',
addContactIdentity = 'addContactIdentity',
createCredentialsToSelectFrom = 'createCredentialsToSelectFrom',
addIssuerBranding = 'addIssuerBranding',
getIssuerBranding = 'getIssuerBranding',
storeIssuerBranding = 'storeIssuerBranding',
createCredentialSelection = 'createCredentialSelection',
getCredentials = 'getCredentials',
assertValidCredentials = 'assertValidCredentials',
Expand Down Expand Up @@ -492,7 +511,7 @@ export type GetCredentialBrandingArgs = {
context: RequiredContext
}

export type GetIssuerBrandingArgs = {
export type GetBasicIssuerLocaleBrandingArgs = {
display: MetadataDisplay[]
context: RequiredContext
}
Expand Down