From 51d5f6ef6429fbda591f915d77d0651bd238bc10 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Fri, 30 Aug 2024 17:04:28 -0700 Subject: [PATCH 1/4] add a credential type method accessor to check if a credential is defined in the input descriptor Signed-off-by: Ryan Tate --- src/core/input_descriptor.rs | 81 ++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/src/core/input_descriptor.rs b/src/core/input_descriptor.rs index 956f66b..c02bf83 100644 --- a/src/core/input_descriptor.rs +++ b/src/core/input_descriptor.rs @@ -1,9 +1,12 @@ +use std::collections::HashSet; + use super::{credential_format::*, presentation_submission::*}; use crate::utils::NonEmptyVec; use anyhow::{bail, Context, Result}; use jsonschema::{JSONSchema, ValidationError}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use ssi_claims::jwt::VerifiablePresentation; use ssi_dids::ssi_json_ld::syntax::from_value; @@ -135,6 +138,72 @@ impl InputDescriptor { self } + /// Return the format of the input descriptor. + pub fn format(&self) -> Option<&ClaimFormatMap> { + self.format.as_ref() + } + + /// Return the format designations of the input descriptor as a hash set. + pub fn format_designations(&self) -> Option> { + self.format.as_ref().map(|f| f.keys().collect()) + } + + /// Return the credential format(s) of the input descriptor, if it can be determined + /// from the format field of the input descriptor constraints fields. + pub fn credential_type(&self) -> Option> { + self.constraints.fields.as_ref().map(|fields| { + fields + .iter() + .filter(|field| { + // Check if field path contains "type" + field + .path + .as_ref() + .iter() + // Check if any of the paths contain a reference to type. + // NOTE: I am not sure if this is normative to add a `type` field to the path + // for a verifiable credential. + .any(|path| path.contains(&"type".to_string())) + }) + .filter_map(|field| { + // Check the filter field to determine what the `const`. + // Use this to determine what the credential type is. + if let Some(credential) = field + .filter + .as_ref() + .map(|filter| { + filter + .get("const") + .and_then(Value::as_str) + .map(CredentialType::from) + }) + .flatten() + { + return Some(credential); + } + + // The `type` field may be an array with a nested const value. + if let Some(credential) = field + .filter + .as_ref() + .map(|filter| { + filter + .get("contains") + .and_then(|value| value.get("const")) + .and_then(Value::as_str) + .map(CredentialType::from) + }) + .flatten() + { + return Some(credential); + } + + None + }) + .collect() + }) + } + /// Set the group of the constraints field. pub fn set_group(mut self, group: Vec) -> Self { self.group = Some(group); @@ -257,18 +326,6 @@ impl InputDescriptor { Ok(()) } - - /// 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. From 027f6ed647f607d3a7827425e5b363b08a65122e Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Mon, 2 Sep 2024 16:46:14 -0700 Subject: [PATCH 2/4] parse the credential type from an input descriptor constraint field Signed-off-by: Ryan Tate --- src/core/credential_format/mod.rs | 6 ++ src/core/input_descriptor.rs | 123 ++++++++++++++++-------------- 2 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/core/credential_format/mod.rs b/src/core/credential_format/mod.rs index a206357..2b61afb 100644 --- a/src/core/credential_format/mod.rs +++ b/src/core/credential_format/mod.rs @@ -250,6 +250,12 @@ impl From for String { } } +impl std::fmt::Display for ClaimFormatDesignation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", String::from(self.clone())) + } +} + /// Credential types that may be requested in a credential request. /// /// Credential types can be presented in a number of formats. diff --git a/src/core/input_descriptor.rs b/src/core/input_descriptor.rs index c02bf83..3031bed 100644 --- a/src/core/input_descriptor.rs +++ b/src/core/input_descriptor.rs @@ -148,62 +148,6 @@ impl InputDescriptor { self.format.as_ref().map(|f| f.keys().collect()) } - /// Return the credential format(s) of the input descriptor, if it can be determined - /// from the format field of the input descriptor constraints fields. - pub fn credential_type(&self) -> Option> { - self.constraints.fields.as_ref().map(|fields| { - fields - .iter() - .filter(|field| { - // Check if field path contains "type" - field - .path - .as_ref() - .iter() - // Check if any of the paths contain a reference to type. - // NOTE: I am not sure if this is normative to add a `type` field to the path - // for a verifiable credential. - .any(|path| path.contains(&"type".to_string())) - }) - .filter_map(|field| { - // Check the filter field to determine what the `const`. - // Use this to determine what the credential type is. - if let Some(credential) = field - .filter - .as_ref() - .map(|filter| { - filter - .get("const") - .and_then(Value::as_str) - .map(CredentialType::from) - }) - .flatten() - { - return Some(credential); - } - - // The `type` field may be an array with a nested const value. - if let Some(credential) = field - .filter - .as_ref() - .map(|filter| { - filter - .get("contains") - .and_then(|value| value.get("const")) - .and_then(Value::as_str) - .map(CredentialType::from) - }) - .flatten() - { - return Some(credential); - } - - None - }) - .collect() - }) - } - /// Set the group of the constraints field. pub fn set_group(mut self, group: Vec) -> Self { self.group = Some(group); @@ -386,6 +330,16 @@ impl Constraints { .map(|fields| fields.iter().any(|field| field.is_required())) .unwrap_or(false) } + + /// Intent to retain. + /// + /// Returns whether any of the contraint fields have an intent + /// to retain the data by the verifier. + pub fn intend_to_retain(&self) -> bool { + self.fields() + .map(|fields| fields.iter().any(|field| field.intent_to_retain())) + .unwrap_or(false) + } } /// ConstraintsField objects are used to describe the constraints that a @@ -657,6 +611,63 @@ impl ConstraintsField { }) .collect() } + + /// Return the Credential Type of the constraints field + pub fn credential_type(&self) -> Option { + // NOTE: There may be other ways to search for a valid the credential type + // that meets the input descriptor constraints. + // + // A more exhaustive search may require parsing each credential to + // check if it contains a certain field, e.g. `firstName`, `familyName`, etc., + // and see if it will satisfy the constraints. + // + // For now, we explicity check the type of the credential if it is present + // in the credential `type` field. + + if self + .path + .as_ref() + .iter() + // Check if any of the paths contain a reference to type. + // NOTE: I am not sure if this is normative to add a `type` field to the path + // for a verifiable credential. + .any(|path| path.contains(&"type".to_string())) + { + // Check the filter field to determine the `const` + // value for the credential type, e.g. `iso.org.18013.5.1.mDL`, etc. + if let Some(credential) = self + .filter + .as_ref() + .map(|filter| { + filter + .get("const") + .and_then(Value::as_str) + .map(CredentialType::from) + }) + .flatten() + { + return Some(credential); + } + + // The `type` field may be an array with a nested const value. + if let Some(credential) = self + .filter + .as_ref() + .map(|filter| { + filter + .get("contains") + .and_then(|value| value.get("const")) + .and_then(Value::as_str) + .map(CredentialType::from) + }) + .flatten() + { + return Some(credential); + } + } + + None + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] From ecc17e41139518f7b3ba30be9e72ab27ce1908e3 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Mon, 2 Sep 2024 16:53:09 -0700 Subject: [PATCH 3/4] fix clippy errors Signed-off-by: Ryan Tate --- src/core/credential_format/mod.rs | 2 +- src/holder/verifiable_presentation_builder.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/credential_format/mod.rs b/src/core/credential_format/mod.rs index 2b61afb..370a1d0 100644 --- a/src/core/credential_format/mod.rs +++ b/src/core/credential_format/mod.rs @@ -298,6 +298,6 @@ impl From<&CredentialType> for String { impl std::fmt::Display for &CredentialType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self) + write!(f, "{}", String::from(*self)) } } diff --git a/src/holder/verifiable_presentation_builder.rs b/src/holder/verifiable_presentation_builder.rs index 8a63faf..5ca1bac 100644 --- a/src/holder/verifiable_presentation_builder.rs +++ b/src/holder/verifiable_presentation_builder.rs @@ -111,7 +111,7 @@ impl VerifiablePresentationBuilder { pub fn as_base64_encoded_vp_token(self) -> Result { let json_string = serde_json::to_string(&self.0).context("Failed to encode JSON string")?; - let token = BASE64_STANDARD.encode(&json_string); + let token = BASE64_STANDARD.encode(json_string); Ok(VpToken(token)) } From 068636121c1441755c92c6f3eddfd54d00212a55 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Mon, 2 Sep 2024 16:55:59 -0700 Subject: [PATCH 4/4] fix clippy errors Signed-off-by: Ryan Tate --- src/core/input_descriptor.rs | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/core/input_descriptor.rs b/src/core/input_descriptor.rs index 3031bed..6bd60db 100644 --- a/src/core/input_descriptor.rs +++ b/src/core/input_descriptor.rs @@ -635,33 +635,23 @@ impl ConstraintsField { { // Check the filter field to determine the `const` // value for the credential type, e.g. `iso.org.18013.5.1.mDL`, etc. - if let Some(credential) = self - .filter - .as_ref() - .map(|filter| { - filter - .get("const") - .and_then(Value::as_str) - .map(CredentialType::from) - }) - .flatten() - { + if let Some(credential) = self.filter.as_ref().and_then(|filter| { + filter + .get("const") + .and_then(Value::as_str) + .map(CredentialType::from) + }) { return Some(credential); } // The `type` field may be an array with a nested const value. - if let Some(credential) = self - .filter - .as_ref() - .map(|filter| { - filter - .get("contains") - .and_then(|value| value.get("const")) - .and_then(Value::as_str) - .map(CredentialType::from) - }) - .flatten() - { + if let Some(credential) = self.filter.as_ref().and_then(|filter| { + filter + .get("contains") + .and_then(|value| value.get("const")) + .and_then(Value::as_str) + .map(CredentialType::from) + }) { return Some(credential); } }