Skip to content

Commit

Permalink
Merge branch 'feature/SPRIND-137' of github.com:Sphereon-Opensource/O…
Browse files Browse the repository at this point in the history
…ID4VC into feature/VDX-341_dcql
  • Loading branch information
Zoe Maas committed Jan 16, 2025
2 parents 1ac4907 + d8fdf44 commit 8b9a1ff
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { parseJWT } from '@sphereon/oid4vc-common'
import { DcqlQuery } from 'dcql'

import { PresentationDefinitionWithLocation } from '../authorization-response'
import { findValidDcqlQuery } from '../authorization-response'
import { Dcql } from '../authorization-response'
import { PresentationExchange } from '../authorization-response/PresentationExchange'
import { fetchByReferenceOrUseByValue, removeNullUndefined } from '../helpers'
import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion'
Expand Down Expand Up @@ -206,7 +206,7 @@ export class AuthorizationRequest {
await this.getSupportedVersion(),
)

const dcqlQuery = await findValidDcqlQuery(mergedPayload)
const dcqlQuery = await Dcql.findValidDcqlQuery(mergedPayload)

return {
jwt,
Expand Down Expand Up @@ -291,6 +291,6 @@ export class AuthorizationRequest {
}

public async getDcqlQuery(): Promise<DcqlQuery | undefined> {
return await findValidDcqlQuery(await this.mergedPayloads())
return await Dcql.findValidDcqlQuery(await this.mergedPayloads())
}
}
2 changes: 1 addition & 1 deletion packages/siop-oid4vp/lib/authorization-request/Payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadO
return undefined
}

if (opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri) {
if (opts.vp_token.presentation_definition !== undefined && opts.vp_token.presentation_definition !== null) {
const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition)
if (discoveryResult.error) {
throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID)
Expand Down
4 changes: 2 additions & 2 deletions packages/siop-oid4vp/lib/authorization-request/URI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { parseJWT } from '@sphereon/oid4vc-common'

import { findValidDcqlQuery } from '../authorization-response/Dcql'
import { Dcql } from '../authorization-response'
import { PresentationExchange } from '../authorization-response/PresentationExchange'
import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers'
import { assertValidRequestObjectPayload, RequestObject } from '../request-object'
Expand Down Expand Up @@ -166,7 +166,7 @@ export class URI implements AuthorizationRequestURI {
if (requestObjectPayload) {
// Only used to validate if the request object contains presentation definition(s) | a dcql query
await PresentationExchange.findValidPresentationDefinitions({ ...authorizationRequestPayload, ...requestObjectPayload })
await findValidDcqlQuery({ ...authorizationRequestPayload, ...requestObjectPayload })
await Dcql.findValidDcqlQuery({ ...authorizationRequestPayload, ...requestObjectPayload })

assertValidRequestObjectPayload(requestObjectPayload)
if (requestObjectPayload.registration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-requ
import { IDToken } from '../id-token'
import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types'

import { assertValidDcqlPresentationResult } from './Dcql'
import { Dcql } from './Dcql'
import {
assertValidVerifiablePresentations,
extractNonceFromWrappedVerifiablePresentation,
Expand Down Expand Up @@ -145,7 +145,7 @@ export class AuthorizationResponse {
},
})
} else if (verifiedAuthorizationRequest.dcqlQuery) {
assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, {
await Dcql.assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, {
hasher: verifyOpts.hasher,
})
} else {
Expand Down
96 changes: 56 additions & 40 deletions packages/siop-oid4vp/lib/authorization-response/Dcql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,66 @@ import { extractDcqlPresentationFromDcqlVpToken } from './OpenID4VP'
* @param authorizationRequestPayload object that can have a dcql_query inside
* @param version
*/
export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise<DcqlQuery | undefined> => {
const dcqlQuery: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query').map((d) => d.value)
const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition')
const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]')
const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri')
const definitionRefsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri[*]')

const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0)
const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0)
const hasDcql = dcqlQuery && dcqlQuery.length > 0

if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) {
throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE)
}

