dif_presentation_exchange/
presentation_definition.rs

1use getset::Getters;
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4use std::collections::HashMap;
5
6/// As specified in https://identity.foundation/presentation-exchange/#presentation-definition.
7#[allow(dead_code)]
8#[skip_serializing_none]
9#[derive(Deserialize, Debug, Getters, PartialEq, Clone, Serialize)]
10pub struct PresentationDefinition {
11    #[getset(get = "pub")]
12    pub(crate) id: String,
13    // All inputs listed in the `input_descriptors` array are required for submission, unless otherwise specified by a
14    // Feature.
15    #[getset(get = "pub")]
16    pub(crate) input_descriptors: Vec<InputDescriptor>,
17    pub(crate) name: Option<String>,
18    pub(crate) purpose: Option<String>,
19    pub(crate) format: Option<HashMap<ClaimFormatDesignation, ClaimFormatProperty>>,
20}
21
22/// As specified in https://identity.foundation/presentation-exchange/#input-descriptor-object.
23/// All input descriptors MUST be satisfied, unless otherwise specified by a Feature.
24#[allow(dead_code)]
25#[skip_serializing_none]
26#[derive(Deserialize, Debug, Getters, PartialEq, Clone, Serialize)]
27pub struct InputDescriptor {
28    // Must not conflict with other input descriptors.
29    #[getset(get = "pub")]
30    pub(crate) id: String,
31    pub(crate) name: Option<String>,
32    pub(crate) purpose: Option<String>,
33    pub(crate) format: Option<HashMap<ClaimFormatDesignation, ClaimFormatProperty>>,
34    #[getset(get = "pub")]
35    pub(crate) constraints: Constraints,
36    pub(crate) schema: Option<String>,
37}
38
39// Its value MUST be an array of one or more format-specific algorithmic identifier references
40// TODO: fix this related to jwt_vc_json and jwt_vp_json: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-e.1
41#[allow(dead_code)]
42#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub enum ClaimFormatDesignation {
45    Jwt,
46    JwtVc,
47    JwtVcJson,
48    JwtVp,
49    JwtVpJson,
50    Ldp,
51    LdpVc,
52    LdpVp,
53    AcVc,
54    AcVp,
55    MsoMdoc,
56}
57
58#[allow(dead_code)]
59#[derive(Deserialize, Debug, PartialEq, Clone, Serialize)]
60#[serde(rename_all = "snake_case")]
61pub enum ClaimFormatProperty {
62    Alg(Vec<serde_json::Value>),
63    ProofType(Vec<serde_json::Value>),
64}
65
66#[allow(dead_code)]
67#[skip_serializing_none]
68#[derive(Deserialize, Debug, Getters, Default, PartialEq, Clone, Serialize)]
69pub struct Constraints {
70    #[getset(get = "pub")]
71    pub(crate) fields: Option<Vec<Field>>,
72    // Omission of the `limit_disclosure` property indicates the Conforment Consumer MAY submit a response that contains
73    // more than the data described in the `fields` array.
74    #[getset(get = "pub")]
75    pub(crate) limit_disclosure: Option<LimitDisclosure>,
76}
77
78#[allow(dead_code)]
79#[derive(Deserialize, Debug, PartialEq, Clone, Serialize)]
80#[serde(rename_all = "snake_case")]
81pub enum LimitDisclosure {
82    Required,
83    Preferred,
84}
85
86#[allow(dead_code)]
87#[skip_serializing_none]
88#[derive(Deserialize, Debug, Getters, Default, PartialEq, Clone, Serialize)]
89pub struct Field {
90    // The value of this property MUST be an array of ONE OR MORE JSONPath string expressions.
91    // The ability to declare multiple expressions in this way allows the Verifier to account for format differences.
92    #[getset(get = "pub")]
93    pub(crate) path: Vec<String>,
94    pub(crate) id: Option<String>,
95    pub(crate) purpose: Option<String>,
96    pub(crate) name: Option<String>,
97    #[getset(get = "pub")]
98    pub(crate) filter: Option<serde_json::Value>,
99    // TODO: check default behaviour
100    #[getset(get = "pub")]
101    pub(crate) optional: Option<bool>,
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use serde::de::DeserializeOwned;
108    use std::{fs::File, path::Path};
109
110    fn json_example<T>(path: &str) -> T
111    where
112        T: DeserializeOwned,
113    {
114        let file_path = Path::new(path);
115        let file = File::open(file_path).expect("file does not exist");
116        serde_json::from_reader::<_, T>(file).expect("could not parse json")
117    }
118
119    #[test]
120    fn test_deserialize_presentation_definition() {
121        assert_eq!(
122            PresentationDefinition {
123                id: "example_vc_ac_sd".to_string(),
124                name: None,
125                format: None,
126                input_descriptors: vec![InputDescriptor {
127                    id: "id_credential".to_string(),
128                    name: None,
129                    purpose: None,
130                    format: Some(HashMap::from_iter(vec![(
131                        ClaimFormatDesignation::AcVc,
132                        ClaimFormatProperty::ProofType(vec![serde_json::json!("CLSignature2019")])
133                    )])),
134                    constraints: Constraints {
135                        limit_disclosure: Some(LimitDisclosure::Required),
136                        fields: Some(vec![
137                            Field {
138                                path: vec!["$.schema_id".to_string()],
139                                filter: Some(serde_json::json!({
140                                    "type": "string",
141                                    "const": "did:indy:idu:test:3QowxFtwciWceMFr7WbwnM:2:BasicScheme:0\\.1"
142                                })),
143                                ..Default::default()
144                            },
145                            Field {
146                                path: vec!["$.values.first_name".to_string()],
147                                ..Default::default()
148                            },
149                            Field {
150                                path: vec!["$.values.last_name".to_string()],
151                                ..Default::default()
152                            }
153                        ]),
154                    },
155                    schema: None,
156                }],
157                purpose: None,
158            },
159            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ac_vc_sd.json")
160        );
161
162        assert_eq!(
163            PresentationDefinition {
164                id: "example_vc_ac".to_string(),
165                name: None,
166                format: None,
167                input_descriptors: vec![InputDescriptor {
168                    id: "id_credential".to_string(),
169                    name: None,
170                    purpose: None,
171                    format: Some(HashMap::from_iter(vec![(
172                        ClaimFormatDesignation::AcVc,
173                        ClaimFormatProperty::ProofType(vec![serde_json::json!("CLSignature2019")])
174                    )])),
175                    constraints: Constraints {
176                        fields: Some(vec![Field {
177                            path: vec!["$.schema_id".to_string()],
178                            filter: Some(serde_json::json!({
179                                "type": "string",
180                                "const": "did:indy:idu:test:3QowxFtwciWceMFr7WbwnM:2:BasicScheme:0\\.1"
181                            })),
182                            ..Default::default()
183                        }]),
184                        limit_disclosure: None,
185                    },
186                    schema: None,
187                }],
188                purpose: None,
189            },
190            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ac_vc.json")
191        );
192
193        assert_eq!(
194            PresentationDefinition {
195                id: "example_jwt_vc".to_string(),
196                name: None,
197                format: None,
198                input_descriptors: vec![InputDescriptor {
199                    id: "id_credential".to_string(),
200                    name: None,
201                    purpose: None,
202                    format: Some(HashMap::from_iter(vec![(
203                        ClaimFormatDesignation::JwtVcJson,
204                        ClaimFormatProperty::ProofType(vec![serde_json::json!("JsonWebSignature2020")])
205                    )])),
206                    constraints: Constraints {
207                        fields: Some(vec![Field {
208                            path: vec!["$.vc.type".to_string()],
209                            filter: Some(serde_json::json!({
210                                "type": "array",
211                                "contains": {
212                                    "const": "IDCredential"
213                                }
214                            })),
215                            ..Default::default()
216                        }]),
217                        limit_disclosure: None,
218                    },
219                    schema: None,
220                }],
221                purpose: None,
222            },
223            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_jwt_vc.json")
224        );
225
226        assert_eq!(
227            PresentationDefinition {
228                id: "example_ldp_vc".to_string(),
229                name: None,
230                format: None,
231                input_descriptors: vec![InputDescriptor {
232                    id: "id_credential".to_string(),
233                    name: None,
234                    purpose: None,
235                    format: Some(HashMap::from_iter(vec![(
236                        ClaimFormatDesignation::LdpVc,
237                        ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
238                    )])),
239                    constraints: Constraints {
240                        fields: Some(vec![Field {
241                            path: vec!["$.type".to_string()],
242                            filter: Some(serde_json::json!({
243                                "type": "array",
244                                "contains": {
245                                    "const": "IDCredential"
246                                }
247                            })),
248                            ..Default::default()
249                        }]),
250                        limit_disclosure: None,
251                    },
252                    schema: None,
253                }],
254                purpose: None,
255            },
256            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ldp_vc.json")
257        );
258
259        // TODO: report json file bug + add retention feature: https://identity.foundation/presentation-exchange/spec/v2.0.0/#retention-feature
260        assert_eq!(
261            PresentationDefinition {
262                id: "mDL-sample-req".to_string(),
263                name: None,
264                format: None,
265                input_descriptors: vec![InputDescriptor {
266                    id: "mDL".to_string(),
267                    name: None,
268                    purpose: None,
269                    format: Some(HashMap::from_iter(vec![(
270                        ClaimFormatDesignation::MsoMdoc,
271                        ClaimFormatProperty::Alg(vec![serde_json::json!("EdDSA"), serde_json::json!("ES256")])
272                    )])),
273                    constraints: Constraints {
274                        limit_disclosure: Some(LimitDisclosure::Required),
275                        fields: Some(vec![
276                            Field {
277                                path: vec!["$.mdoc.doctype".to_string()],
278                                filter: Some(serde_json::json!({
279                                    "type": "string",
280                                    "const": "org.iso.18013.5.1.mDL"
281                                })),
282                                ..Default::default()
283                            },
284                            Field {
285                                path: vec!["$.mdoc.namespace".to_string()],
286                                filter: Some(serde_json::json!({
287                                    "type": "string",
288                                    "const": "org.iso.18013.5.1"
289                                })),
290                                ..Default::default()
291                            },
292                            Field {
293                                path: vec!["$.mdoc.family_name".to_string()],
294                                ..Default::default()
295                            },
296                            Field {
297                                path: vec!["$.mdoc.portrait".to_string()],
298                                ..Default::default()
299                            },
300                            Field {
301                                path: vec!["$.mdoc.driving_privileges".to_string()],
302                                ..Default::default()
303                            },
304                        ]),
305                    },
306                    schema: None,
307                }],
308                purpose: None,
309            },
310            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_mdl_iso_cbor.json")
311        );
312
313        assert_eq!(
314            PresentationDefinition {
315                id: "example with selective disclosure".to_string(),
316                name: None,
317                format: None,
318                input_descriptors: vec![InputDescriptor {
319                    id: "ID card with constraints".to_string(),
320                    name: None,
321                    purpose: None,
322                    format: Some(HashMap::from_iter(vec![(
323                        ClaimFormatDesignation::LdpVc,
324                        ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
325                    )])),
326                    constraints: Constraints {
327                        fields: Some(vec![
328                            Field {
329                                path: vec!["$.type".to_string()],
330                                filter: Some(serde_json::json!({
331                                    "type": "string",
332                                    "pattern": "IDCardCredential"
333                                })),
334                                ..Default::default()
335                            },
336                            Field {
337                                path: vec!["$.credentialSubject.given_name".to_string()],
338                                ..Default::default()
339                            },
340                            Field {
341                                path: vec!["$.credentialSubject.family_name".to_string()],
342                                ..Default::default()
343                            },
344                            Field {
345                                path: vec!["$.credentialSubject.birthdate".to_string()],
346                                ..Default::default()
347                            }
348                        ]),
349                        limit_disclosure: Some(LimitDisclosure::Required),
350                    },
351                    schema: None,
352                }],
353                purpose: None,
354            },
355            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/vp_token_type_and_claims.json")
356        );
357
358        assert_eq!(
359            PresentationDefinition {
360                id: "vp token example".to_string(),
361                name: None,
362                format: None,
363                input_descriptors: vec![InputDescriptor {
364                    id: "id card credential".to_string(),
365                    name: None,
366                    purpose: None,
367                    format: Some(HashMap::from_iter(vec![(
368                        ClaimFormatDesignation::LdpVc,
369                        ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
370                    )])),
371                    constraints: Constraints {
372                        fields: Some(vec![Field {
373                            path: vec!["$.type".to_string()],
374                            filter: Some(serde_json::json!({
375                                "type": "string",
376                                "pattern": "IDCardCredential"
377                            })),
378                            ..Default::default()
379                        }]),
380                        limit_disclosure: None,
381                    },
382                    schema: None,
383                }],
384                purpose: None,
385            },
386            json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/vp_token_type_only.json")
387        );
388    }
389}