Skip to content

Commit 7d976b2

Browse files
authored
Add EHIC and PDA1 attestations
* Attestation definitions * Initial implementation * Fixes sass map-get warnings * Display nested attribute values in the presentation results * Updated ehic and pda1 definitions * Renaming * Cleared console.log() statements
1 parent 678eb2a commit 7d976b2

33 files changed

Lines changed: 363 additions & 109 deletions

src/app/core/constants/attestation-definitions.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export const PHOTO_ID_ATTESTATION: AttestationDefinition = {
9595
{ identifier: 'portrait', attribute: 'Portrait'},
9696
{ identifier: 'portrait_capture_date', attribute: 'Portrait capture date'},
9797
{ identifier: 'person_id', attribute: 'Person id'},
98-
{ identifier: 'family_name', attribute: 'Family_name'},
98+
{ identifier: 'family_name', attribute: 'Family name'},
9999
{ identifier: 'given_name', attribute: 'Given name'},
100100
{ identifier: 'birth_date', attribute: 'Birth date'},
101101
{ identifier: 'age_over_18', attribute: 'Age over 18'},
@@ -127,10 +127,41 @@ export const PHOTO_ID_ATTESTATION: AttestationDefinition = {
127127
]
128128
}
129129

130+
export const EHIC_ATTESTATION: AttestationDefinition = {
131+
name: "European Health Insurance Card (EHIC)",
132+
type: AttestationType.EHIC,
133+
dataSet: [
134+
{ identifier: "credential_holder", attribute: "Credential holder" },
135+
{ identifier: "subject", attribute: "Subject" },
136+
{ identifier: 'social_security_pin', attribute: 'Social security PIN'},
137+
{ identifier: "starting_date", attribute: "Starting date" },
138+
{ identifier: "ending_date", attribute: "Ending date" },
139+
{ identifier: 'document_id', attribute: 'Document identifier' },
140+
{ identifier: "competent_institution", attribute: 'Competent institution' }
141+
],
142+
}
143+
144+
export const PDA1_ATTESTATION: AttestationDefinition = {
145+
name: "Portable Document A1 (PDA1)",
146+
type: AttestationType.PDA1,
147+
dataSet: [
148+
{ identifier: "credential_holder", attribute: "Credential holder" },
149+
{ identifier: 'social_security_pin', attribute: 'Social security PIN' },
150+
{ identifier: "nationality", attribute: "Nationality" },
151+
{ identifier: "employment_details", attribute: "Employment details" },
152+
{ identifier: 'places_of_work', attribute: 'Places of work' },
153+
{ identifier: 'legislation', attribute: 'Legislation' },
154+
{ identifier: 'status_confirmation', attribute: 'Status confirmation'},
155+
{ identifier: 'document_id', attribute: 'Document identifier'},
156+
{ identifier: "competent_institution", attribute: "Competent institution"},
157+
]
158+
}
159+
130160
export const SUPPORTED_ATTESTATIONS: { [id: string]: AttestationDefinition } = {
131161
"pid": PID_ATTESTATION,
132162
"mdl": MDL_ATTESTATION,
133163
"photo_id": PHOTO_ID_ATTESTATION,
134164
"age_over_18": AGE_OVER_18_ATTESTATION,
165+
"ehic": EHIC_ATTESTATION,
166+
"pda1": PDA1_ATTESTATION
135167
}
136-

src/app/core/constants/attestations-per-format.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Attestation, MsoMdocAttestation, SdJwtVcAttestation} from "@core/models/attestation/Attestations";
2-
import {AGE_OVER_18_ATTESTATION, MDL_ATTESTATION, PHOTO_ID_ATTESTATION, PID_ATTESTATION} from "@core/constants/attestation-definitions";
2+
import {AGE_OVER_18_ATTESTATION, EHIC_ATTESTATION, MDL_ATTESTATION, PDA1_ATTESTATION, PHOTO_ID_ATTESTATION, PID_ATTESTATION} from "@core/constants/attestation-definitions";
33
import {AttestationFormat} from "@core/models/attestation/AttestationFormat";
44
import {AttestationType} from "@core/models/attestation/AttestationType";
55
import {DataElement} from "@core/models/attestation/AttestationDefinition";
@@ -56,8 +56,46 @@ export const PHOTO_ID_MSO_MDOC: MsoMdocAttestation = {
5656
claimPath: (attribute: DataElement) => { return { namespace: 'org.iso.23220.photoid.1', claim_name: attribute.identifier } }
5757
}
5858

