From bf8b3021eeedf2cb656ba8bf399ea088055e3d3a Mon Sep 17 00:00:00 2001 From: Enrico Marconi Date: Thu, 18 Jan 2024 09:53:24 +0100 Subject: [PATCH] credential status refactor --- identity_credential/Cargo.toml | 2 +- identity_credential/src/credential/mod.rs | 2 - .../credential/revocation_bitmap_status.rs | 42 ++--------- identity_credential/src/credential/status.rs | 73 ++----------------- .../revocation/status_list_2021/credential.rs | 15 ++-- .../src/revocation/status_list_2021/entry.rs | 19 +++-- .../jwt_credential_validator_utils.rs | 22 ++---- .../storage/tests/credential_validation.rs | 4 +- 8 files changed, 41 insertions(+), 138 deletions(-) diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 89f5bf5d61..0b2608162f 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -26,6 +26,7 @@ reqwest = { version = "0.11", default-features = false, features = ["default-tls roaring = { version = "0.10", default-features = false, optional = true } serde.workspace = true serde-aux = { version = "4.3.1", default-features = false, optional = true } +serde_json.workspace = true serde_repr = { version = "0.1", default-features = false, optional = true } strum.workspace = true thiserror.workspace = true @@ -36,7 +37,6 @@ anyhow = "1.0.62" identity_eddsa_verifier = { version = "=1.0.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "std", "random"] } proptest = { version = "1.0.0", default-features = false, features = ["std"] } -serde_json.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } [package.metadata.docs.rs] diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 7e50951e06..efa20a3c87 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -35,8 +35,6 @@ pub use self::refresh::RefreshService; #[cfg(feature = "revocation-bitmap")] pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; -pub use self::status::CredentialStatus; -pub use self::status::CustomStatus; pub use self::status::Status; pub use self::subject::Subject; diff --git a/identity_credential/src/credential/revocation_bitmap_status.rs b/identity_credential/src/credential/revocation_bitmap_status.rs index 5d079c3296..d4310d154a 100644 --- a/identity_credential/src/credential/revocation_bitmap_status.rs +++ b/identity_credential/src/credential/revocation_bitmap_status.rs @@ -12,12 +12,10 @@ use crate::credential::Status; use crate::error::Error; use crate::error::Result; -use super::CustomStatus; - /// Information used to determine the current status of a [`Credential`][crate::credential::Credential] /// using the `RevocationBitmap2022` specification. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RevocationBitmapStatus(CustomStatus); +pub struct RevocationBitmapStatus(Status); impl RevocationBitmapStatus { const INDEX_PROPERTY: &'static str = "revocationBitmapIndex"; @@ -48,7 +46,7 @@ impl RevocationBitmapStatus { let mut object = Object::new(); object.insert(Self::INDEX_PROPERTY.to_owned(), Value::String(index.to_string())); - RevocationBitmapStatus(CustomStatus::new_with_properties( + RevocationBitmapStatus(Status::new_with_properties( Url::from(id), Self::TYPE.to_owned(), object, @@ -79,23 +77,6 @@ impl TryFrom for RevocationBitmapStatus { type Error = Error; fn try_from(status: Status) -> Result { - use crate::credential::CredentialStatus; - - match status { - Status::StatusList2021(s) => Err(Self::Error::InvalidStatus(format!( - "expected type '{}', got '{}'", - Self::TYPE, - s.type_() - ))), - Status::Other(s) => s.try_into(), - } - } -} - -impl TryFrom for RevocationBitmapStatus { - type Error = Error; - - fn try_from(status: CustomStatus) -> Result { if status.type_ != Self::TYPE { return Err(Error::InvalidStatus(format!( "expected type '{}', got '{}'", @@ -143,13 +124,7 @@ impl TryFrom for RevocationBitmapStatus { impl From for Status { fn from(status: RevocationBitmapStatus) -> Self { - Status::Other(status.0) - } -} - -impl From for CustomStatus { - fn from(value: RevocationBitmapStatus) -> Self { - value.0 + status.0 } } @@ -170,7 +145,6 @@ mod tests { use identity_core::convert::FromJson; use identity_did::DIDUrl; - use crate::credential::CustomStatus; use crate::Error; use super::RevocationBitmapStatus; @@ -188,15 +162,15 @@ mod tests { RevocationBitmapStatus::INDEX_PROPERTY.to_owned(), Value::String(revocation_list_index.to_string()), )]); - let status: CustomStatus = - CustomStatus::new_with_properties(url.clone(), RevocationBitmapStatus::TYPE.to_owned(), object.clone()); + let status: Status = + Status::new_with_properties(url.clone(), RevocationBitmapStatus::TYPE.to_owned(), object.clone()); assert_eq!(embedded_revocation_status, status.try_into().unwrap()); - let status_missing_property: CustomStatus = - CustomStatus::new_with_properties(url.clone(), RevocationBitmapStatus::TYPE.to_owned(), Object::new()); + let status_missing_property: Status = + Status::new_with_properties(url.clone(), RevocationBitmapStatus::TYPE.to_owned(), Object::new()); assert!(RevocationBitmapStatus::try_from(status_missing_property).is_err()); - let status_wrong_type: CustomStatus = CustomStatus::new_with_properties(url, "DifferentType".to_owned(), object); + let status_wrong_type: Status = Status::new_with_properties(url, "DifferentType".to_owned(), object); assert!(RevocationBitmapStatus::try_from(status_wrong_type).is_err()); } diff --git a/identity_credential/src/credential/status.rs b/identity_credential/src/credential/status.rs index 9a05ab21e9..bc5d0d3f8d 100644 --- a/identity_credential/src/credential/status.rs +++ b/identity_credential/src/credential/status.rs @@ -7,63 +7,11 @@ use serde::Serialize; use identity_core::common::Object; use identity_core::common::Url; -#[cfg(feature = "revocation-bitmap")] -use crate::revocation::status_list_2021::StatusList2021Entry; - -/// Credential status interface -pub trait CredentialStatus { - /// `credentialStatus.type` field - fn type_(&self) -> &str; - /// `credentialStatus.id` field - fn id(&self) -> &Url; -} - /// Information used to determine the current status of a [`Credential`][crate::credential::Credential]. /// /// [More Info](https://www.w3.org/TR/vc-data-model/#status) -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Status { - /// CredentialStatus using [`StatusList2021Entry`] - #[cfg(feature = "revocation-bitmap")] - StatusList2021(StatusList2021Entry), - /// Any other status - Other(CustomStatus), -} - -impl From> for Status { - fn from(value: CustomStatus) -> Self { - Status::Other(value) - } -} - -#[cfg(feature = "revocation-bitmap")] -impl From for Status { - fn from(value: StatusList2021Entry) -> Self { - Status::StatusList2021(value) - } -} - -impl CredentialStatus for Status { - fn id(&self) -> &Url { - match self { - #[cfg(feature = "revocation-bitmap")] - Self::StatusList2021(s) => s.id(), - Self::Other(s) => s.id(), - } - } - fn type_(&self) -> &str { - match self { - #[cfg(feature = "revocation-bitmap")] - Self::StatusList2021(s) => s.type_(), - Self::Other(s) => s.type_(), - } - } -} - #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -/// A weakly type credential status that can encode any status -pub struct CustomStatus { +pub struct Status { /// A Url identifying the credential status. pub id: Url, /// The type(s) of the credential status. @@ -74,23 +22,14 @@ pub struct CustomStatus { pub properties: T, } -impl CredentialStatus for CustomStatus { - fn id(&self) -> &Url { - &self.id - } - fn type_(&self) -> &str { - &self.type_ - } -} - -impl CustomStatus { +impl Status { /// Creates a new `Status`. pub fn new(id: Url, type_: String) -> Self { Self::new_with_properties(id, type_, Object::new()) } } -impl CustomStatus { +impl Status { /// Creates a new `Status` with the given `properties`. pub fn new_with_properties(id: Url, type_: String, properties: T) -> Self { Self { id, type_, properties } @@ -107,8 +46,8 @@ mod tests { #[test] fn test_from_json() { - let status = Status::from_json(JSON).unwrap(); - assert_eq!(status.id().as_str(), "https://example.edu/status/24"); - assert_eq!(status.type_(), "CredentialStatusList2017"); + let status: Status = Status::from_json(JSON).unwrap(); + assert_eq!(status.id.as_str(), "https://example.edu/status/24"); + assert_eq!(status.type_, "CredentialStatusList2017"); } } diff --git a/identity_credential/src/revocation/status_list_2021/credential.rs b/identity_credential/src/revocation/status_list_2021/credential.rs index 1d7c14ec5d..f8f7b23407 100644 --- a/identity_credential/src/revocation/status_list_2021/credential.rs +++ b/identity_credential/src/revocation/status_list_2021/credential.rs @@ -34,7 +34,6 @@ use crate::credential::Credential; use crate::credential::CredentialBuilder; use crate::credential::Issuer; use crate::credential::Proof; -use crate::credential::Status; use super::status_list::StatusListError; use super::StatusList2021; @@ -92,23 +91,19 @@ impl StatusList2021Credential { /// Sets the credential status of a given [`Credential`], /// mapping it to the `index`-th entry of this [`StatusList2021Credential`]. - pub fn set_credential_status<'c>( + pub fn set_credential_status( &mut self, - credential: &'c mut Credential, + credential: &mut Credential, index: usize, value: bool, - ) -> Result<&'c StatusList2021Entry, StatusListError> { + ) -> Result { let mut status_list = self.status_list()?; let entry = StatusList2021Entry::new(self.id.clone().unwrap(), self.purpose(), index); status_list.set(index, value)?; - credential.credential_status = Some(entry.into()); + credential.credential_status = Some(entry.clone().into()); - let entry_ref = match credential.credential_status.as_ref().unwrap() { - Status::StatusList2021(ref entry) => entry, - _ => unreachable!(), - }; - Ok(entry_ref) + Ok(entry) } /// Returns the status of the `index-th` entry. diff --git a/identity_credential/src/revocation/status_list_2021/entry.rs b/identity_credential/src/revocation/status_list_2021/entry.rs index 627cb3ff74..d9d7be3da5 100644 --- a/identity_credential/src/revocation/status_list_2021/entry.rs +++ b/identity_credential/src/revocation/status_list_2021/entry.rs @@ -5,8 +5,9 @@ use identity_core::common::Url; use serde::Deserialize; use serde::Serialize; +use crate::credential::Status; + use super::credential::StatusPurpose; -use crate::credential::CredentialStatus; const CREDENTIAL_STATUS_TYPE: &str = "StatusList2021Entry"; @@ -61,12 +62,18 @@ pub struct StatusList2021Entry { status_list_credential: Url, } -impl CredentialStatus for StatusList2021Entry { - fn id(&self) -> &Url { - &self.id +impl TryFrom<&Status> for StatusList2021Entry { + type Error = serde_json::Error; + fn try_from(status: &Status) -> Result { + let json_status = serde_json::to_value(status).unwrap(); + serde_json::from_value(json_status) } - fn type_(&self) -> &str { - self.type_.0 +} + +impl From for Status { + fn from(entry: StatusList2021Entry) -> Self { + let json_status = serde_json::to_value(entry).unwrap(); // Safety: shouldn't go out of memory + serde_json::from_value(json_status).unwrap() // Safety: `StatusList2021Entry` is a credential status } } diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_utils.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_utils.rs index b49536fa80..7dda9bda69 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_utils.rs @@ -95,8 +95,7 @@ impl JwtCredentialValidatorUtils { status_list_credential: &StatusList2021Credential, status_check: crate::validator::StatusCheck, ) -> ValidationUnitResult { - use crate::credential::CredentialStatus; - use crate::credential::Status; + use crate::revocation::status_list_2021::StatusList2021Entry; if status_check == crate::validator::StatusCheck::SkipAll { return Ok(()); @@ -104,7 +103,9 @@ impl JwtCredentialValidatorUtils { match &credential.credential_status { None => Ok(()), - Some(Status::StatusList2021(status)) => { + Some(status) => { + let status = StatusList2021Entry::try_from(status) + .map_err(|e| JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(e.to_string())))?; if Some(status.credential()) == status_list_credential.id.as_ref() && status.purpose() == status_list_credential.purpose() { @@ -124,16 +125,6 @@ impl JwtCredentialValidatorUtils { ))) } } - Some(Status::Other(status)) => { - if status_check == crate::validator::StatusCheck::SkipUnsupported { - Ok(()) - } else { - Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( - "unsupported type '{}'", - status.type_() - )))) - } - } } } /// Checks whether the credential status has been revoked. @@ -145,7 +136,6 @@ impl JwtCredentialValidatorUtils { trusted_issuers: &[DOC], status_check: crate::validator::StatusCheck, ) -> ValidationUnitResult { - use crate::credential::CredentialStatus; use identity_did::CoreDID; use identity_document::document::CoreDocument; @@ -157,13 +147,13 @@ impl JwtCredentialValidatorUtils { None => Ok(()), Some(status) => { // Check status is supported. - if status.type_() != crate::revocation::RevocationBitmap::TYPE { + if status.type_ != crate::revocation::RevocationBitmap::TYPE { if status_check == crate::validator::StatusCheck::SkipUnsupported { return Ok(()); } return Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( "unsupported type '{}'", - status.type_() + status.type_ )))); } let status: crate::credential::RevocationBitmapStatus = diff --git a/identity_storage/src/storage/tests/credential_validation.rs b/identity_storage/src/storage/tests/credential_validation.rs index 09d468b8b3..d827cfef94 100644 --- a/identity_storage/src/storage/tests/credential_validation.rs +++ b/identity_storage/src/storage/tests/credential_validation.rs @@ -5,9 +5,9 @@ use identity_core::common::Duration; use identity_core::common::Object; use identity_core::common::Timestamp; use identity_core::common::Url; -use identity_credential::credential::CustomStatus; use identity_credential::credential::Jwt; use identity_credential::credential::RevocationBitmapStatus; +use identity_credential::credential::Status; use identity_credential::revocation::RevocationBitmap; use identity_credential::revocation::RevocationDocumentExt; use identity_credential::validator::FailFast; @@ -320,7 +320,7 @@ where // 1: unsupported status type. credential.credential_status = Some( - CustomStatus::new( + Status::new( Url::parse("https://example.com/").unwrap(), "UnsupportedStatus2023".to_owned(), )