diff --git a/Cargo.toml b/Cargo.toml index e1b1a51..8449afe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,17 @@ async-trait = "0.1.73" base64 = "0.21.4" did-web = "0.2.2" http = "1.1.0" +jsonpath_lib = "0.3.0" +jsonschema = "0.18.0" p256 = { version = "0.13.2", features = ["jwk"], optional = true } +regex = "1.10.6" reqwest = { version = "0.12.5", features = ["rustls-tls"], optional = true } serde = "1.0.188" serde_cbor = "0.11.2" serde_json = "1.0.107" serde_qs = "0.12.0" serde_urlencoded = "0.7.1" -ssi = "0.7.0" +ssi = "0.7" thiserror = "1.0.49" tokio = "1.32.0" tracing = "0.1.37" diff --git a/src/core/response/mod.rs b/src/core/response/mod.rs index 30fff9a..b6752ee 100644 --- a/src/core/response/mod.rs +++ b/src/core/response/mod.rs @@ -108,8 +108,8 @@ mod test { let object: UntypedObject = serde_json::from_value(json!( { "presentation_submission": { - "id": "id", - "definition_id": "definition_id", + "id": "d05a7f51-ac09-43af-8864-e00f0175f2c7", + "definition_id": "f619e64a-8f80-4b71-8373-30cf07b1e4f2", "descriptor_map": [] }, "vp_token": "string" @@ -119,7 +119,7 @@ mod test { let response = UnencodedAuthorizationResponse::try_from(object).unwrap(); assert_eq!( response.into_x_www_form_urlencoded().unwrap(), - "presentation_submission=%7B%22definition_id%22%3A%22definition_id%22%2C%22descriptor_map%22%3A%5B%5D%2C%22id%22%3A%22id%22%7D&vp_token=string", + "presentation_submission=%7B%22id%22%3A%22d05a7f51-ac09-43af-8864-e00f0175f2c7%22%2C%22definition_id%22%3A%22f619e64a-8f80-4b71-8373-30cf07b1e4f2%22%2C%22descriptor_map%22%3A%5B%5D%7D&vp_token=string", ) } } diff --git a/src/presentation_exchange.rs b/src/presentation_exchange.rs index df3c7cc..93f5f34 100644 --- a/src/presentation_exchange.rs +++ b/src/presentation_exchange.rs @@ -1,80 +1,603 @@ +use std::collections::HashMap; + pub use crate::utils::NonEmptyVec; + +use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; use serde_json::Map; +/// A JSONPath is a string that represents a path to a specific value within a JSON object. +/// +/// For syntax details, see [https://identity.foundation/presentation-exchange/spec/v2.0.0/#jsonpath-syntax-definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#jsonpath-syntax-definition) +pub type JsonPath = String; + +/// A Json object of claim formats. +pub type ClaimFormatMap = HashMap; + +/// The Presentation Definition MAY include a format property. The value MUST be an object with one or +/// more properties matching the registered [ClaimFormatDesignation] (e.g., jwt, jwt_vc, jwt_vp, etc.). +/// The properties inform the Holder of the Claim format configurations the Verifier can process. +/// The value for each claim format property MUST be an object composed as follows: +/// +/// The object MUST include a format-specific property (i.e., alg, proof_type) that expresses which +/// algorithms the Verifier supports for the format. Its value MUST be an array of one or more +/// format-specific algorithmic identifier references, as noted in the [ClaimFormatDesignation]. +/// +/// See [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition) +/// for an example schema. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum ClaimFormat { + #[serde(rename = "jwt")] + Jwt { + // The algorithm used to sign the JWT. + alg: Vec, + }, + #[serde(rename = "jwt_vc")] + JwtVc { + // The algorithm used to sign the JWT verifiable credential. + alg: Vec, + }, + #[serde(rename = "jwt_vp")] + JwtVp { + // The algorithm used to sign the JWT verifiable presentation. + alg: Vec, + }, + #[serde(rename = "ldp")] + Ldp { + // The proof type used to sign the linked data proof. + // e.g., "JsonWebSignature2020", "Ed25519Signature2018", "EcdsaSecp256k1Signature2019", "RsaSignature2018" + proof_type: Vec, + }, + #[serde(rename = "ldp_vc")] + LdpVc { + // The proof type used to sign the linked data proof verifiable credential. + proof_type: Vec, + }, + #[serde(rename = "ldp_vp")] + LdpVp { + // The proof type used to sign the linked data proof verifiable presentation. + proof_type: Vec, + }, + #[serde(rename = "ac_vc")] + AcVc { + // The proof type used to sign the anoncreds verifiable credential. + proof_type: Vec, + }, + #[serde(rename = "ac_vp")] + AcVp { + // The proof type used to sign the anoncreds verifiable presentation. + proof_type: Vec, + }, + #[serde(rename = "mso_mdoc")] + MsoMDoc(serde_json::Value), + Other(serde_json::Value), +} + +impl ClaimFormat { + /// Returns the designated format of the claim. + /// + /// e.g., jwt, jwt_vc, jwt_vp, ldp, ldp_vc, ldp_vp, ac_vc, ac_vp, mso_mdoc + pub fn designation(&self) -> ClaimFormatDesignation { + match self { + ClaimFormat::Jwt { .. } => ClaimFormatDesignation::Jwt, + ClaimFormat::JwtVc { .. } => ClaimFormatDesignation::JwtVc, + ClaimFormat::JwtVp { .. } => ClaimFormatDesignation::JwtVp, + ClaimFormat::Ldp { .. } => ClaimFormatDesignation::Ldp, + ClaimFormat::LdpVc { .. } => ClaimFormatDesignation::LdpVc, + ClaimFormat::LdpVp { .. } => ClaimFormatDesignation::LdpVp, + ClaimFormat::AcVc { .. } => ClaimFormatDesignation::AcVc, + ClaimFormat::AcVp { .. } => ClaimFormatDesignation::AcVp, + ClaimFormat::MsoMDoc(_) => ClaimFormatDesignation::MsoMDoc, + ClaimFormat::Other(_) => ClaimFormatDesignation::Other, + } + } +} + +/// Claim format payload +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum ClaimFormatPayload { + #[serde(rename = "alg")] + Alg(Vec), + #[serde(rename = "proof_type")] + ProofType(Vec), +} + +/// The claim format designation type is used in the input description object to specify the format of the claim. +/// +/// Registry of claim format type: https://identity.foundation/claim-format-registry/#registry +/// +/// Documentation based on the [DIF Presentation Exchange Specification v2.0](https://identity.foundation/presentation-exchange/spec/v2.0.0/#claim-format-designations) +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum ClaimFormatDesignation { + /// The format is a JSON Web Token (JWT) as defined by [RFC7519](https://identity.foundation/claim-format-registry/#ref:RFC7519) + /// that will be submitted in the form of a JWT encoded string. Expression of + /// supported algorithms in relation to this format MUST be conveyed using an `alg` + /// property paired with values that are identifiers from the JSON Web Algorithms + /// registry [RFC7518](https://identity.foundation/claim-format-registry/#ref:RFC7518). + #[serde(rename = "jwt")] + Jwt, + /// These formats are JSON Web Tokens (JWTs) [RFC7519](https://identity.foundation/claim-format-registry/#ref:RFC7519) + /// that will be submitted in the form of a JWT-encoded string, with a payload extractable from it defined according to the + /// JSON Web Token (JWT) [section] of the W3C [VC-DATA-MODEL](https://identity.foundation/claim-format-registry/#term:vc-data-model) + /// specification. Expression of supported algorithms in relation to these formats MUST be conveyed using an JWT alg + /// property paired with values that are identifiers from the JSON Web Algorithms registry in + /// [RFC7518](https://identity.foundation/claim-format-registry/#ref:RFC7518) Section 3. + #[serde(rename = "jwt_vc")] + JwtVc, + /// See [JwtVc](JwtVc) for more information. + #[serde(rename = "jwt_vp")] + JwtVp, + #[serde(rename = "jwt_vc_json")] + JwtVcJson, + #[serde(rename = "jwt_vp_json")] + JwtVpJson, + /// The format is a Linked-Data Proof that will be submitted as an object. + /// Expression of supported algorithms in relation to these formats MUST be + /// conveyed using a proof_type property with values that are identifiers from + /// the Linked Data Cryptographic Suite Registry [LDP-Registry](https://identity.foundation/claim-format-registry/#term:ldp-registry). + #[serde(rename = "ldp")] + Ldp, + /// Verifiable Credentials or Verifiable Presentations signed with Linked Data Proof formats. + /// These are descriptions of formats normatively defined in the W3C Verifiable Credentials + /// specification [VC-DATA-MODEL](https://identity.foundation/claim-format-registry/#term:vc-data-model), + /// and will be submitted in the form of a JSON object. Expression of supported algorithms in relation to + /// these formats MUST be conveyed using a proof_type property paired with values that are identifiers from the + /// Linked Data Cryptographic Suite Registry (LDP-Registry). + #[serde(rename = "ldp_vc")] + LdpVc, + /// See [LdpVc](LdpVc) for more information. + #[serde(rename = "ldp_vp")] + LdpVp, + /// This format is for Verifiable Credentials using AnonCreds. + /// AnonCreds is a VC format that adds important + /// privacy-protecting ZKP (zero-knowledge proof) capabilities + /// to the core VC assurances. + #[serde(rename = "ac_vc")] + AcVc, + /// This format is for Verifiable Presentations using AnonCreds. + /// AnonCreds is a VC format that adds important privacy-protecting ZKP + /// (zero-knowledge proof) capabilities to the core VC assurances. + #[serde(rename = "ac_vp")] + AcVp, + /// The format is defined by ISO/IEC 18013-5:2021 [ISO.18013-5](https://identity.foundation/claim-format-registry/#term:iso.18013-5) + /// which defines a mobile driving license (mDL) Credential in the mobile document (mdoc) format. + /// Although ISO/IEC 18013-5:2021 ISO.18013-5 is specific to mobile driving licenses (mDLs), + /// the Credential format can be utilized with any type of Credential (or mdoc document types). + #[serde(rename = "mso_mdoc")] + MsoMDoc, + /// Other claim format designations not covered by the above. + /// + /// The value of this variant is the name of the claim format designation. + Other, +} + +/// A presentation definition is a JSON object that describes the information a [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) requires of a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder). +/// +/// > Presentation Definitions are objects that articulate what proofs a [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) requires. +/// > These help the [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) to decide how or whether to interact with a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder). +/// +/// Presentation Definitions are composed of inputs, which describe the forms and details of the +/// proofs they require, and optional sets of selection rules, to allow [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder)s flexibility +/// in cases where different types of proofs may satisfy an input requirement. +/// +/// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition) +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct PresentationDefinition { - pub id: String, // Uuid, - pub input_descriptors: Vec, + id: String, + input_descriptors: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + name: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub purpose: Option, + purpose: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub format: Option, // TODO + format: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +impl PresentationDefinition { + /// The Presentation Definition MUST contain an id property. The value of this property MUST be a string. + /// The string SHOULD provide a unique ID for the desired context. + /// + /// The Presentation Definition MUST contain an input_descriptors property. Its value MUST be an array of Input Descriptor Objects, + /// the composition of which are found [InputDescriptor] type. + /// + pub fn new(id: String, input_descriptor: InputDescriptor) -> Self { + Self { + id, + input_descriptors: vec![input_descriptor], + ..Default::default() + } + } + + /// Return the id of the presentation definition. + pub fn id(&self) -> &String { + &self.id + } + + /// Add a new input descriptor to the presentation definition. + pub fn add_input_descriptors(mut self, input_descriptor: InputDescriptor) -> Self { + self.input_descriptors.push(input_descriptor); + self + } + + /// Return the input descriptors of the presentation definition. + pub fn input_descriptors(&self) -> &Vec { + &self.input_descriptors + } + + /// Return a mutable reference to the input descriptors of the presentation definition. + pub fn input_descriptors_mut(&mut self) -> &mut Vec { + &mut self.input_descriptors + } + + /// Set the name of the presentation definition. + /// + /// The [PresentationDefinition] MAY contain a name property. If present, its value SHOULD be a + /// human-friendly string intended to constitute a distinctive designation of the Presentation Definition. + pub fn set_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + /// Return the name of the presentation definition. + pub fn name(&self) -> Option<&String> { + self.name.as_ref() + } + + /// Set the purpose of the presentation definition. + /// + /// The [PresentationDefinition] MAY contain a purpose property. If present, its value MUST be a string that + /// describes the purpose for which the Presentation Definition's inputs are being used for. + pub fn set_purpose(mut self, purpose: String) -> Self { + self.purpose = Some(purpose); + self + } + + /// Return the purpose of the presentation definition. + pub fn purpose(&self) -> Option<&String> { + self.purpose.as_ref() + } + + /// Attach a format to the presentation definition. + /// + /// The Presentation Definition MAY include a format property. If present, + /// the value MUST be an object with one or more properties matching the + /// registered Claim Format Designations (e.g., jwt, jwt_vc, jwt_vp, etc.). + /// + /// The properties inform the [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder) of the Claim format configurations the [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) can process. + /// The value for each claim format property MUST be an object composed as follows: + /// + /// The object MUST include a format-specific property (i.e., alg, proof_type) + /// that expresses which algorithms the [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) supports for the format. + /// Its value MUST be an array of one or more format-specific algorithmic identifier references, + /// as noted in the Claim Format Designations section. + /// + /// See: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition) + pub fn set_format(mut self, format: ClaimFormatMap) -> Self { + self.format = Some(format); + self + } + + /// Add a new format to the presentation definition. + pub fn add_format(mut self, format: ClaimFormatDesignation, value: ClaimFormatPayload) -> Self { + self.format + .get_or_insert_with(HashMap::new) + .insert(format, value); + self + } + + /// Return the format of the presentation definition. + pub fn format(&self) -> Option<&ClaimFormatMap> { + self.format.as_ref() + } +} + +/// Input Descriptors are objects used to describe the information a +/// [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) requires of a +/// [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder). +/// +/// All Input Descriptors MUST be satisfied, unless otherwise specified by a +/// [Feature](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:feature). +/// +/// See: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object) +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct InputDescriptor { - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + id: String, + #[serde(default)] + constraints: Constraints, #[serde(skip_serializing_if = "Option::is_none")] - pub purpose: Option, + name: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub format: Option, // TODO + purpose: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub constraints: Option, // TODO shouldn't be optional + format: Option, } -// TODO must have at least one -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +impl InputDescriptor { + /// Create a new instance of the input descriptor with the given id and constraints. + /// + /// The Input Descriptor Object MUST contain an id property. The value of the id + /// property MUST be a string that does not conflict with the id of another + /// Input Descriptor Object in the same Presentation Definition. + /// + /// + /// The Input Descriptor Object MUST contain a constraints property. + /// + /// See: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object) + pub fn new(id: String, constraints: Constraints) -> Self { + Self { + id, + constraints, + ..Default::default() + } + } + + /// Return the id of the input descriptor. + pub fn id(&self) -> &String { + &self.id + } + + /// Return the constraints of the input descriptor. + pub fn constraints(&self) -> &Constraints { + &self.constraints + } + + /// Set the name of the input descriptor. + pub fn set_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + /// Return the name of the input descriptor. + pub fn name(&self) -> Option<&String> { + self.name.as_ref() + } + + /// Set the purpose of the input descriptor. + /// + /// The purpose of the input descriptor is an optional field. + /// + /// If present, the purpose MUST be a string that describes the purpose for which the + /// [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim)'s + /// data is being requested. + pub fn set_purpose(mut self, purpose: String) -> Self { + self.purpose = Some(purpose); + self + } + + /// Return the purpose of the input descriptor. + /// + /// If present, the purpose MUST be a string that describes the purpose for which the + /// [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim)'s + /// data is being requested. + pub fn purpose(&self) -> Option<&String> { + self.purpose.as_ref() + } + + /// Set the format of the input descriptor. + /// + /// The Input Descriptor Object MAY contain a format property. If present, + /// its value MUST be an object with one or more properties matching the registered + /// Claim Format Designations (e.g., jwt, jwt_vc, jwt_vp, etc.). + /// + /// This format property is identical in value signature to the top-level format object, + /// but can be used to specifically constrain submission of a single input to a subset of formats or algorithms. + pub fn set_format(mut self, format: ClaimFormatMap) -> Self { + self.format = Some(format); + self + } + + /// Return the format of the input descriptor. + /// + /// The Input Descriptor Object MAY contain a format property. If present, + /// its value MUST be an object with one or more properties matching the registered + /// Claim Format Designations (e.g., jwt, jwt_vc, jwt_vp, etc.). + /// + /// This format property is identical in value signature to the top-level format object, + /// but can be used to specifically constrain submission of a single input to a subset of formats or algorithms. + pub fn format(&self) -> Option<&ClaimFormatMap> { + self.format.as_ref() + } +} + +/// Constraints are objects used to describe the constraints that a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder) must satisfy to fulfill an Input Descriptor. +/// +/// A constraint object MAY be empty, or it may include a `fields` and/or `limit_disclosure` property. +/// +/// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object) +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct Constraints { #[serde(skip_serializing_if = "Option::is_none")] - pub fields: Option>, + fields: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub limit_disclosure: Option, + limit_disclosure: Option, } +impl Constraints { + /// Returns an empty Constraints object. + pub fn new() -> Self { + Self::default() + } + + /// Add a new field constraint to the constraints list. + pub fn add_constraint(mut self, field: ConstraintsField) -> Self { + self.fields.get_or_insert_with(Vec::new).push(field); + self + } + + /// Returns the fields of the constraints object. + pub fn fields(&self) -> Option<&Vec> { + self.fields.as_ref() + } + + /// Set the limit disclosure value. + /// + /// For all [Claims](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claims) submitted in relation to [InputDescriptor] Objects that include a `constraints` + /// object with a `limit_disclosure` property set to the string value `required`, + /// ensure that the data submitted is limited to the entries specified in the `fields` property of the `constraints` object. + /// If the `fields` property IS NOT present, or contains zero field objects, the submission SHOULD NOT include any data from the Claim. + /// + /// For example, a [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) may simply want to know whether a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder) has a valid, signed [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim) of a particular type, + /// without disclosing any of the data it contains. + /// + /// For more information: see [https://identity.foundation/presentation-exchange/spec/v2.0.0/#limited-disclosure-submissions](https://identity.foundation/presentation-exchange/spec/v2.0.0/#limited-disclosure-submissions) + pub fn set_limit_disclosure(mut self, limit_disclosure: ConstraintsLimitDisclosure) -> Self { + self.limit_disclosure = Some(limit_disclosure); + self + } + + /// Returns the limit disclosure value. + pub fn limit_disclosure(&self) -> Option<&ConstraintsLimitDisclosure> { + self.limit_disclosure.as_ref() + } +} + +/// ConstraintsField objects are used to describe the constraints that a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder) must satisfy to fulfill an Input Descriptor. +/// +/// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct ConstraintsField { - pub path: NonEmptyVec, // TODO JsonPath validation at deserialization time + // JSON Regex path -> check regex against JSON structure to check if there is a match; + // TODO JsonPath validation at deserialization time + // Regular expression includes the path -> whether or not the JSON object contains a property. + path: NonEmptyVec, #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, + id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub purpose: Option, + purpose: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + name: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option, // TODO JSONSchema validation at deserialization time + filter: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub optional: Option, + optional: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub intent_to_retain: Option, + intent_to_retain: Option, } pub type ConstraintsFields = Vec; +impl From> for ConstraintsField { + fn from(path: NonEmptyVec) -> Self { + Self { + path, + id: None, + purpose: None, + name: None, + filter: None, + optional: None, + intent_to_retain: None, + } + } +} + impl ConstraintsField { - pub fn new( - path: NonEmptyVec, - id: Option, - purpose: Option, - name: Option, - filter: Option, - optional: Option, - intent_to_retain: Option, - ) -> ConstraintsField { + /// Create a new instance of the constraints field with the given path. + /// + /// Tip: Use the [ConstraintsField::From](ConstraintsField::From) trait to convert a [NonEmptyVec](NonEmptyVec) of + /// [JsonPath](JsonPath) to a [ConstraintsField](ConstraintsField) if more than one path is known. + pub fn new(path: JsonPath) -> ConstraintsField { ConstraintsField { - path, - id, - purpose, - name, - filter, - optional, - intent_to_retain, + path: NonEmptyVec::new(path), + id: None, + purpose: None, + name: None, + filter: None, + optional: None, + intent_to_retain: None, } } + + /// Add a new path to the constraints field. + pub fn add_path(mut self, path: JsonPath) -> Self { + self.path.push(path); + self + } + + /// Return the paths of the constraints field. + /// + /// `path` is a non empty list of [JsonPath](https://goessner.net/articles/JsonPath/) expressions. + /// + /// For syntax definition, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#jsonpath-syntax-definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#jsonpath-syntax-definition) + pub fn path(&self) -> &NonEmptyVec { + &self.path + } + + /// Set the id of the constraints field. + /// + /// The fields object MAY contain an id property. If present, its value MUST be a string that + /// is unique from every other field object’s id property, including those contained in other + /// Input Descriptor Objects. + pub fn set_id(mut self, id: String) -> Self { + self.id = Some(id); + self + } + + /// Return the id of the constraints field. + pub fn id(&self) -> Option<&String> { + self.id.as_ref() + } + + /// Set the purpose of the constraints field. + /// + /// If present, its value MUST be a string that describes the purpose for which the field is being requested. + pub fn set_purpose(mut self, purpose: String) -> Self { + self.purpose = Some(purpose); + self + } + + /// Return the purpose of the constraints field. + pub fn purpose(&self) -> Option<&String> { + self.purpose.as_ref() + } + + /// Set the name of the constraints field. + /// + /// If present, its value MUST be a string, and SHOULD be a human-friendly + /// name that describes what the target field represents. + /// + /// For example, the name of the constraint could be "over_18" if the field is a date of birth. + pub fn set_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + /// Return the name of the constraints field. + pub fn name(&self) -> Option<&String> { + self.name.as_ref() + } + + /// Set the filter of the constraints field. + /// + /// If present its value MUST be a JSON Schema descriptor used to filter against + /// the values returned from evaluation of the JSONPath string expressions in the path array. + pub fn set_filter(mut self, filter: serde_json::Value) -> Self { + self.filter = Some(filter); + self + } + + /// Return the filter of the constraints field. + pub fn filter(&self) -> Option<&serde_json::Value> { + self.filter.as_ref() + } + + /// Set the optional value of the constraints field. + /// + /// The value of this property MUST be a boolean, wherein true indicates the + /// field is optional, and false or non-presence of the property indicates the + /// field is required. Even when the optional property is present, the value + /// located at the indicated path of the field MUST validate against the + /// JSON Schema filter, if a filter is present. + /// + /// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object) + pub fn set_optional(mut self, optional: bool) -> Self { + self.optional = Some(optional); + self + } + + /// Return the optional value of the constraints field. + pub fn optional(&self) -> bool { + self.optional.unwrap_or(false) + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -84,19 +607,124 @@ pub enum ConstraintsLimitDisclosure { Preferred, } +/// Presentation Submissions are objects embedded within target +/// [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim) negotiation +/// formats that express how the inputs presented as proofs to a +/// [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier) are +/// provided in accordance with the requirements specified in a [PresentationDefinition]. +/// +/// Embedded Presentation Submission objects MUST be located within target data format as +/// the value of a `presentation_submission` property. +/// +/// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct PresentationSubmission { - pub id: String, - pub definition_id: String, - pub descriptor_map: Vec, + id: uuid::Uuid, + definition_id: uuid::Uuid, + descriptor_map: Vec, +} + +impl PresentationSubmission { + /// The presentation submission MUST contain an id property. The value of this property MUST be a unique identifier, i.e. a UUID. + /// + /// The presentation submission object MUST contain a `definition_id` property. The value of this property MUST be the id value of a valid [PresentationDefinition::id()]. + pub fn new( + id: uuid::Uuid, + definition_id: uuid::Uuid, + descriptor_map: Vec, + ) -> Self { + Self { + id, + definition_id, + descriptor_map, + } + } + + /// Return the id of the presentation submission. + pub fn id(&self) -> &uuid::Uuid { + &self.id + } + + /// Return the definition id of the presentation submission. + pub fn definition_id(&self) -> &uuid::Uuid { + &self.definition_id + } + + /// Return the descriptor map of the presentation submission. + pub fn descriptor_map(&self) -> &Vec { + &self.descriptor_map + } + + /// Return a mutable reference to the descriptor map of the presentation submission. + pub fn descriptor_map_mut(&mut self) -> &mut Vec { + &mut self.descriptor_map + } } +/// Descriptor Maps are objects used to describe the information a [Holder](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:holder) provides to a [Verifier](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:verifier). +/// +/// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct DescriptorMap { - pub id: String, - pub format: String, // TODO should be enum of supported formats - pub path: String, - //pub path_nested: Option>, + id: String, + format: ClaimFormatDesignation, + path: JsonPath, + path_nested: Option>, +} + +impl DescriptorMap { + /// The descriptor map MUST include an `id` property. The value of this property MUST be a string that matches the `id` property of the [InputDescriptor::id()] in the Presentation Definition that this [PresentationSubmission] is related to. + /// + /// The descriptor map object MUST include a `format` property. The value of this property MUST be a string that matches one of the [ClaimFormatDesignation]. This denotes the data format of the [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim). + /// + /// The descriptor map object MUST include a `path` property. The value of this property MUST be a [JSONPath](https://goessner.net/articles/JsonPath/) string expression. The path property indicates the [Claim](https://identity.foundation/presentation-exchange/spec/v2.0.0/#term:claim) submitted in relation to the identified [InputDescriptor], when executed against the top-level of the object the [PresentationSubmission] is embedded within. + /// + /// For more information, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) + pub fn new(id: String, format: ClaimFormatDesignation, path: JsonPath) -> Self { + Self { + id, + format, + path, + path_nested: None, + } + } + + /// Return the id of the descriptor map. + pub fn id(&self) -> &String { + &self.id + } + + /// Return the format of the descriptor map. + pub fn format(&self) -> &ClaimFormatDesignation { + &self.format + } + + /// Return the path of the descriptor map. + pub fn path(&self) -> &JsonPath { + &self.path + } + + /// Set the nested path of the descriptor map. + /// + /// The format of a path_nested object mirrors that of a [DescriptorMap] property. The nesting may be any number of levels deep. + /// The `id` property MUST be the same for each level of nesting. + /// + /// The path property inside each `path_nested` property provides a relative path within a given nested value. + /// + /// For more information on nested paths, see: [https://identity.foundation/presentation-exchange/spec/v2.0.0/#processing-of-submission-entries](https://identity.foundation/presentation-exchange/spec/v2.0.0/#processing-of-submission-entries) + /// + /// Errors: + /// - The id of the nested path must be the same as the parent id. + pub fn set_path_nested(mut self, path_nested: DescriptorMap) -> Result { + // Check the id of the nested path is the same as the parent id. + if path_nested.id() != self.id() { + bail!("The id of the nested path must be the same as the parent id.") + } + + self.path_nested = Some(Box::new(path_nested)); + + Ok(self) + } } #[derive(Deserialize)] @@ -151,10 +779,10 @@ pub(crate) mod tests { fn request_example() { let value = json!( { - "id": "vp token example", + "id": "36682080-c2ed-4ba6-a4cd-37c86ef2da8c", "input_descriptors": [ { - "id": "id card credential", + "id": "d05a7f51-ac09-43af-8864-e00f0175f2c7", "format": { "ldp_vc": { "proof_type": [ @@ -202,7 +830,7 @@ pub(crate) mod tests { continue; } } - print!("{} -> ", path.file_name().unwrap().to_str().unwrap()); + println!("{} -> ", path.file_name().unwrap().to_str().unwrap()); let file = File::open(path).unwrap(); let jd = &mut serde_json::Deserializer::from_reader(file.try_clone().unwrap()); let _: PresentationDefinitionTest = serde_path_to_error::deserialize(jd) @@ -235,7 +863,7 @@ pub(crate) mod tests { continue; } } - print!("{} -> ", path.file_name().unwrap().to_str().unwrap()); + println!("{} -> ", path.file_name().unwrap().to_str().unwrap()); let file = File::open(path).unwrap(); let jd = &mut serde_json::Deserializer::from_reader(file.try_clone().unwrap()); let _: PresentationSubmissionTest = serde_path_to_error::deserialize(jd) diff --git a/tests/e2e.rs b/tests/e2e.rs index 7a6e47a..b79d689 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -20,14 +20,15 @@ async fn w3c_vc_did_client_direct_post() { "id": "0b4dd017-efa6-4a05-a269-9790fa3c22c2", "input_descriptors": [ { - "id": "vc", + "id": "064255a8-a0fa-4108-9ded-429f83003350", "format": { "jwt_vc_json": { "proof_type": [ "JsonWebSignature2020" ] } - } + }, + "constraints": {} } ] })) @@ -68,9 +69,9 @@ async fn w3c_vc_did_client_direct_post() { "definition_id": "0b4dd017-efa6-4a05-a269-9790fa3c22c2", "descriptor_map": [ { - "id": "vc", + "id": "064255a8-a0fa-4108-9ded-429f83003350", "path": "$", - "format": "jwt_vc_json" + "format": "jwt_vp" } ] }