59+
/*---- EHIC INSTANCES PER FORMAT ----*/
60+
export const EHIC_MSO_MDOC: MsoMdocAttestation = {
61+
format: AttestationFormat.MSO_MDOC,
62+
attestationDef: EHIC_ATTESTATION,
63+
doctype: 'eu.europa.ec.eudi.ehic.1',
64+
namespace: 'eu.europa.ec.eudi.ehic.1',
65+
attributePath: (attribute: DataElement) => { return msoMdocAttributePath(attribute, 'eu.europa.ec.eudi.ehic.1') },
66+
claimPath: (attribute: DataElement) => { return { namespace: 'eu.europa.ec.eudi.ehic.1', claim_name: attribute.identifier } }
67+
}
68+
export const EHIC_SD_JWT_VC: SdJwtVcAttestation = {
69+
format: AttestationFormat.SD_JWT_VC,
70+
attestationDef: EHIC_ATTESTATION,
71+
vct: 'urn:eu.europa.ec.eudi:ehic:1',
72+
attributePath: (attribute: DataElement) => { return `$.${sdJwtVcAttributePath(attribute, AttestationType.EHIC)}` },
73+
claimPath: (attribute: DataElement) => { return { path: sdJwtVcAttributePath(attribute, AttestationType.EHIC).split('.') } }
74+
}
75+
76+
/*---- PDA1 INSTANCES PER FORMAT ----*/
77+
export const PDA1_MSO_MDOC: MsoMdocAttestation = {
78+
format: AttestationFormat.MSO_MDOC,
79+
attestationDef: PDA1_ATTESTATION,
80+
doctype: 'eu.europa.ec.eudi.pda1.1',
81+
namespace: 'eu.europa.ec.eudi.pda1.1',
82+
attributePath: (attribute: DataElement) => { return msoMdocAttributePath(attribute, 'eu.europa.ec.eudi.pda1.1') },
83+
claimPath: (attribute: DataElement) => { return { namespace: 'eu.europa.ec.eudi.pda1.1', claim_name: attribute.identifier } }
84+
}
85+
export const PDA1_SD_JWT_VC: SdJwtVcAttestation = {
86+
format: AttestationFormat.SD_JWT_VC,
87+
attestationDef: PDA1_ATTESTATION,
88+
vct: 'urn:eu.europa.ec.eudi:pda1:1',
89+
attributePath: (attribute: DataElement) => { return `$.${sdJwtVcAttributePath(attribute, AttestationType.PDA1)}` },
90+
claimPath: (attribute: DataElement) => { return { path: sdJwtVcAttributePath(attribute, AttestationType.PDA1).split('.') } }
91+
}
92+
5993
function msoMdocAttributePath(attribute: DataElement, namespace: string): string {
60-
return '$[\'' + namespace + '\'][\'' + attribute.identifier + '\']'
94+
if(attribute.identifier.includes('.')) {
95+
return '$[\'' + namespace + '\'][\'' + attribute.identifier.split('.').join('\'][\'') + '\']'
96+
} else {
97+
return '$[\'' + namespace + '\'][\'' + attribute.identifier + '\']'
98+
}
6199
}
62100

