From a1b827115f3dd2b6f9d1f8ee08e8f5ddbd965391 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Wed, 13 Dec 2023 16:31:55 +0100 Subject: [PATCH 01/11] feat: make startup commands segregated --- agent_issuance/Cargo.toml | 1 + agent_issuance/src/startup_commands.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/agent_issuance/Cargo.toml b/agent_issuance/Cargo.toml index 7745d47c..130c957f 100644 --- a/agent_issuance/Cargo.toml +++ b/agent_issuance/Cargo.toml @@ -17,6 +17,7 @@ derivative = "2.2" did-key = "0.2" jsonschema = "0.17" jsonwebtoken = "8.2" +lazy_static.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 5649c739..46f098a0 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -12,6 +12,10 @@ use oid4vci::{ use crate::command::IssuanceCommand; +lazy_static! { + pub static ref BASE_URL: url::Url = format!("http://{}:3033/", config!("host").unwrap()).parse().unwrap(); +} + /// Returns the startup commands for the application. pub fn startup_commands(host: url::Url) -> Vec { vec![ From 3ebadf29c246c048ff1c1b25defb0801833d3366 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Wed, 13 Dec 2023 17:01:51 +0100 Subject: [PATCH 02/11] refactor: reuse `startup_commands` in `agent_api_rest` unit tests --- agent_issuance/src/startup_commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 46f098a0..814cbaeb 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -13,7 +13,7 @@ use oid4vci::{ use crate::command::IssuanceCommand; lazy_static! { - pub static ref BASE_URL: url::Url = format!("http://{}:3033/", config!("host").unwrap()).parse().unwrap(); + static ref BASE_URL: url::Url = format!("http://{}:3033/", config!("host").unwrap()).parse().unwrap(); } /// Returns the startup commands for the application. From 4659a58db5f7aee783b2e64748a36ed22832cd2a Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Wed, 13 Dec 2023 18:13:01 +0100 Subject: [PATCH 03/11] feat: accept host url in `startup_commands` --- agent_issuance/Cargo.toml | 1 - agent_issuance/src/startup_commands.rs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/agent_issuance/Cargo.toml b/agent_issuance/Cargo.toml index 130c957f..7745d47c 100644 --- a/agent_issuance/Cargo.toml +++ b/agent_issuance/Cargo.toml @@ -17,7 +17,6 @@ derivative = "2.2" did-key = "0.2" jsonschema = "0.17" jsonwebtoken = "8.2" -lazy_static.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 814cbaeb..5649c739 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -12,10 +12,6 @@ use oid4vci::{ use crate::command::IssuanceCommand; -lazy_static! { - static ref BASE_URL: url::Url = format!("http://{}:3033/", config!("host").unwrap()).parse().unwrap(); -} - /// Returns the startup commands for the application. pub fn startup_commands(host: url::Url) -> Vec { vec![ From f669f3dd966f591c433d8b59d31b1065fabc2e02 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 14 Dec 2023 12:11:00 +0100 Subject: [PATCH 04/11] feat: support credential logo url --- .env.example | 2 ++ agent_issuance/src/startup_commands.rs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 34eebb93..fea12b6d 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ AGENT_CONFIG_LOG_FORMAT=json AGENT_CONFIG_EVENT_STORE=postgres AGENT_APPLICATION_HOST=my-domain.example.org +AGENT_ISSUANCE_CREDENTIAL_NAME="Demo Credential" +AGENT_ISSUANCE_CREDENTIAL_LOGO_URL=https://my-domain.example.org/credential_logo.png AGENT_STORE_DB_CONNECTION_STRING=postgresql://demo_user:demo_pass@localhost:5432/demo diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 5649c739..1b52cae4 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -1,3 +1,4 @@ +use agent_shared::config; use oid4vci::{ credential_format_profiles::{ w3c_verifiable_credentials::jwt_vc_json::{CredentialDefinition, JwtVcJson}, @@ -9,6 +10,7 @@ use oid4vci::{ }, ProofType, }; +use serde_json::json; use crate::command::IssuanceCommand; @@ -74,7 +76,17 @@ pub fn create_credentials_supported() -> IssuanceCommand { cryptographic_binding_methods_supported: Some(vec!["did:key".to_string()]), cryptographic_suites_supported: Some(vec!["EdDSA".to_string()]), proof_types_supported: Some(vec![ProofType::Jwt]), - display: None, + display: Some(vec![json!({ + "name": config!("credential_name").unwrap(), + "logo": { + "url": config!("credential_logo_url").unwrap() + } + })]), }], } } + +#[test] +fn test_startup_commands() { + dbg!(create_credentials_supported()); +} From 78ee38253327f4a73e3a2c50649f481c6dcd2cee Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 14 Dec 2023 12:12:59 +0100 Subject: [PATCH 05/11] fix: remove temp test --- agent_issuance/src/startup_commands.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 1b52cae4..3a95e864 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -85,8 +85,3 @@ pub fn create_credentials_supported() -> IssuanceCommand { }], } } - -#[test] -fn test_startup_commands() { - dbg!(create_credentials_supported()); -} From 24a78c5c8de7479eed6fbdde1c51613b8d547a91 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 14 Dec 2023 14:08:39 +0100 Subject: [PATCH 06/11] feat: add `test` feature to `agent_shared` to allow for testing across agents --- agent_shared/Cargo.toml | 3 +++ agent_shared/src/config.rs | 30 ++++++++++++++++++++++-------- agent_shared/tests/.env.test | 4 +++- agent_shared/tests/config.rs | 3 +-- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/agent_shared/Cargo.toml b/agent_shared/Cargo.toml index 5844f50c..b9f35e0b 100644 --- a/agent_shared/Cargo.toml +++ b/agent_shared/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" config = { version = "0.13" } dotenvy = { version = "0.15" } tracing.workspace = true + +[features] +test = [] diff --git a/agent_shared/src/config.rs b/agent_shared/src/config.rs index 0036a653..179bb69e 100644 --- a/agent_shared/src/config.rs +++ b/agent_shared/src/config.rs @@ -2,17 +2,31 @@ use tracing::info; /// Read environment variables pub fn config(package_name: &str) -> config::Config { - // Load global .env file - dotenvy::dotenv().ok(); + let config = if cfg!(feature = "test") { + test_config() + } else { + dotenvy::dotenv().ok(); - // Build configuration - let config = config::Config::builder() - .add_source(config::Environment::with_prefix(package_name)) - .add_source(config::Environment::with_prefix("AGENT_CONFIG")) - .build() - .unwrap(); + config::Config::builder() + .add_source(config::Environment::with_prefix(package_name)) + .add_source(config::Environment::with_prefix("AGENT_CONFIG")) + .build() + .unwrap() + }; info!("{:?}", config); config } + +/// Read environment variables for tests that can be used across packages +#[cfg(feature = "test")] +fn test_config() -> config::Config { + dotenvy::from_filename("agent_shared/tests/.env.test").ok(); + + config::Config::builder() + .add_source(config::Environment::with_prefix("TEST")) + .add_source(config::Environment::with_prefix("AGENT_CONFIG")) + .build() + .unwrap() +} diff --git a/agent_shared/tests/.env.test b/agent_shared/tests/.env.test index 02dee4f5..b08652e7 100644 --- a/agent_shared/tests/.env.test +++ b/agent_shared/tests/.env.test @@ -1,3 +1,5 @@ -AGENT_SHARED_VARIABLE=env_value +TEST_VARIABLE=env_value AGENT_CONFIG_GLOBAL_VARIABLE=global_env_value AGENT_OTHER_OTHER_VARIABLE=other_env_value +TEST_CREDENTIAL_NAME="Demo Credential" +TEST_CREDENTIAL_LOGO_URL=https://my-domain.example.org/credential_logo.png diff --git a/agent_shared/tests/config.rs b/agent_shared/tests/config.rs index 50bd0d2f..11b161a5 100644 --- a/agent_shared/tests/config.rs +++ b/agent_shared/tests/config.rs @@ -1,9 +1,8 @@ use agent_shared::config; +#[cfg(feature = "test")] #[test] fn test_config() { - dotenvy::from_filename("tests/.env.test").ok(); - assert_eq!(config!("variable").unwrap(), "env_value"); assert_eq!(config!("global_variable").unwrap(), "global_env_value"); // Reading from an environment variable that belongs to another package should fail. From a0e581756e4ed44f9491af11377e38197dcdf6f8 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 14 Dec 2023 14:09:28 +0100 Subject: [PATCH 07/11] test: use the `agent_shared` `test` feature --- Cargo.lock | 1 + agent_api_rest/Cargo.toml | 1 + .../well_known/openid_credential_issuer.rs | 71 ++++++++++++------- agent_issuance/src/queries.rs | 6 +- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d12fc0b..25611e09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "agent_api_rest" version = "0.1.0" dependencies = [ "agent_issuance", + "agent_shared", "agent_store", "axum", "axum-auth", diff --git a/agent_api_rest/Cargo.toml b/agent_api_rest/Cargo.toml index c50501d9..d85c6236 100644 --- a/agent_api_rest/Cargo.toml +++ b/agent_api_rest/Cargo.toml @@ -19,6 +19,7 @@ serde_json.workspace = true tracing.workspace = true [dev-dependencies] +agent_shared = { path = "../agent_shared", features = ["test"] } agent_store = { path = "../agent_store" } lazy_static.workspace = true diff --git a/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs index 4ba1fa59..f014eb0a 100644 --- a/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs +++ b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs @@ -35,12 +35,23 @@ mod tests { startup_commands::{create_credentials_supported, load_credential_issuer_metadata}, state::{initialize, CQRS}, }; + use agent_shared::config; use agent_store::in_memory; use axum::{ body::Body, http::{self, Request}, }; - use serde_json::{json, Value}; + use oid4vci::{ + credential_format_profiles::{ + w3c_verifiable_credentials::jwt_vc_json::{CredentialDefinition, JwtVcJson}, + CredentialFormats, Parameters, + }, + credential_issuer::{ + credential_issuer_metadata::CredentialIssuerMetadata, credentials_supported::CredentialsSupportedObject, + }, + ProofType, + }; + use serde_json::json; use tower::ServiceExt; #[tokio::test] @@ -73,31 +84,41 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let body: Value = serde_json::from_slice(&body).unwrap(); + let cfredential_issuer_metadata: CredentialIssuerMetadata = serde_json::from_slice(&body).unwrap(); assert_eq!( - body, - json!({ - "credential_issuer": "https://example.com/", - "credential_endpoint": "https://example.com/openid4vci/credential", - "credentials_supported": [{ - "format": "jwt_vc_json", - "cryptographic_binding_methods_supported": [ - "did:key" - ], - "cryptographic_suites_supported": [ - "EdDSA" - ], - "credential_definition":{ - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" - ] - }, - "proof_types_supported": [ - "jwt" - ] - }] - }) + cfredential_issuer_metadata, + CredentialIssuerMetadata { + credential_issuer: BASE_URL.clone(), + authorization_server: None, + credential_endpoint: BASE_URL.join("openid4vci/credential").unwrap(), + batch_credential_endpoint: None, + deferred_credential_endpoint: None, + credentials_supported: vec![CredentialsSupportedObject { + id: None, + credential_format: CredentialFormats::JwtVcJson(Parameters { + format: JwtVcJson, + parameters: ( + CredentialDefinition { + type_: vec!["VerifiableCredential".to_string(), "OpenBadgeCredential".to_string()], + credential_subject: None, + }, + None, + ) + .into(), + }), + scope: None, + cryptographic_binding_methods_supported: Some(vec!["did:key".to_string()]), + cryptographic_suites_supported: Some(vec!["EdDSA".to_string()]), + proof_types_supported: Some(vec![ProofType::Jwt]), + display: Some(vec![json!({ + "name": config!("credential_name").unwrap(), + "logo": { + "url": config!("credential_logo_url").unwrap() + } + })]), + }], + display: None, + } ); } } diff --git a/agent_issuance/src/queries.rs b/agent_issuance/src/queries.rs index 58453f7d..1bd94d44 100644 --- a/agent_issuance/src/queries.rs +++ b/agent_issuance/src/queries.rs @@ -72,9 +72,9 @@ impl View for IssuanceDataView { .replace(credential_offer.clone()); } UnsignedCredentialCreated { subject_id, credential } => { - if let Some(subject) = self.subjects - .iter_mut() - .find(|subject| subject.id == *subject_id) { subject.credentials.replace(credential.clone()); } + if let Some(subject) = self.subjects.iter_mut().find(|subject| subject.id == *subject_id) { + subject.credentials.replace(credential.clone()); + } } PreAuthorizedCodeUpdated { subject_id, From 15fee78bcf570e22a2b508f137a9ba8316c84370 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Wed, 20 Dec 2023 15:37:11 +0100 Subject: [PATCH 08/11] fix: fix test feature settings for `fn config` --- agent_shared/src/config.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/agent_shared/src/config.rs b/agent_shared/src/config.rs index 179bb69e..4e7c1db9 100644 --- a/agent_shared/src/config.rs +++ b/agent_shared/src/config.rs @@ -1,10 +1,14 @@ use tracing::info; /// Read environment variables +#[cfg(feature = "test")] // Only allow unused code when testing +#[allow(unused)] pub fn config(package_name: &str) -> config::Config { - let config = if cfg!(feature = "test") { - test_config() - } else { + #[cfg(feature = "test")] + let config = test_config(); + + #[cfg(not(feature = "test"))] + let config = { dotenvy::dotenv().ok(); config::Config::builder() From e0a7b58cffa21b907af59e251f3777e543b7fb60 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Wed, 20 Dec 2023 16:02:10 +0100 Subject: [PATCH 09/11] fix: remove --- agent_shared/src/config.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/agent_shared/src/config.rs b/agent_shared/src/config.rs index 4e7c1db9..4a2e1ad6 100644 --- a/agent_shared/src/config.rs +++ b/agent_shared/src/config.rs @@ -1,7 +1,6 @@ use tracing::info; /// Read environment variables -#[cfg(feature = "test")] // Only allow unused code when testing #[allow(unused)] pub fn config(package_name: &str) -> config::Config { #[cfg(feature = "test")] From 14c023766d2d04aaeb6ff104673bd719da61038c Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 21 Dec 2023 15:20:33 +0100 Subject: [PATCH 10/11] fix: make optional --- agent_issuance/src/startup_commands.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/agent_issuance/src/startup_commands.rs b/agent_issuance/src/startup_commands.rs index 3a95e864..8ce42e27 100644 --- a/agent_issuance/src/startup_commands.rs +++ b/agent_issuance/src/startup_commands.rs @@ -76,12 +76,15 @@ pub fn create_credentials_supported() -> IssuanceCommand { cryptographic_binding_methods_supported: Some(vec!["did:key".to_string()]), cryptographic_suites_supported: Some(vec!["EdDSA".to_string()]), proof_types_supported: Some(vec![ProofType::Jwt]), - display: Some(vec![json!({ - "name": config!("credential_name").unwrap(), - "logo": { - "url": config!("credential_logo_url").unwrap() - } - })]), + display: match (config!("credential_name"), config!("credential_logo_url")) { + (Ok(name), Ok(logo_url)) => Some(vec![json!({ + "name": name, + "logo": { + "url": logo_url + } + })]), + _ => None, + }, }], } } From b7dedf3be08f13d4371c79aa217b44bf1e63ce54 Mon Sep 17 00:00:00 2001 From: nanderstabel Date: Thu, 21 Dec 2023 16:41:53 +0100 Subject: [PATCH 11/11] fix: typo --- .../credential_issuer/well_known/openid_credential_issuer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs index f014eb0a..fdbffb91 100644 --- a/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs +++ b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs @@ -84,9 +84,9 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let cfredential_issuer_metadata: CredentialIssuerMetadata = serde_json::from_slice(&body).unwrap(); + let credential_issuer_metadata: CredentialIssuerMetadata = serde_json::from_slice(&body).unwrap(); assert_eq!( - cfredential_issuer_metadata, + credential_issuer_metadata, CredentialIssuerMetadata { credential_issuer: BASE_URL.clone(), authorization_server: None,