if (dcqlQuery.length === 0) return undefined
export class Dcql {

static findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise<DcqlQuery | undefined> => {
const dcqlQuery: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query').map((d) => d.value)
const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition')
const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]')
const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri')
const definitionRefsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri[*]')

const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0)
const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0)
const hasDcql = dcqlQuery && dcqlQuery.length > 0

if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) {
throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE)
}

if (dcqlQuery.length === 0) return undefined

if (dcqlQuery.length > 1) {
throw new Error('Found multiple dcql_query in vp_token. Only one is allowed')
if (dcqlQuery.length > 1) {
throw new Error('Found multiple dcql_query in vp_token. Only one is allowed')
}

return DcqlQuery.parse(JSON.parse(dcqlQuery[0]))
}

return DcqlQuery.parse(JSON.parse(dcqlQuery[0]))
}
static getDcqlPresentationResult = (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: {
hasher?: Hasher
}) => {
const dcqlPresentation = Object.fromEntries(
Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => {
if (p.format === 'mso_mdoc') {
return [
queryId,
{
credential_format: 'mso_mdoc',
doctype: p.vcs[0].credential.toJson().docType,
namespaces: p.vcs[0].decoded
} satisfies DcqlMdocCredential,
]
} else if (p.format === 'vc+sd-jwt') {
return [queryId, {
credential_format: 'vc+sd-jwt',
vct: p.vcs[0].decoded.vct,
claims: p.vcs[0].decoded
} satisfies DcqlSdJwtVcCredential]
} else {
throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt')
}
}),
)

export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => {
const dcqlPresentation = Object.fromEntries(
Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => {
if (p.format === 'mso_mdoc') {
return [
queryId,
{ credential_format: 'mso_mdoc', doctype: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential,
]
} else if (p.format === 'vc+sd-jwt') {
return [queryId, { credential_format: 'vc+sd-jwt', vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential]
} else {
throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt')
}
}),
)

return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery })
}
return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery })
}

export const assertValidDcqlPresentationResult = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => {
const result = getDcqlPresentationResult(record, dcqlQuery, opts)
return DcqlPresentationResult.validate(result)
static assertValidDcqlPresentationResult = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: {
hasher?: Hasher
}) => {
const result = Dcql.getDcqlPresentationResult(record, dcqlQuery, opts)
return DcqlPresentationResult.validate(result)
}
}
4 changes: 2 additions & 2 deletions packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from '../types'

import { AuthorizationResponse } from './AuthorizationResponse'
import { assertValidDcqlPresentationResult } from './Dcql'
import { Dcql } from './Dcql'
import { PresentationExchange } from './PresentationExchange'
import {
AuthorizationResponseOpts,
Expand Down Expand Up @@ -96,7 +96,7 @@ export const verifyPresentations = async (
),
)

assertValidDcqlPresentationResult(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher })
await Dcql.assertValidDcqlPresentationResult(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher })

if (verifiedPresentations.some((verified) => !verified)) {
const message = verifiedPresentations
Expand Down
6 changes: 3 additions & 3 deletions packages/siop-oid4vp/lib/request-object/Payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export const createRequestObjectPayload = async (opts: CreateAuthorizationReques
state,
...registration.payload,
claims,
presentation_definition_uri: payload.presentation_definition_uri,
presentation_definition: payload.presentation_definition,
dcql_query: payload.dcql_query,
...(payload.presentation_definition_uri && { presentation_definition_uri: payload.presentation_definition_uri }),
...(payload.presentation_definition && { presentation_definition: payload.presentation_definition }),
...(payload.dcql_query && { dcql_query: payload.dcql_query }),
client_metadata: payload.client_metadata,
iat,
nbf,
Expand Down

0 comments on commit 8b9a1ff

Please sign in to comment.