63101
function sdJwtVcAttributePath(attribute: DataElement, attestationType: AttestationType): string {
@@ -88,8 +126,8 @@ export const PID_SD_JWT_VC_ATTRIBUTE_MAP: { [id: string]: string } = {
88126
}
89127

90128
export const ATTESTATIONS_BY_FORMAT: { [id: string]: Attestation[] } = {
91-
"mso_mdoc": [PID_MSO_MDOC, MDL_MSO_MDOC, PHOTO_ID_MSO_MDOC, AGE_OVER_18_MSO_MDOC],
92-
"vc+sd-jwt": [PID_SD_JWT_VC]
129+
"mso_mdoc": [PID_MSO_MDOC, MDL_MSO_MDOC, PHOTO_ID_MSO_MDOC, AGE_OVER_18_MSO_MDOC, EHIC_MSO_MDOC, PDA1_MSO_MDOC],
130+
"vc+sd-jwt": [PID_SD_JWT_VC, EHIC_SD_JWT_VC, PDA1_SD_JWT_VC]
93131
}
94132

95133
export const getAttestationByFormatAndType =

src/app/core/layout/wallet-layout/wallet-layout-header/wallet-layout-header.component.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
@use "/src/template" as temp;
2+
@use "sass:map";
23

34
:host {
45
border: none;
56
border-bottom: 1px;
6-
border-bottom-color: map-get(
7-
map-get(map-get(temp.$palette, general), divider),
7+
border-bottom-color: map.get(
8+
map.get(map.get(temp.$palette, general), divider),
89
dark
910
);
1011
border-bottom-style: solid;

src/app/core/models/FormSelectableField.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export type FormSelectableField = {
33
label: string,
44
value: string,
55
visible?: boolean
6+
nested?: FormSelectableField[]
67
}

src/app/core/models/attestation/AttestationDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export type DataElement = {
1010
identifier: string,
1111
attribute: string,
1212
description?: string,
13+
nested?: DataElement[]
1314
}

src/app/core/models/attestation/AttestationType.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ export enum AttestationType {
22
PID = "pid",
33
MDL = "mdl",
44
PHOTO_ID = "photo_id",
5-
AGE_OVER_18 = "age_over_18"
5+
AGE_OVER_18 = "age_over_18",
6+
EHIC = "ehic",
7+
PDA1 = "pda1"
68
}

src/app/core/services/decoders/DecodingUtils.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
1-
21
export function elementAsString(element: any, prepend?: string): string {
3-
if ((typeof element) === "object") {
4-
5-
if (Array.isArray(element)) {
6-
return (element as string[]).map((it) => {
7-
return JSON.stringify(it);
8-
}).join(', ')
9-
10-
} else {
11-
let str = ""
12-
if (typeof prepend !== 'undefined') {
13-
str += "<br/>"
14-
} else {
15-
prepend = ""
16-
}
17-
return str + Object.keys(element).map((it) => {
18-
return prepend + "&nbsp;&nbsp;" + it + ": " + elementAsString(element[it], "&nbsp;&nbsp;").toString()
19-
}).join("<br/>");
20-
}
21-
2+
if (typeof element === 'object') {
3+
return JSON.stringify(element);
224
} else {
235
return element.toString();
246
}

src/app/core/services/decoders/JwtVcJsonAttestationDecoder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ export class JwtVcJsonAttestationDecoder implements AttestationDecoder {
2929
let sharedCredentials = this.unWrapCredentials(vp)
3030

3131
if (sharedCredentials.length == 1) {
32-
return of(this.toSinge(sharedCredentials[0]))
32+
return of(this.toSingle(sharedCredentials[0]))
3333

3434
} else {
35-
let singles = sharedCredentials.map(it => this.toSinge(it));
35+
let singles = sharedCredentials.map(it => this.toSingle(it));
3636
return of({
3737
kind: "enveloped",
3838
attestations: singles
3939
})
4040
}
4141
}
4242

43-
toSinge(vcJwt: string): Single {
43+
toSingle(vcJwt: string): Single {
4444
let vc = this.jWTService.decodeToObject(vcJwt) as any;
4545
return {
4646
kind: "single",

src/app/core/services/jwt.service.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export class JWTService {
1414

1515
decodeToKeyValues (token: string): KeyValue<string, string>[] {
1616
const decoded: any = jwtDecode(token);
17-
console.log(decoded)
1817
const result: KeyValue<string, string>[] = [];
1918
Object.keys(decoded).forEach((item) => {
2019
result.push({

src/app/core/services/presentation-definition-service.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class PresentationDefinitionService {
3333
): PresentationDefinitionTransactionRequest {
3434
let inputDescriptors: InputDescriptor[] = [];
3535

36-
selectedAttestations.map((attestation) => {
36+
selectedAttestations.forEach((attestation) => {
3737
const selectedAttributesForAttestation =
3838
selectedAttributes[attestation.type];
3939

@@ -156,14 +156,21 @@ export class PresentationDefinitionService {
156156
includeAttributes?: string[]
157157
): FieldConstraint[] {
158158
const fieldConstraints: FieldConstraint[] = [];
159+
const includeAll = typeof includeAttributes == 'undefined';
160+
159161
attestation.attestationDef.dataSet.forEach((dataElement: DataElement) => {
160-
if (
161-
typeof includeAttributes == 'undefined' ||
162-
includeAttributes.includes(dataElement.identifier)
163-
) {
162+
if (includeAll || includeAttributes.includes(dataElement.identifier)) {
164163
fieldConstraints.push(
165164
this.fieldConstraint(attestation.attributePath(dataElement))
166165
);
166+
} else if (dataElement.nested) {
167+
dataElement.nested.forEach((nestedDataElement: DataElement) => {
168+
if (includeAll || includeAttributes.includes(nestedDataElement.identifier)) {
169+
fieldConstraints.push(
170+
this.fieldConstraint(attestation.attributePath(nestedDataElement))
171+
);
172+
}
173+
});
167174
}
168175
});
169176
return fieldConstraints;

0 commit comments

Comments
 (0)