-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
345 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
syntax = "proto3"; | ||
package status_list_2021; | ||
|
||
enum Purpose { | ||
REVOCATION = 0; | ||
SUSPENSION = 1; | ||
} | ||
|
||
message CreateRequest { | ||
Purpose purpose = 1; | ||
optional uint64 length = 2; | ||
optional string id = 3; | ||
optional string expiration_date = 4; | ||
repeated string contexts = 5; | ||
repeated string types = 6; | ||
string issuer = 7; | ||
} | ||
|
||
message StatusListCredential { | ||
string credential_json = 1; | ||
} | ||
|
||
message UpdateRequest { | ||
string credential_json = 1; | ||
map<uint64, bool> entries = 2; | ||
} | ||
|
||
service StatusList2021Svc { | ||
rpc create(CreateRequest) returns(StatusListCredential); | ||
rpc update(UpdateRequest) returns(StatusListCredential); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
use identity_iota::core::Context; | ||
use identity_iota::core::FromJson; | ||
use identity_iota::core::Timestamp; | ||
use identity_iota::core::ToJson; | ||
use identity_iota::core::Url; | ||
use identity_iota::credential::status_list_2021::StatusList2021; | ||
use identity_iota::credential::status_list_2021::StatusList2021Credential; | ||
use identity_iota::credential::status_list_2021::StatusList2021CredentialBuilder; | ||
use identity_iota::credential::status_list_2021::StatusList2021CredentialError; | ||
use identity_iota::credential::status_list_2021::StatusPurpose; | ||
use identity_iota::credential::Issuer; | ||
use identity_iota::credential::{self}; | ||
|
||
use _status_list_2021::status_list2021_svc_server::StatusList2021Svc; | ||
use _status_list_2021::status_list2021_svc_server::StatusList2021SvcServer; | ||
use _status_list_2021::CreateRequest; | ||
use _status_list_2021::Purpose; | ||
use _status_list_2021::StatusListCredential; | ||
use _status_list_2021::UpdateRequest; | ||
use tonic::Code; | ||
use tonic::Request; | ||
use tonic::Response; | ||
use tonic::Status; | ||
|
||
mod _status_list_2021 { | ||
use identity_iota::credential::status_list_2021::StatusPurpose; | ||
|
||
tonic::include_proto!("status_list_2021"); | ||
|
||
impl From<Purpose> for StatusPurpose { | ||
fn from(value: Purpose) -> Self { | ||
match value { | ||
Purpose::Revocation => StatusPurpose::Revocation, | ||
Purpose::Suspension => StatusPurpose::Suspension, | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub enum Error { | ||
#[error("A valid status list must have at least 16KB entries")] | ||
InvalidStatusListLength, | ||
#[error("\"{0}\" is not a valid context")] | ||
InvalidContext(String), | ||
#[error("\"{0}\" is not a valid issuer")] | ||
InvalidIssuer(String), | ||
#[error("\"{0}\" is not a valid timestamp")] | ||
InvalidTimestamp(String), | ||
#[error("\"{0}\" is not a valid id")] | ||
InvalidId(String), | ||
#[error("Failed to deserialize into a valid StatusList2021Credential")] | ||
CredentialDeserializationError(#[source] identity_iota::core::Error), | ||
#[error(transparent)] | ||
CredentialError(#[from] credential::Error), | ||
#[error(transparent)] | ||
StatusListError(StatusList2021CredentialError), | ||
} | ||
|
||
impl From<Error> for Status { | ||
fn from(value: Error) -> Self { | ||
let code = match &value { | ||
Error::InvalidStatusListLength | ||
| Error::InvalidContext(_) | ||
| Error::InvalidIssuer(_) | ||
| Error::InvalidTimestamp(_) => Code::InvalidArgument, | ||
_ => Code::Internal, | ||
}; | ||
|
||
Status::new(code, value.to_string()) | ||
} | ||
} | ||
|
||
pub struct StatusList2021Service; | ||
|
||
#[tonic::async_trait] | ||
impl StatusList2021Svc for StatusList2021Service { | ||
#[tracing::instrument( | ||
name = "create_status_list_credential", | ||
skip_all, | ||
fields(request = ?req.get_ref()) | ||
ret, | ||
err, | ||
)] | ||
async fn create(&self, req: Request<CreateRequest>) -> Result<Response<StatusListCredential>, Status> { | ||
let CreateRequest { | ||
purpose, | ||
length, | ||
id, | ||
expiration_date, | ||
contexts, | ||
types, | ||
issuer, | ||
} = req.into_inner(); | ||
let status_list = length | ||
.map(|entries| StatusList2021::new(entries as usize)) | ||
.unwrap_or(Ok(StatusList2021::default())) | ||
.map_err(|_| Error::InvalidStatusListLength)?; | ||
|
||
let mut builder = StatusList2021CredentialBuilder::new(status_list); | ||
for ctx in contexts { | ||
let url = Url::parse(&ctx).map_err(move |_| Error::InvalidContext(ctx))?; | ||
builder = builder.context(Context::Url(url)); | ||
} | ||
for t in types { | ||
builder = builder.add_type(t); | ||
} | ||
let issuer = Url::parse(&issuer) | ||
.map_err(move |_| Error::InvalidIssuer(issuer)) | ||
.map(Issuer::Url)?; | ||
builder = builder.issuer(issuer); | ||
builder = builder.purpose(StatusPurpose::from(Purpose::try_from(purpose).unwrap())); | ||
if let Some(exp) = expiration_date { | ||
let exp = Timestamp::parse(&exp).map_err(move |_| Error::InvalidTimestamp(exp))?; | ||
builder = builder.expiration_date(exp); | ||
} | ||
if let Some(id) = id { | ||
let id = Url::parse(&id).map_err(move |_| Error::InvalidId(id))?; | ||
builder = builder.subject_id(id); | ||
} | ||
let status_list_credential = builder.build().map_err(Error::CredentialError)?; | ||
let res = StatusListCredential { | ||
credential_json: status_list_credential.to_json().unwrap(), | ||
}; | ||
|
||
Ok(Response::new(res)) | ||
} | ||
|
||
#[tracing::instrument( | ||
name = "update_status_list_credential", | ||
skip_all, | ||
fields(request = ?req.get_ref()) | ||
ret, | ||
err, | ||
)] | ||
async fn update(&self, req: Request<UpdateRequest>) -> Result<Response<StatusListCredential>, Status> { | ||
let UpdateRequest { | ||
credential_json, | ||
entries, | ||
} = req.into_inner(); | ||
let mut status_list_credential = | ||
StatusList2021Credential::from_json(&credential_json).map_err(Error::CredentialDeserializationError)?; | ||
|
||
status_list_credential | ||
.update(move |status_list| { | ||
for (idx, value) in entries { | ||
if let Err(e) = status_list.set_entry(idx as usize, value) { | ||
return Err(e); | ||
} | ||
} | ||
|
||
Ok(()) | ||
}) | ||
.map_err(Error::StatusListError)?; | ||
|
||
Ok(Response::new(StatusListCredential { | ||
credential_json: status_list_credential.to_json().unwrap(), | ||
})) | ||
} | ||
} | ||
|
||
pub fn service() -> StatusList2021SvcServer<StatusList2021Service> { | ||
StatusList2021SvcServer::new(StatusList2021Service) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ mod health_check; | |
mod helpers; | ||
mod jwt; | ||
mod sd_jwt_validation; | ||
mod status_list_2021; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
use crate::helpers::TestServer; | ||
use _status_list_2021::status_list2021_svc_client::StatusList2021SvcClient; | ||
use _status_list_2021::CreateRequest; | ||
use _status_list_2021::Purpose; | ||
use _status_list_2021::UpdateRequest; | ||
use identity_iota::core::FromJson; | ||
use identity_iota::core::ToJson; | ||
use identity_iota::core::Url; | ||
use identity_iota::credential::status_list_2021::StatusList2021; | ||
use identity_iota::credential::status_list_2021::StatusList2021Credential; | ||
use identity_iota::credential::status_list_2021::StatusList2021CredentialBuilder; | ||
use identity_iota::credential::status_list_2021::StatusPurpose; | ||
use identity_iota::credential::Issuer; | ||
use tonic::Request; | ||
|
||
mod _status_list_2021 { | ||
tonic::include_proto!("status_list_2021"); | ||
} | ||
|
||
#[tokio::test] | ||
async fn status_list_2021_credential_creation() -> anyhow::Result<()> { | ||
let server = TestServer::new().await; | ||
|
||
let id = Url::parse("http://example.com/credentials/status/1").unwrap(); | ||
let issuer = Issuer::Url(Url::parse("http://example.com/issuers/1").unwrap()); | ||
let status_list_credential = StatusList2021CredentialBuilder::new(StatusList2021::default()) | ||
.purpose(StatusPurpose::Revocation) | ||
.subject_id(id.clone()) | ||
.issuer(issuer.clone()) | ||
.build() | ||
.unwrap(); | ||
|
||
let mut grpc_client = StatusList2021SvcClient::connect(server.endpoint()).await?; | ||
let res = grpc_client | ||
.create(Request::new(CreateRequest { | ||
id: Some(id.into_string()), | ||
issuer: issuer.url().to_string(), | ||
purpose: Purpose::Revocation as i32, | ||
length: None, | ||
expiration_date: None, | ||
contexts: vec![], | ||
types: vec![], | ||
})) | ||
.await? | ||
.into_inner() | ||
.credential_json; | ||
let grpc_credential = StatusList2021Credential::from_json(&res)?; | ||
|
||
assert_eq!(status_list_credential, grpc_credential); | ||
Ok(()) | ||
} | ||
|
||
#[tokio::test] | ||
async fn status_list_2021_credential_update() -> anyhow::Result<()> { | ||
let server = TestServer::new().await; | ||
|
||
let id = Url::parse("http://example.com/credentials/status/1").unwrap(); | ||
let issuer = Issuer::Url(Url::parse("http://example.com/issuers/1").unwrap()); | ||
let mut status_list_credential = StatusList2021CredentialBuilder::new(StatusList2021::default()) | ||
.purpose(StatusPurpose::Revocation) | ||
.subject_id(id) | ||
.issuer(issuer) | ||
.build() | ||
.unwrap(); | ||
|
||
let entries_to_set = [0_u64, 42, 420, 4200]; | ||
let entries = entries_to_set.iter().map(|i| (*i, true)).collect(); | ||
|
||
let mut grpc_client = StatusList2021SvcClient::connect(server.endpoint()).await?; | ||
let grpc_credential = grpc_client | ||
.update(Request::new(UpdateRequest { | ||
credential_json: status_list_credential.to_json().unwrap(), | ||
entries, | ||
})) | ||
.await | ||
.map(|res| res.into_inner().credential_json) | ||
.map(|credential_json| StatusList2021Credential::from_json(&credential_json).unwrap()) | ||
.unwrap(); | ||
|
||
status_list_credential.update(|status_list| { | ||
for idx in entries_to_set { | ||
if let Err(e) = status_list.set_entry(idx as usize, true) { | ||
return Err(e); | ||
} | ||
} | ||
Ok(()) | ||
})?; | ||
|
||
assert_eq!(status_list_credential, grpc_credential); | ||
Ok(()) | ||
} |
Oops, something went wrong.