diff --git a/agent_api_rest/src/issuance/credential_issuer/credential.rs b/agent_api_rest/src/issuance/credential_issuer/credential.rs index 152004bf..b5f95c6b 100644 --- a/agent_api_rest/src/issuance/credential_issuer/credential.rs +++ b/agent_api_rest/src/issuance/credential_issuer/credential.rs @@ -81,9 +81,11 @@ pub(crate) async fn credential( } Some(OfferView { credential_ids, - subject_id: Some(subject_id), + subject_id, .. - }) => break (credential_ids, subject_id), + }) => { + break (credential_ids, subject_id); + } _ => { return Err(internal_server_error()); } @@ -163,6 +165,7 @@ pub mod tests { }; const CREDENTIAL_JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2dFODROQ01wTWVBeDlqSzljZjVXNEc4Z2NaOXh1d0p2RzFlN3dOazhLQ2d0I3o2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCIsInN1YiI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIiwibmJmIjoxMjYyMzA0MDAwLCJpYXQiOjEyNjIzMDQwMDAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6a2V5Ono2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCIsImZpcnN0X25hbWUiOiJGZXJyaXMiLCJsYXN0X25hbWUiOiJSdXN0YWNlYW4ifSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ0U4NE5DTXBNZUF4OWpLOWNmNVc0RzhnY1o5eHV3SnZHMWU3d05rOEtDZ3QiLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsImNyZWRlbnRpYWxTdGF0dXMiOnsiaWQiOiJodHRwczovL215LWRvbWFpbi5leGFtcGxlLm9yZy9pZXRmLW9hdXRoLXRva2VuLXN0YXR1cy1saXN0LzAiLCJ0eXBlIjoic3RhdHVzbGlzdCtqd3QiLCJpZHgiOjEyMywidXJpIjoiaHR0cHM6Ly9teS1kb21haW4uZXhhbXBsZS5vcmcvaWV0Zi1vYXV0aC10b2tlbi1zdGF0dXMtbGlzdC8wIn19LCJzdGF0dXMiOnsic3RhdHVzX2xpc3QiOnsiaWR4IjoxMjMsInVyaSI6Imh0dHBzOi8vbXktZG9tYWluLmV4YW1wbGUub3JnL2lldGYtb2F1dGgtdG9rZW4tc3RhdHVzLWxpc3QvMCJ9fX0.LpNq8l-qqqCA-htsB8KZLaVoNCfxqTrsPxVmEj0dsPAGFhOqO8lXI7DU0FhNwzWedxJ1ySS_Vq7ChBW-TgY7Bw"; + const ANONYMOUS_CREDENTIAL_JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2dFODROQ01wTWVBeDlqSzljZjVXNEc4Z2NaOXh1d0p2RzFlN3dOazhLQ2d0I3o2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCIsIm5iZiI6MTI2MjMwNDAwMCwiaWF0IjoxMjYyMzA0MDAwLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImZpcnN0X25hbWUiOiJGZXJyaXMiLCJsYXN0X25hbWUiOiJSdXN0YWNlYW4ifSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ0U4NE5DTXBNZUF4OWpLOWNmNVc0RzhnY1o5eHV3SnZHMWU3d05rOEtDZ3QiLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsImNyZWRlbnRpYWxTdGF0dXMiOnsiaWQiOiJodHRwczovL215LWRvbWFpbi5leGFtcGxlLm9yZy9pZXRmLW9hdXRoLXRva2VuLXN0YXR1cy1saXN0LzAiLCJ0eXBlIjoic3RhdHVzbGlzdCtqd3QiLCJpZHgiOjEyMywidXJpIjoiaHR0cHM6Ly9teS1kb21haW4uZXhhbXBsZS5vcmcvaWV0Zi1vYXV0aC10b2tlbi1zdGF0dXMtbGlzdC8wIn19LCJzdGF0dXMiOnsic3RhdHVzX2xpc3QiOnsiaWR4IjoxMjMsInVyaSI6Imh0dHBzOi8vbXktZG9tYWluLmV4YW1wbGUub3JnL2lldGYtb2F1dGgtdG9rZW4tc3RhdHVzLWxpc3QvMCJ9fX0.SxT7dwfIdkqTTYnSDzAEE5-csEUb9ucWWEcgIgDEEiK7VwsdW9k7ozLvi79Yfa71Q1buILLJdzLYf1mHE-V2Bg"; const DEFAULT_EXTERNAL_SERVER_RESPONSE_TIMEOUT_MS: u64 = 1000; trait CredentialEventTrigger { @@ -255,15 +258,17 @@ pub mod tests { } #[rstest] - #[case::without_external_server(false, false, 0)] - #[case::with_external_server(true, false, 0)] - #[case::with_external_server_and_self_signed_credential(true, true, 0)] + #[case::without_external_server(false, false, false, 0)] + #[case::with_anonymous_access(true, false, false, 0)] + #[case::with_external_server(false, true, false, 0)] + #[case::with_external_server_and_self_signed_credential(false, true, true, 0)] #[should_panic(expected = "assertion `left == right` failed\n left: 500\n right: 200")] - #[case::should_panic_due_to_timeout(true, false, DEFAULT_EXTERNAL_SERVER_RESPONSE_TIMEOUT_MS + 100)] + #[case::should_panic_due_to_timeout(false, true, false, DEFAULT_EXTERNAL_SERVER_RESPONSE_TIMEOUT_MS + 100)] #[serial_test::serial] #[tokio::test(flavor = "multi_thread")] #[tracing_test::traced_test] async fn test_credential_endpoint( + #[case] with_anonymous_access: bool, #[case] with_external_server: bool, #[case] is_self_signed: bool, #[case] delay: u64, @@ -310,6 +315,12 @@ pub mod tests { let access_token: String = token(&mut app, pre_authorized_code).await; + let jwt = if with_anonymous_access { + "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVkRFNBIiwia2lkIjoiZGlkOmtleTp6Nk1rcHc2OGh1OVFnTUFDNmJDOE0xeFo0cDZ4VXNFeUs0bUtZdEtNYkpnTmRrSjIjejZNa3B3NjhodTlRZ01BQzZiQzhNMXhaNHA2eFVzRXlLNG1LWXRLTWJKZ05ka0oyIn0.eyJhdWQiOiJodHRwOi8vMTI3LjAuMC4xOjQwNzI1LyIsImlhdCI6MTc2MjI2NTgxMH0.GuGCIl-0VGkADdbWkcL56P5jXZjGKBzYbr-gPfQ5Yl7u4KltF1pjle52RuTVInxIQXeP9GuDL1Ag52B6Y0NSAg" + } else { + "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVkRFNBIiwia2lkIjoiZGlkOmtleTp6Nk1raWlleW9MTVNWc0pBWnY3SmplNXdXU2tERXltVWdreUY4a2JjcmpacFgzcWQjejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCIsImF1ZCI6Imh0dHBzOi8vZXhhbXBsZS5jb20vIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE1NzEzMjQ4MDAsIm5vbmNlIjoiN2UwM2FkM2Y3NmNiMzMzOGMzYTU2NDJmZTc2MzQ0NzZhYTNhZDkzZmExZDU4NDAxMWJhMjE1MGQ5ZGE0NzEzMyJ9.bDxmEWTGwKJJC8J5N16JHAR2ZBYtgWlhM_o_voJdXLnw_ScZMwGjZwNH6aQWKlgIaFWKonF88KNRFX2UAOAuBQ" + }; + let response = app .oneshot( Request::builder() @@ -322,15 +333,7 @@ pub mod tests { "credential_configuration_id": CREDENTIAL_CONFIGURATION_ID, "proof": { "proof_type": "jwt", - "jwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVkRFNBIiwia2lk\ - IjoiZGlkOmtleTp6Nk1raWlleW9MTVNWc0pBWnY3SmplNXdXU2tERXltVWdreUY4\ - a2JjcmpacFgzcWQjejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lG\ - OGtiY3JqWnBYM3FkIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtpaWV5b0xNU1ZzSkFa\ - djdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCIsImF1ZCI6Imh0dHBzOi8v\ - ZXhhbXBsZS5jb20vIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE1NzEzMjQ4MDAs\ - Im5vbmNlIjoiN2UwM2FkM2Y3NmNiMzMzOGMzYTU2NDJmZTc2MzQ0NzZhYTNhZDkz\ - ZmExZDU4NDAxMWJhMjE1MGQ5ZGE0NzEzMyJ9.bDxmEWTGwKJJC8J5N16JHAR2ZBY\ - tgWlhM_o_voJdXLnw_ScZMwGjZwNH6aQWKlgIaFWKonF88KNRFX2UAOAuBQ" + "jwt": jwt } })) .unwrap(), @@ -345,7 +348,12 @@ pub mod tests { let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let body: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(body["credentials"][0]["credential"], json!(CREDENTIAL_JWT)); + + if with_anonymous_access { + assert_eq!(body["credentials"][0]["credential"], json!(ANONYMOUS_CREDENTIAL_JWT)); + } else { + assert_eq!(body["credentials"][0]["credential"], json!(CREDENTIAL_JWT)); + } if let Some(external_server) = external_server { // Assert that the event was dispatched to the target URL. diff --git a/agent_event_publisher_http/src/lib.rs b/agent_event_publisher_http/src/lib.rs index 0c6219c5..5417a923 100644 --- a/agent_event_publisher_http/src/lib.rs +++ b/agent_event_publisher_http/src/lib.rs @@ -330,7 +330,7 @@ mod tests { // A new event for the `Offer` aggregate that the publisher is not interested in. let offer_event = OfferEvent::CredentialRequestVerified { offer_id: Default::default(), - subject_id: "subject_id".to_string(), + subject_id: Some("subject_id".to_string()), }; let events = [EventEnvelope:: { diff --git a/agent_issuance/src/credential/aggregate.rs b/agent_issuance/src/credential/aggregate.rs index 900015ee..d0453f8f 100644 --- a/agent_issuance/src/credential/aggregate.rs +++ b/agent_issuance/src/credential/aggregate.rs @@ -373,7 +373,10 @@ impl Aggregate for Credential { // Create a new Map and insert the id field first let mut new_credential_subject = serde_json::Map::new(); - new_credential_subject.insert("id".to_string(), json!(subject_id)); + + if let Some(subject_id) = &subject_id { + new_credential_subject.insert("id".to_string(), json!(subject_id)); + } // Insert the rest of the fields for (key, value) in credential_subject { @@ -407,11 +410,11 @@ impl Aggregate for Credential { }); // Add standard claims - let vc_jwt_builder = VerifiableCredentialJwt::builder() - .sub(subject_id) - .iss(issuer_did) - .iat(iat) - .nbf(iat); // TODO: setting the `nbf` to `iat` makes the JWT immediately usable + let mut vc_jwt_builder = VerifiableCredentialJwt::builder().iss(issuer_did).iat(iat).nbf(iat); // TODO: setting the `nbf` to `iat` makes the JWT immediately usable + + if let Some(subject_id) = subject_id { + vc_jwt_builder = vc_jwt_builder.sub(subject_id); + } let vc_jwt_builder = if let Some(exp) = exp { vc_jwt_builder.exp(exp) @@ -651,7 +654,7 @@ pub mod credential_tests { }]) .when(CredentialCommand::SignCredential { credential_id: credential_id.clone(), - subject_id: holder.identifier("did:key", Algorithm::EdDSA).await.unwrap(), + subject_id: Some(holder.identifier("did:key", Algorithm::EdDSA).await.unwrap()), overwrite: false, }) .then_expect_events(vec![CredentialEvent::CredentialSigned { diff --git a/agent_issuance/src/credential/command.rs b/agent_issuance/src/credential/command.rs index 268c2dc2..afe92e39 100644 --- a/agent_issuance/src/credential/command.rs +++ b/agent_issuance/src/credential/command.rs @@ -24,7 +24,7 @@ pub enum CredentialCommand { }, SignCredential { credential_id: String, - subject_id: String, + subject_id: Option, // When true, a credential will be re-signed if it already exists. overwrite: bool, }, diff --git a/agent_issuance/src/offer/aggregate.rs b/agent_issuance/src/offer/aggregate.rs index 4f477282..e5d42639 100644 --- a/agent_issuance/src/offer/aggregate.rs +++ b/agent_issuance/src/offer/aggregate.rs @@ -269,12 +269,7 @@ impl Aggregate for Offer { .await .map_err(|e| InvalidProofError(e.to_string()))?; - let subject_did = proof - .rfc7519_claims - .iss() - .as_ref() - .ok_or(MissingProofIssuerError)? - .clone(); + let subject_did = proof.rfc7519_claims.iss().as_ref().cloned(); Ok(vec![CredentialRequestVerified { offer_id, @@ -351,7 +346,7 @@ impl Aggregate for Offer { } CredentialOfferSent { .. } => {} CredentialRequestVerified { subject_id, .. } => { - self.subject_id.replace(subject_id); + self.subject_id = subject_id; } TokenResponseCreated { token_response, .. } => { self.token_response.replace(token_response); @@ -576,7 +571,7 @@ pub mod tests { }) .then_expect_events(vec![OfferEvent::CredentialRequestVerified { offer_id: offer_id.clone(), - subject_id: holder.identifier("did:key", Algorithm::EdDSA).await.unwrap(), + subject_id: Some(holder.identifier("did:key", Algorithm::EdDSA).await.unwrap()), }]); } @@ -626,7 +621,7 @@ pub mod tests { }, OfferEvent::CredentialRequestVerified { offer_id: offer_id.clone(), - subject_id: holder.identifier("did:key", Algorithm::EdDSA).await.unwrap(), + subject_id: Some(holder.identifier("did:key", Algorithm::EdDSA).await.unwrap()), }, ]) .when(OfferCommand::CreateCredentialResponse { @@ -684,7 +679,7 @@ pub mod tests { }, OfferEvent::CredentialRequestVerified { offer_id: offer_id.clone(), - subject_id: holder.identifier("did:key", Algorithm::EdDSA).await.unwrap(), + subject_id: Some(holder.identifier("did:key", Algorithm::EdDSA).await.unwrap()), }, // Credentials are only added after the credential request is verified (JIT) OfferEvent::CredentialsAdded { diff --git a/agent_issuance/src/offer/event.rs b/agent_issuance/src/offer/event.rs index 0a1c9e60..21c4c4db 100644 --- a/agent_issuance/src/offer/event.rs +++ b/agent_issuance/src/offer/event.rs @@ -42,7 +42,7 @@ pub enum OfferEvent { }, CredentialRequestVerified { offer_id: String, - subject_id: String, + subject_id: Option, }, CredentialResponseCreated { offer_id: String, diff --git a/agent_issuance/src/offer/views/mod.rs b/agent_issuance/src/offer/views/mod.rs index f9da6ad7..dd408d5d 100644 --- a/agent_issuance/src/offer/views/mod.rs +++ b/agent_issuance/src/offer/views/mod.rs @@ -58,7 +58,7 @@ impl View for Offer { } CredentialRequestVerified { offer_id, subject_id } => { self.offer_id.clone_from(offer_id); - self.subject_id.replace(subject_id.clone()); + self.subject_id.clone_from(subject_id); } TokenResponseCreated { offer_id,