Skip to content

Commit

Permalink
credential status refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
UMR1352 committed Jan 18, 2024
1 parent 7993eb3 commit bf8b302
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 138 deletions.
2 changes: 1 addition & 1 deletion identity_credential/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down
2 changes: 0 additions & 2 deletions identity_credential/src/credential/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
42 changes: 8 additions & 34 deletions identity_credential/src/credential/revocation_bitmap_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -79,23 +77,6 @@ impl TryFrom<Status> for RevocationBitmapStatus {
type Error = Error;

fn try_from(status: Status) -> Result<Self> {
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<CustomStatus> for RevocationBitmapStatus {
type Error = Error;

fn try_from(status: CustomStatus) -> Result<Self> {
if status.type_ != Self::TYPE {
return Err(Error::InvalidStatus(format!(
"expected type '{}', got '{}'",
Expand Down Expand Up @@ -143,13 +124,7 @@ impl TryFrom<CustomStatus> for RevocationBitmapStatus {

impl From<RevocationBitmapStatus> for Status {
fn from(status: RevocationBitmapStatus) -> Self {
Status::Other(status.0)
}
}

impl From<RevocationBitmapStatus> for CustomStatus {
fn from(value: RevocationBitmapStatus) -> Self {
value.0
status.0
}
}

Expand All @@ -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;
Expand All @@ -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());
}

Expand Down
73 changes: 6 additions & 67 deletions identity_credential/src/credential/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = Object> {
/// CredentialStatus using [`StatusList2021Entry`]
#[cfg(feature = "revocation-bitmap")]
StatusList2021(StatusList2021Entry),
/// Any other status
Other(CustomStatus<T>),
}

impl<T> From<CustomStatus<T>> for Status<T> {
fn from(value: CustomStatus<T>) -> Self {
Status::Other(value)
}
}

#[cfg(feature = "revocation-bitmap")]
impl<T> From<StatusList2021Entry> for Status<T> {
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<T = Object> {
pub struct Status<T = Object> {
/// A Url identifying the credential status.
pub id: Url,
/// The type(s) of the credential status.
Expand All @@ -74,23 +22,14 @@ pub struct CustomStatus<T = Object> {
pub properties: T,
}

impl<T> CredentialStatus for CustomStatus<T> {
fn id(&self) -> &Url {
&self.id
}
fn type_(&self) -> &str {
&self.type_
}
}

impl CustomStatus<Object> {
impl Status<Object> {
/// Creates a new `Status`.
pub fn new(id: Url, type_: String) -> Self {
Self::new_with_properties(id, type_, Object::new())
}
}

impl<T> CustomStatus<T> {
impl<T> Status<T> {
/// Creates a new `Status` with the given `properties`.
pub fn new_with_properties(id: Url, type_: String, properties: T) -> Self {
Self { id, type_, properties }
Expand All @@ -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");
}
}
15 changes: 5 additions & 10 deletions identity_credential/src/revocation/status_list_2021/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<StatusList2021Entry, StatusListError> {
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.
Expand Down
19 changes: 13 additions & 6 deletions identity_credential/src/revocation/status_list_2021/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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<Self, Self::Error> {
let json_status = serde_json::to_value(status).unwrap();
serde_json::from_value(json_status)
}
fn type_(&self) -> &str {
self.type_.0
}

impl From<StatusList2021Entry> 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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,17 @@ 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(());
}

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()
{
Expand All @@ -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.
Expand All @@ -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;

Expand All @@ -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 =
Expand Down
4 changes: 2 additions & 2 deletions identity_storage/src/storage/tests/credential_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
)
Expand Down

0 comments on commit bf8b302

Please sign in to comment.