diff --git a/Cargo.lock b/Cargo.lock index ffd1fe56..50c5fcf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,7 @@ dependencies = [ "agent_verification", "anyhow", "askama", + "async-trait", "axum 0.8.3", "axum-auth 0.8.1", "axum-macros", @@ -99,6 +100,8 @@ dependencies = [ "identity_core", "identity_credential", "identity_did", + "identity_iota", + "identity_storage", "jsonwebtoken", "lazy_static", "metrics", @@ -161,6 +164,7 @@ version = "0.1.0" dependencies = [ "agent_authorization", "agent_holder", + "agent_identity", "agent_issuance", "agent_secret_manager", "agent_shared", @@ -198,6 +202,7 @@ dependencies = [ "agent_identity", "agent_issuance", "agent_library", + "agent_secret_manager", "agent_shared", "agent_store", "agent_verification", @@ -242,6 +247,7 @@ dependencies = [ "agent_api_http", "agent_authorization", "agent_holder", + "agent_identity", "agent_issuance", "agent_secret_manager", "agent_shared", @@ -292,6 +298,7 @@ dependencies = [ "async-trait", "axum 0.8.3", "base64 0.22.1", + "consumer", "cqrs-es", "derivative", "did-jwk", @@ -303,6 +310,7 @@ dependencies = [ "identity_document", "identity_iota", "identity_storage", + "identity_stronghold_ext", "iota-sdk 1.6.1", "itertools 0.14.0", "jsonwebtoken", @@ -310,6 +318,7 @@ dependencies = [ "mime", "names", "oid4vc-core", + "p256 0.13.2", "product_common", "rand 0.9.1", "reqwest 0.12.20", @@ -336,6 +345,7 @@ name = "agent_issuance" version = "0.1.0" dependencies = [ "agent_holder", + "agent_identity", "agent_issuance", "agent_library", "agent_secret_manager", @@ -346,12 +356,14 @@ dependencies = [ "chrono", "cqrs-es", "derivative", + "did-key", "identity_core", "identity_credential", "jsonwebtoken", "lazy_static", "oauth_tsl", "oid4vc-core", + "oid4vc-manager", "oid4vci", "once_cell", "rand 0.9.1", @@ -411,8 +423,13 @@ dependencies = [ "async-trait", "base64 0.22.1", "consumer", + "cqrs-es", "futures", + "identity_core", + "identity_credential", + "identity_did", "identity_iota", + "identity_storage", "identity_stronghold_ext", "iota-sdk 1.1.5", "iota-sdk 1.6.1", @@ -425,7 +442,11 @@ dependencies = [ "p256 0.13.2", "ring", "serde", + "serde_json", + "strum 0.26.3", + "thiserror 1.0.69", "tokio", + "tracing", "url", ] @@ -472,6 +493,7 @@ dependencies = [ "agent_identity", "agent_issuance", "agent_library", + "agent_secret_manager", "agent_shared", "agent_verification", "async-trait", @@ -487,6 +509,7 @@ dependencies = [ name = "agent_verification" version = "0.1.0" dependencies = [ + "agent_identity", "agent_secret_manager", "agent_shared", "agent_verification", @@ -2432,7 +2455,7 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "consumer" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "did_iota", "did_jwk", @@ -3191,7 +3214,7 @@ dependencies = [ [[package]] name = "did_iota" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "anyhow", "identity_iota", @@ -3206,7 +3229,7 @@ dependencies = [ [[package]] name = "did_jwk" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "did-jwk", "identity_iota", @@ -3223,7 +3246,7 @@ dependencies = [ [[package]] name = "did_key" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "did-method-key", "identity_iota", @@ -3270,7 +3293,7 @@ dependencies = [ [[package]] name = "did_web" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "did-web", "identity_iota", @@ -5280,7 +5303,7 @@ dependencies = [ [[package]] name = "identity_stronghold_ext" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "async-trait", "elliptic-curve 0.13.8", @@ -5291,6 +5314,7 @@ dependencies = [ "k256", "log", "p256 0.13.2", + "rand 0.8.5", "secret-storage", "serde_json", "stronghold_ext", @@ -10740,7 +10764,7 @@ dependencies = [ [[package]] name = "secret-storage" version = "0.3.0" -source = "git+https://github.com/iotaledger/secret-storage.git?tag=v0.3.0#7dc13ddae8a7f2c8a931d3155f3cb9e9a27902fb" +source = "git+https://github.com/iotaledger/secret-storage?tag=v0.3.0#7dc13ddae8a7f2c8a931d3155f3cb9e9a27902fb" dependencies = [ "anyhow", "async-trait", @@ -11183,7 +11207,7 @@ dependencies = [ [[package]] name = "shared" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager?tag=v1.0.0-beta.6#e8f68b575a3ee354ac4b08663939917977974556" +source = "git+https://github.com/impierce/did-manager?rev=d730318#d730318136c82b4ecb2c61839ab24f8cc52ff58c" dependencies = [ "identity_iota", "identity_storage", diff --git a/agent_api_http/Cargo.toml b/agent_api_http/Cargo.toml index 31916237..26da8d98 100644 --- a/agent_api_http/Cargo.toml +++ b/agent_api_http/Cargo.toml @@ -16,6 +16,7 @@ agent_verification = { path = "../agent_verification" } anyhow.workspace = true askama = "0.14" +async-trait.workspace = true axum.workspace = true axum-auth = "0.8" axum-macros = "0.5" @@ -28,6 +29,8 @@ hyper = { version = "1.2" } identity_core.workspace = true identity_credential.workspace = true identity_did.workspace = true +identity_iota.workspace = true +identity_storage.workspace = true metrics = "0.24" metrics-exporter-prometheus = { version = "0.17", default-features = false } oauth_tsl.workspace = true diff --git a/agent_api_http/openapi.yaml b/agent_api_http/openapi.yaml index dd0f4d9a..5a702244 100644 --- a/agent_api_http/openapi.yaml +++ b/agent_api_http/openapi.yaml @@ -1429,6 +1429,111 @@ paths: "201": description: Linked VP service created successfully + /v0/keys/generate-new-key: + post: + tags: + - Identity + summary: Generate a new key + description: Generates a new cryptographic key and stores it in the key management system. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - alias + properties: + alias: + type: string + description: A human-readable name for this key. + example: "my-signing-key-01" + signatureAlgorithm: + type: string + description: The signature algorithm to use for this key. Defaults to Ed25519 if not specified. + example: "Ed25519" + + /v0/keys/remove-key: + post: + tags: + - Identity + summary: Remove an existing key + description: Removes an existing cryptographic key from the key management system. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - keyId + properties: + keyId: + type: string + description: The ID of the key to remove. + example: "a81bc81b-d7a7-4e5d-abff-90865d1e13b1" + + /v0/keys/rename-key-alias: + post: + tags: + - Identity + summary: Rename the alias of a key + description: Renames the human-readable alias of an existing cryptographic key in the key management system. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - keyId + - newAlias + properties: + keyId: + type: string + description: The ID of the key to rename. + example: "a81bc81b-d7a7-4e5d-abff-90865d1e13b1" + newAlias: + type: string + description: The new alias for the key. + example: "my-new-key-alias" + + /v0/keys/set-signing-key: + post: + tags: + - Identity + summary: Set a key as the active signing key + description: Designates a specific managed key to be used for signing operations. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - keyId + properties: + keyId: + type: string + description: The ID of the key to set as the signing key. + example: "a81bc81b-abcd-4e5d-abff-90865d1e13b1" + + /v0/keys/list-all: + get: + tags: + - Identity + summary: List all managed keys + description: Retrieves a list of all cryptographic keys managed by the key management system. + responses: + "200": + description: Keys retrieved successfully + content: + application/json: + schema: + type: array + items: + type: object + /auth/token: post: summary: Standard OAuth 2.0 endpoint for fetching a token diff --git a/agent_api_http/postman/ssi-agent.postman_collection.json b/agent_api_http/postman/ssi-agent.postman_collection.json index 83403a3e..d5f4477a 100644 --- a/agent_api_http/postman/ssi-agent.postman_collection.json +++ b/agent_api_http/postman/ssi-agent.postman_collection.json @@ -1,9 +1,10 @@ { "info": { - "_postman_id": "e9b3fbae-f20e-4957-9d3a-6a310f5fda62", + "_postman_id": "4da8f32a-5645-4785-911d-5c2c729e2092", "name": "ssi-agent", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "38461474" + "_exporter_id": "41786572", + "_collection_link": "https://impierce-technologies-bv.postman.co/workspace/New-Team-Workspace~1aa38760-77fc-4608-a1f5-e8a6fdaa0ad9/collection/41786572-4da8f32a-5645-4785-911d-5c2c729e2092?action=share&source=collection_link&creator=41786572" }, "item": [ { @@ -2283,25 +2284,148 @@ ] }, { - "name": "public", + "name": "v1", "item": [ { - "name": "Show sponsoring configuration", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{HOST}}/public/sponsoring-configuration", - "host": [ - "{{HOST}}" - ], - "path": [ - "public", - "sponsoring-configuration" + "name": "Identity", + "item": [ + { + "name": "Keys", + "item": [ + { + "name": "Generate Key", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"alias\": \"An Example Alias\",\n \"signingAlgorithm\": \"ES256\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{HOST}}/v0/keys/generate-new-key", + "host": [ + "{{HOST}}" + ], + "path": [ + "v0", + "keys", + "generate-new-key" + ] + } + }, + "response": [] + }, + { + "name": "Remove Key", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"keyId\": \"your-managed-key-id\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{HOST}}/v0/keys/remove-key", + "host": [ + "{{HOST}}" + ], + "path": [ + "v0", + "keys", + "remove-key" + ] + } + }, + "response": [] + }, + { + "name": "Rename Key Alias", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"keyId\": \"your-managed-key-id\",\n \"newAlias\": \"new-key-alias\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{HOST}}/v0/keys/rename-key-alias", + "host": [ + "{{HOST}}" + ], + "path": [ + "v0", + "keys", + "rename-key-alias" + ] + } + }, + "response": [] + }, + { + "name": "Set Signing Key", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"keyId\": \"your-managed-key-id\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{HOST}}/v0/keys/set-signing-key", + "host": [ + "{{HOST}}" + ], + "path": [ + "v0", + "keys", + "set-signing-key" + ] + } + }, + "response": [] + }, + { + "name": "List all Keys", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{HOST}}/v0/keys/list-all", + "host": [ + "{{HOST}}" + ], + "path": [ + "v0", + "keys", + "list-all" + ] + } + }, + "response": [] + } ] } - }, - "response": [] + ] } ] } diff --git a/agent_api_http/src/lib.rs b/agent_api_http/src/lib.rs index f2e1a0f4..4b5ebf2c 100644 --- a/agent_api_http/src/lib.rs +++ b/agent_api_http/src/lib.rs @@ -1,16 +1,23 @@ pub mod public; pub mod v0; +pub mod v1; pub mod error; pub mod handlers; pub mod metrics; +pub mod sagas; pub mod utils; +use crate::{ + sagas::{key_generation_saga::KeyGenerationSaga, key_removal_saga::KeyRemovalSaga}, + v1::identity::IdentityContext, +}; use agent_authorization::state::AuthorizationState; use agent_holder::state::HolderState; use agent_identity::state::IdentityState; use agent_issuance::state::IssuanceState; use agent_library::state::LibraryState; +use agent_secret_manager::state::SecretManagerState; use agent_shared::config::config; use agent_verification::state::VerificationState; use axum::{ @@ -34,26 +41,51 @@ pub const DOCUMENTATION_URL: &str = "https://beta.docs.impierce.com/unicore/"; #[derive(Default)] pub struct ApplicationState { + pub secret_manager_state: Option>, pub identity_state: Option>, pub library_state: Option>, pub authorization_state: Option>, pub issuance_state: Option>, pub holder_state: Option>, pub verification_state: Option>, + + pub key_generation_saga: Option, + pub key_removal_saga: Option, } pub fn app( ApplicationState { + secret_manager_state, identity_state, library_state, authorization_state, issuance_state, holder_state, verification_state, + key_generation_saga, + key_removal_saga, }: ApplicationState, ) -> Router { + let v1_identity_router = match ( + identity_state.clone(), + secret_manager_state.clone(), + key_generation_saga, + key_removal_saga, + ) { + (Some(identity_state), Some(secret_manager_state), Some(gen_saga), Some(rem_saga)) => { + let context = IdentityContext { + identity_state, + secret_manager_state, + key_generation_saga: gen_saga, + key_removal_saga: rem_saga, + }; + v1::identity::router(context) + } + _ => Router::new(), + }; let app = Router::new() .merge(identity_state.map(v0::identity::router).unwrap_or_default()) + .merge(v1_identity_router) .merge(library_state.map(v0::library::router).unwrap_or_default()) .merge( authorization_state diff --git a/agent_api_http/src/sagas/key_generation_saga.rs b/agent_api_http/src/sagas/key_generation_saga.rs new file mode 100644 index 00000000..276cc1a4 --- /dev/null +++ b/agent_api_http/src/sagas/key_generation_saga.rs @@ -0,0 +1,145 @@ +use agent_secret_manager::{ + managed_key::{aggregate::SigningAlgorithm, command::ManagedKeyCommand}, + state::SecretManagerState, +}; +use agent_shared::handlers::{command_handler, query_handler}; +use async_trait::async_trait; +use cqrs_es::{EventEnvelope, Query}; +use identity_iota::verification::VerificationMethod; +use identity_storage::KeyId; + +use agent_identity::{ + document::command::DocumentCommand, + service::command::ServiceCommand, + services::IdentityServices, + state::{IdentityState, DOMAIN_LINKAGE_SERVICE_ID}, +}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct KeyGenerationSaga { + secret_manager_state: Arc, + identity_state: Arc, + identity_services: Arc, +} + +impl KeyGenerationSaga { + pub fn new( + secret_manager_state: Arc, + identity_state: Arc, + identity_services: Arc, + ) -> Self { + Self { + secret_manager_state, + identity_state, + identity_services, + } + } + + pub async fn generate_default_keys(&self) -> Result<(), Box> { + let current_keys_n = query_handler("all_managed_keys", &self.secret_manager_state.query.all_managed_keys) + .await? + .map(|all_managed_keys_view| all_managed_keys_view.managed_keys.len()) + .unwrap_or_default(); + + if current_keys_n == 0 { + self.generate_key("EdDSA Key".to_string(), SigningAlgorithm::EdDSA) + .await?; + self.generate_key("ES256 Key".to_string(), SigningAlgorithm::ES256) + .await?; + } + + Ok(()) + } + + pub async fn generate_key( + &self, + alias: String, + signing_algorithm: SigningAlgorithm, + ) -> Result> { + // TODO: Add undo logic!!! + + let managed_key_id = uuid::Uuid::new_v4().to_string(); + + let command = ManagedKeyCommand::GenerateKey { + managed_key_id: managed_key_id.clone(), + alias, + signing_algorithm, + }; + + command_handler(&managed_key_id, &self.secret_manager_state.command.managed_key, command).await?; + + if let Some(managed_key_view) = + query_handler(&managed_key_id, &self.secret_manager_state.query.managed_key).await? + { + let key_id = managed_key_view.key_id.clone(); + let signing_algorithm = managed_key_view.signing_algorithm.unwrap(); + + if let Some(all_documents_view) = + query_handler("all_documents", &self.identity_state.query.all_documents).await? + { + for (document_id, document_view) in &all_documents_view.documents { + if document_view + .did_method + .map(|did_method| did_method.supports_update()) + .unwrap_or(false) + { + let command = DocumentCommand::AddVerificationMethod { + key_id: key_id.clone(), + signing_algorithm: signing_algorithm.clone(), + }; + + command_handler(&document_id, &self.identity_state.command.document, command).await?; + } + } + + let command = ServiceCommand::CreateDomainLinkageService { + service_id: DOMAIN_LINKAGE_SERVICE_ID.to_string(), + verification_methods: vec![], + }; + + command_handler( + &DOMAIN_LINKAGE_SERVICE_ID, + &self.identity_state.command.service, + command, + ) + .await + .ok(); + + let domain_linkage_service = + query_handler(DOMAIN_LINKAGE_SERVICE_ID, &self.identity_state.query.service).await?; + + for (document_id, document_view) in all_documents_view.documents { + if document_view + .did_method + .map(|did_method| did_method.supports_update()) + .unwrap_or(false) + { + if let Some(domain_linkage_service) = domain_linkage_service + .as_ref() + .and_then(|domain_linkage_service| domain_linkage_service.service.clone()) + { + let command = DocumentCommand::AddService { + service_id: DOMAIN_LINKAGE_SERVICE_ID.to_string(), + service: Box::new(domain_linkage_service), + }; + + command_handler(&document_id, &self.identity_state.command.document, command).await?; + } + + if document_view + .did_method + .map(|did_method| did_method.hosted_decentrally()) + .unwrap_or(false) + { + let command = DocumentCommand::PublishDocument; + + command_handler(&document_id, &self.identity_state.command.document, command).await?; + } + } + } + } + } + Ok(managed_key_id) + } +} diff --git a/agent_api_http/src/sagas/key_removal_saga.rs b/agent_api_http/src/sagas/key_removal_saga.rs new file mode 100644 index 00000000..f589dc24 --- /dev/null +++ b/agent_api_http/src/sagas/key_removal_saga.rs @@ -0,0 +1,78 @@ +use agent_secret_manager::{managed_key::command::ManagedKeyCommand, state::SecretManagerState}; +use agent_shared::handlers::{command_handler, query_handler}; +use async_trait::async_trait; +use cqrs_es::{EventEnvelope, Query}; +use identity_iota::verification::VerificationMethod; +use identity_storage::KeyId; + +use agent_identity::{document::command::DocumentCommand, services::IdentityServices, state::IdentityState}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct KeyRemovalSaga { + secret_manager_state: Arc, + identity_state: Arc, + identity_services: Arc, +} + +impl KeyRemovalSaga { + pub fn new( + secret_manager_state: Arc, + identity_state: Arc, + identity_services: Arc, + ) -> Self { + Self { + secret_manager_state, + identity_state, + identity_services, + } + } + + pub async fn remove_key(&self, managed_key_id: String) { + // TODO: Add undo logic!!! + + let command = ManagedKeyCommand::RemoveKey; + + command_handler(&managed_key_id, &self.secret_manager_state.command.managed_key, command) + .await + .unwrap(); + + if let Some(managed_key_view) = query_handler(&managed_key_id, &self.secret_manager_state.query.managed_key) + .await + .unwrap() + { + let key_id = managed_key_view.key_id.clone(); + + if let Some(all_documents_view) = query_handler("all_documents", &self.identity_state.query.all_documents) + .await + .unwrap() + { + for (document_id, document_view) in all_documents_view.documents { + if document_view + .did_method + .map(|did_method| did_method.supports_update()) + .unwrap_or(false) + { + let command = DocumentCommand::RemoveVerificationMethod { key_id: key_id.clone() }; + + command_handler(&document_id, &self.identity_state.command.document, command) + .await + .unwrap(); + + if document_view + .did_method + .map(|did_method| did_method.hosted_decentrally()) + .unwrap_or(false) + { + let command = DocumentCommand::PublishDocument; + + command_handler(&document_id, &self.identity_state.command.document, command) + .await + .unwrap(); + } + } + } + } + } + } +} diff --git a/agent_api_http/src/sagas/mod.rs b/agent_api_http/src/sagas/mod.rs new file mode 100644 index 00000000..4253b6d6 --- /dev/null +++ b/agent_api_http/src/sagas/mod.rs @@ -0,0 +1,2 @@ +pub mod key_generation_saga; +pub mod key_removal_saga; diff --git a/agent_api_http/src/utils.rs b/agent_api_http/src/utils.rs index 356e65a5..c26f5035 100644 --- a/agent_api_http/src/utils.rs +++ b/agent_api_http/src/utils.rs @@ -162,13 +162,80 @@ pub(crate) mod serde_explicit_null { } #[cfg(test)] -mod tests { +pub mod tests { use super::*; + use agent_authorization::services::AuthorizationServices; + use agent_authorization::state::AuthorizationState; + use agent_identity::services::{IdentityApplicationService, IdentityServices}; + use agent_issuance::services::IssuanceServices; + use agent_issuance::state::IssuanceState; + use agent_secret_manager::services::SecretManagerServices; + use agent_store::in_memory::InMemory; + use agent_store::{issuance_state, EventPublisher}; + use agent_verification::services::VerificationServices; + use agent_verification::state::VerificationState; use axum::body::Body; use axum::http::{Method, Request}; use oid4vc_core::utils::form_urlencoded::to_form_urlencoded_string; use serde::{Deserialize, Serialize}; use serde_json::json; + use std::sync::Arc; + + pub(crate) async fn main_service() -> Arc { + let secret_manager_services = Arc::new(SecretManagerServices::new().await); + let secret_manager_state = + Arc::new(agent_store::secret_manager_state(&InMemory, secret_manager_services.clone(), vec![]).await); + + let identity_services = Arc::new(IdentityServices::new(secret_manager_services.clone())); + let identity_state = Arc::new(agent_store::identity_state(&InMemory, identity_services, vec![]).await); + + let identity_application_service = Arc::new( + IdentityApplicationService::new( + secret_manager_state.clone(), + secret_manager_services.clone(), + identity_state.clone(), + ) + .await, + ); + identity_application_service + } + + pub(crate) async fn test_issuance_state( + main_service: Arc, + event_publishers: Vec>, + ) -> Arc { + Arc::new( + issuance_state( + &InMemory, + Arc::new(IssuanceServices::new(main_service)), + event_publishers, + ) + .await, + ) + } + + pub(crate) async fn test_authorization_state( + main_service: Arc, + ) -> Arc { + Arc::new( + agent_store::authorization_state(&InMemory, Arc::new(AuthorizationServices::new(main_service)), vec![]) + .await, + ) + } + + pub(crate) async fn test_verification_state( + main_service: Arc, + event_publishers: Vec>, + ) -> Arc { + Arc::new( + agent_store::verification_state( + &InMemory, + Arc::new(VerificationServices::new(main_service)), + event_publishers, + ) + .await, + ) + } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct TestObject { diff --git a/agent_api_http/src/v0/authorization/authorization_server/authorize.rs b/agent_api_http/src/v0/authorization/authorization_server/authorize.rs index 17f86265..aa4f299c 100644 --- a/agent_api_http/src/v0/authorization/authorization_server/authorize.rs +++ b/agent_api_http/src/v0/authorization/authorization_server/authorize.rs @@ -34,13 +34,15 @@ pub(crate) async fn authorize( #[cfg(test)] pub mod tests { use super::*; + use crate::utils::tests::{main_service, test_authorization_state, test_issuance_state}; use crate::v0::authorization::authorization_server::consent::tests::{get_consent, post_consent}; use crate::v0::authorization::authorization_server::par::tests::par; use crate::v0::issuance::credentials::tests::credentials; use crate::v0::issuance::offers::tests::offers; use crate::v0::{authorization, issuance}; + use agent_authorization::services::AuthorizationServices; use agent_authorization::state::UNIME_CLIENT_ID; - use agent_secret_manager::service::Service; + use agent_issuance::services::IssuanceServices; use agent_store::in_memory::InMemory; use agent_store::{authorization_state, issuance_state}; use axum::{ @@ -110,7 +112,9 @@ pub mod tests { #[serial_test::serial] #[tokio::test] async fn test_authorization_endpoint() { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let main_service = main_service().await; + + let issuance_state = test_issuance_state(main_service.clone(), vec![]).await; agent_issuance::state::initialize(&issuance_state).await.unwrap(); @@ -121,8 +125,8 @@ pub mod tests { let AuthorizationCode { issuer_state, .. } = authorization_code.unwrap(); let issuer_state = issuer_state.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; + agent_authorization::state::initialize(&authorization_state) .await .unwrap(); diff --git a/agent_api_http/src/v0/authorization/authorization_server/par.rs b/agent_api_http/src/v0/authorization/authorization_server/par.rs index 7589d3e1..d0ee4050 100644 --- a/agent_api_http/src/v0/authorization/authorization_server/par.rs +++ b/agent_api_http/src/v0/authorization/authorization_server/par.rs @@ -26,15 +26,17 @@ pub(crate) async fn par( #[cfg(test)] pub mod tests { use super::*; - use crate::v0::{ - authorization, - issuance::{self, credentials::tests::credentials, offers::tests::offers}, + use crate::{ + utils::tests::{main_service, test_authorization_state, test_issuance_state}, + v0::{ + authorization, + issuance::{self, credentials::tests::credentials, offers::tests::offers}, + }, }; use agent_authorization::state::UNIME_CLIENT_ID; use agent_authorization::{ domain::oauth2_authorization_request::aggregate::test_utils::code_challenge, state::UNIME_REDIRECT_URI, }; - use agent_secret_manager::service::Service; use agent_store::{authorization_state, in_memory::InMemory, issuance_state}; use axum::{ body::Body, @@ -104,7 +106,9 @@ pub mod tests { #[serial_test::serial] #[tokio::test] async fn test_pushed_authorization_request_endpoint() { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let main_service = main_service().await; + + let issuance_state = test_issuance_state(main_service.clone(), vec![]).await; agent_issuance::state::initialize(&issuance_state).await.unwrap(); @@ -115,8 +119,7 @@ pub mod tests { let AuthorizationCode { issuer_state, .. } = authorization_code.unwrap(); let issuer_state = issuer_state.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; agent_authorization::state::initialize(&authorization_state) .await .unwrap(); diff --git a/agent_api_http/src/v0/authorization/authorization_server/token.rs b/agent_api_http/src/v0/authorization/authorization_server/token.rs index 2776848e..9f090e7d 100644 --- a/agent_api_http/src/v0/authorization/authorization_server/token.rs +++ b/agent_api_http/src/v0/authorization/authorization_server/token.rs @@ -25,6 +25,7 @@ pub(crate) async fn token( #[cfg(test)] pub mod tests { use super::*; + use crate::utils::tests::{main_service, test_authorization_state, test_issuance_state}; use crate::v0::{ authorization::{ self, @@ -40,7 +41,6 @@ pub mod tests { use agent_authorization::{ domain::oauth2_authorization_request::aggregate::test_utils::code_verifier, state::UNIME_REDIRECT_URI, }; - use agent_secret_manager::service::Service; use agent_store::{authorization_state, in_memory::InMemory, issuance_state}; use axum::{ body::Body, @@ -139,7 +139,9 @@ pub mod tests { #[serial_test::serial] #[tokio::test] async fn test_token_endpoint(#[case] is_pre_authorized: bool) { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let main_service = main_service().await; + + let issuance_state = test_issuance_state(main_service.clone(), vec![]).await; agent_issuance::state::initialize(&issuance_state).await.unwrap(); @@ -154,8 +156,7 @@ pub mod tests { credentials(&mut app, &credential_configuration_id).await; let grants = offers(&mut app, &credential_configuration_id).await.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; agent_authorization::state::initialize(&authorization_state) .await diff --git a/agent_api_http/src/v0/identity/mod.rs b/agent_api_http/src/v0/identity/mod.rs index 945d8332..4cdca393 100644 --- a/agent_api_http/src/v0/identity/mod.rs +++ b/agent_api_http/src/v0/identity/mod.rs @@ -7,6 +7,10 @@ pub mod well_known; pub mod error; +use crate::{ + v0::identity::profiles::{get_profile, patch_profile}, + API_VERSION, +}; use agent_identity::state::IdentityState; use axum::{ routing::{get, post}, @@ -18,11 +22,6 @@ use services::{linked_vp::linked_vp, service, services}; use std::sync::Arc; use well_known::{did::did, did_configuration::did_configuration}; -use crate::{ - v0::identity::profiles::{get_profile, patch_profile}, - API_VERSION, -}; - pub fn router(identity_state: Arc) -> Router { Router::new() .nest( diff --git a/agent_api_http/src/v0/issuance/credential_issuer/credential.rs b/agent_api_http/src/v0/issuance/credential_issuer/credential.rs index e581101d..2a2ed59a 100644 --- a/agent_api_http/src/v0/issuance/credential_issuer/credential.rs +++ b/agent_api_http/src/v0/issuance/credential_issuer/credential.rs @@ -147,10 +147,10 @@ pub mod tests { v0::issuance::{credentials::CredentialsEndpointRequest, offers::tests::offers}, }; + use crate::utils::tests::{main_service, test_authorization_state, test_issuance_state}; use agent_event_publisher_http::EventPublisherHttp; use agent_issuance::credential::aggregate::CredentialExpiry; use agent_issuance::offer::event::OfferEvent; - use agent_secret_manager::service::Service; use agent_shared::config::{set_config, Events}; use agent_store::authorization_state; use agent_store::{in_memory::InMemory, issuance_state, EventPublisher}; @@ -159,6 +159,7 @@ pub mod tests { http::{self, Request}, Router, }; + use rstest::rstest; use serde_json::{json, Value}; use std::sync::Arc; @@ -352,7 +353,9 @@ pub mod tests { (None, Default::default()) }; - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), issuance_event_publishers).await); + let main_service = main_service().await; + + let issuance_state = test_issuance_state(main_service.clone(), issuance_event_publishers).await; agent_issuance::state::initialize(&issuance_state).await.unwrap(); let mut issuance_app = router(issuance_state.clone()); @@ -382,8 +385,7 @@ pub mod tests { let grants = offers(&mut issuance_app, &credential_configuration_id).await.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; agent_authorization::state::initialize(&authorization_state) .await .unwrap(); diff --git a/agent_api_http/src/v0/issuance/credential_issuer/notification.rs b/agent_api_http/src/v0/issuance/credential_issuer/notification.rs index 8af6c52f..77d08a7a 100644 --- a/agent_api_http/src/v0/issuance/credential_issuer/notification.rs +++ b/agent_api_http/src/v0/issuance/credential_issuer/notification.rs @@ -63,12 +63,12 @@ pub async fn notification( #[cfg(test)] mod tests { use super::*; + use crate::utils::tests::{main_service, test_authorization_state, test_issuance_state}; use crate::v0::authorization::authorization_server::token::tests::token; use crate::v0::issuance::credential_issuer::credential::tests::credential; use crate::v0::issuance::credentials::tests::credentials; use crate::v0::issuance::offers::tests::offers; use crate::v0::{authorization, issuance}; - use agent_secret_manager::service::Service; use agent_store::in_memory::InMemory; use agent_store::{authorization_state, issuance_state}; use axum::{body::Body, http::Request}; @@ -80,15 +80,16 @@ mod tests { #[serial_test::serial] #[tokio::test] async fn test_valid_notification_request() { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let main_service = main_service().await; + let issuance_state = test_issuance_state(main_service.clone(), vec![]).await; + agent_issuance::state::initialize(&issuance_state).await.unwrap(); let mut issuance_app = issuance::router(issuance_state.clone()); credentials(&mut issuance_app, "001").await; let grants = offers(&mut issuance_app, "001").await.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; agent_authorization::state::initialize(&authorization_state) .await .unwrap(); @@ -119,15 +120,17 @@ mod tests { #[tokio::test] async fn test_invalid_notification_request() { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let main_service = main_service().await; + let issuance_state = test_issuance_state(main_service.clone(), vec![]).await; + agent_issuance::state::initialize(&issuance_state).await.unwrap(); let mut issuance_app = issuance::router(issuance_state.clone()); credentials(&mut issuance_app, "001").await; let grants = offers(&mut issuance_app, "001").await.unwrap(); - let authorization_state = - Arc::new(authorization_state(&InMemory, Service::default(), Default::default()).await); + let authorization_state = test_authorization_state(main_service).await; + agent_authorization::state::initialize(&authorization_state) .await .unwrap(); diff --git a/agent_api_http/src/v0/issuance/credential_issuer/token_status_list.rs b/agent_api_http/src/v0/issuance/credential_issuer/token_status_list.rs index 7bca40d1..5781b752 100644 --- a/agent_api_http/src/v0/issuance/credential_issuer/token_status_list.rs +++ b/agent_api_http/src/v0/issuance/credential_issuer/token_status_list.rs @@ -42,7 +42,7 @@ pub mod tests { use std::sync::Arc; use agent_issuance::state::initialize; - use agent_secret_manager::{service::Service, subject::Subject}; + use agent_secret_manager::subject::Subject; use agent_shared::config::{config, BITS_PER_STATUS, STATUS_LIST_BYTES_AMOUNT}; use agent_store::{in_memory::InMemory, issuance_state}; use axum::body::{self, Body}; @@ -54,14 +54,17 @@ pub mod tests { }; use oid4vc_core::authentication::verify::Verify; - use crate::v0::issuance::router; + use crate::{ + utils::tests::{main_service, test_issuance_state}, + v0::issuance::router, + }; use tower::Service as _; /// This test calls the token status list endpoint which in turn calls the function above. /// The remainder of the test breaks down the Token Status List response in various steps and checks these steps one by one. #[tokio::test] pub async fn test_token_status_list() { - let issuance_state = Arc::new(issuance_state(&InMemory, Service::default(), Default::default()).await); + let issuance_state = test_issuance_state(main_service().await, vec![]).await; initialize(&issuance_state).await.unwrap(); let relying_party_state = Subject::default(); diff --git a/agent_api_http/src/v0/issuance/credential_issuer/well_known/oauth_authorization_server.rs b/agent_api_http/src/v0/issuance/credential_issuer/well_known/oauth_authorization_server.rs index 9119a8e4..4f2dc498 100644 --- a/agent_api_http/src/v0/issuance/credential_issuer/well_known/oauth_authorization_server.rs +++ b/agent_api_http/src/v0/issuance/credential_issuer/well_known/oauth_authorization_server.rs @@ -26,9 +26,11 @@ pub(crate) async fn oauth_authorization_server(State(state): State]; - let verification_state = Arc::new(verification_state(&InMemory, Service::default(), event_publishers).await); + let verification_state = test_verification_state(main_service().await, event_publishers).await; let mut app = router(verification_state); diff --git a/agent_api_http/src/v0/verification/relying_party/request.rs b/agent_api_http/src/v0/verification/relying_party/request.rs index 37ae3e7e..ebf04a9b 100644 --- a/agent_api_http/src/v0/verification/relying_party/request.rs +++ b/agent_api_http/src/v0/verification/relying_party/request.rs @@ -34,9 +34,9 @@ pub(crate) async fn request( #[cfg(test)] pub mod tests { use super::*; - use crate::v0::verification::authorization_requests::tests::authorization_requests; + use crate::utils::tests::test_verification_state; use crate::v0::verification::router; - use agent_secret_manager::service::Service; + use crate::{utils::tests::main_service, v0::verification::authorization_requests::tests::authorization_requests}; use agent_store::{in_memory::InMemory, verification_state}; use axum::{ body::Body, @@ -74,7 +74,7 @@ pub mod tests { #[tokio::test] #[tracing_test::traced_test] async fn test_request_endpoint() { - let verification_state = Arc::new(verification_state(&InMemory, Service::default(), Default::default()).await); + let verification_state = test_verification_state(main_service().await, vec![]).await; let mut app = router(verification_state); diff --git a/agent_api_http/src/v1/identity/keys/mod.rs b/agent_api_http/src/v1/identity/keys/mod.rs new file mode 100644 index 00000000..f3e284d7 --- /dev/null +++ b/agent_api_http/src/v1/identity/keys/mod.rs @@ -0,0 +1,180 @@ +use crate::IdentityContext; + +use agent_secret_manager::managed_key::{ + aggregate::{ManagedKey, SigningAlgorithm}, + command::ManagedKeyCommand, +}; +use agent_shared::handlers::{command_handler, query_handler}; +use axum::extract::{Json, State}; +use http_api_problem::ApiError; +use hyper::StatusCode; +use serde::{Deserialize, Serialize}; + +/// Data transfer object for Managed Keys. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagedKeyDto { + #[serde(rename = "id")] + pub managed_key_id: String, + pub key_id: String, + pub alias: String, + pub signing_algorithm: Option, + pub is_signing_key: bool, +} + +impl From for ManagedKeyDto { + fn from(value: ManagedKey) -> Self { + Self { + managed_key_id: value.managed_key_id, + key_id: value.key_id, + alias: value.alias, + signing_algorithm: value.signing_algorithm, + is_signing_key: value.is_signing_key, + } + } +} + +#[derive(Deserialize, Serialize, Default)] +#[serde(default, rename_all = "camelCase")] +pub struct PostGenerateKey { + pub alias: String, + pub signature_algorithm: Option, +} + +#[axum_macros::debug_handler] +pub(crate) async fn generate_key( + State(context): State, + Json(payload): Json, +) -> Result<(StatusCode, String), ApiError> { + let signing_algorithm = payload.signature_algorithm.unwrap_or(SigningAlgorithm::ES256); + + let managed_key_id = context + .key_generation_saga + .generate_key(payload.alias, signing_algorithm) + .await + .map_err(|e| { + tracing::error!("Saga failed: {:?}", e); + ApiError::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; + + Ok((StatusCode::CREATED, managed_key_id)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PostRemoveKey { + pub key_id: String, +} + +pub(crate) async fn remove_key( + State(context): State, + Json(payload): Json, +) -> Result { + let managed_key_id = get_managed_key_id(&payload.key_id, &context).await?; + + context.key_removal_saga.remove_key(managed_key_id).await; + + Ok(StatusCode::NO_CONTENT) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PostRenameKeyAlias { + pub key_id: String, + pub new_alias: String, +} + +pub(crate) async fn rename_key_alias( + State(context): State, + Json(payload): Json, +) -> Result { + let managed_key_id = get_managed_key_id(&payload.key_id, &context).await?; + + let command = ManagedKeyCommand::UpdateKeyAlias { + new_alias: payload.new_alias, + }; + + command_handler( + &managed_key_id, + &context.secret_manager_state.command.managed_key, + command, + ) + .await + .map_err(|e| { + tracing::error!("Saga failed: {:?}", e); + ApiError::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; + + Ok(StatusCode::NO_CONTENT) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PostSetSigningKey { + pub key_id: String, +} + +pub(crate) async fn set_signing_key( + State(context): State, + Json(payload): Json, +) -> Result { + let managed_key_id = get_managed_key_id(&payload.key_id, &context).await?; + + let command = ManagedKeyCommand::SetSigningKey {}; + + command_handler( + &managed_key_id, + &context.secret_manager_state.command.managed_key, + command, + ) + .await + .map_err(|e| { + tracing::error!("Saga failed: {:?}", e); + ApiError::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; + + Ok(StatusCode::NO_CONTENT) +} + +#[axum_macros::debug_handler] +pub(crate) async fn list_all( + State(context): State, +) -> Result<(StatusCode, Json>), ApiError> { + let view = query_handler("all_managed_keys", &context.secret_manager_state.query.all_managed_keys) + .await + .map_err(|e| { + tracing::error!("Query failed: {:?}", e); + ApiError::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; + + let keys = match view { + Some(all_keys_view) => all_keys_view + .managed_keys + .into_values() + .filter_map(|key_view| (!key_view.is_removed).then(|| ManagedKeyDto::from(key_view))) + .collect(), + None => Vec::new(), + }; + + Ok((StatusCode::OK, Json(keys))) +} + +// Helper function to fetch the managed_key_id by its key_id. +async fn get_managed_key_id(key_id: &str, context: &IdentityContext) -> Result { + let view = query_handler("all_managed_keys", &context.secret_manager_state.query.all_managed_keys) + .await + .map_err(|e| { + tracing::error!("Query failed: {:?}", e); + ApiError::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; + + match view { + Some(all_keys_view) => all_keys_view + .managed_keys + .into_values() + .find(|key_view| key_view.key_id == key_id && !key_view.is_removed) + .map(|key_view| key_view.managed_key_id) + .ok_or_else(|| ApiError::new(StatusCode::NOT_FOUND)), + None => Err(ApiError::new(StatusCode::NOT_FOUND)), + } +} diff --git a/agent_api_http/src/v1/identity/mod.rs b/agent_api_http/src/v1/identity/mod.rs new file mode 100644 index 00000000..db7c92a4 --- /dev/null +++ b/agent_api_http/src/v1/identity/mod.rs @@ -0,0 +1,36 @@ +pub mod keys; + +use crate::sagas::key_generation_saga::KeyGenerationSaga; +use crate::sagas::key_removal_saga::KeyRemovalSaga; +use crate::v1::identity::keys::{generate_key, list_all, remove_key, rename_key_alias, set_signing_key}; +use crate::API_VERSION; +use agent_identity::state::IdentityState; +use agent_secret_manager::state::SecretManagerState; +use axum::{ + routing::{get, post}, + Router, +}; + +use std::sync::Arc; + +#[derive(Clone)] +pub struct IdentityContext { + pub identity_state: Arc, + pub secret_manager_state: Arc, + pub key_generation_saga: KeyGenerationSaga, + pub key_removal_saga: KeyRemovalSaga, +} + +pub fn router(context: IdentityContext) -> Router { + Router::new() + .nest( + API_VERSION, + Router::new() + .route("/keys/generate-new-key", post(generate_key)) + .route("/keys/remove-key", post(remove_key)) + .route("/keys/rename-key-alias", post(rename_key_alias)) + .route("/keys/set-signing-key", post(set_signing_key)) + .route("/keys/list-all", get(list_all)), + ) + .with_state(context) +} diff --git a/agent_api_http/src/v1/mod.rs b/agent_api_http/src/v1/mod.rs new file mode 100644 index 00000000..db53a0c9 --- /dev/null +++ b/agent_api_http/src/v1/mod.rs @@ -0,0 +1 @@ +pub mod identity; diff --git a/agent_application/src/main.rs b/agent_application/src/main.rs index 0910715c..3f184ddd 100644 --- a/agent_application/src/main.rs +++ b/agent_application/src/main.rs @@ -6,18 +6,21 @@ mod probes; use agent_api_http::{ app, metrics::{metrics, track_metrics}, + sagas::{key_generation_saga::KeyGenerationSaga, key_removal_saga::KeyRemovalSaga}, ApplicationState, }; use agent_authorization::services::AuthorizationServices; use agent_event_publisher_http::EventPublisherHttp; use agent_event_publisher_nats::EventPublisherNats; use agent_holder::services::HolderServices; -use agent_identity::services::IdentityServices; +use agent_identity::services::{IdentityApplicationService, IdentityServices, IDENTITY_APPLICATION_SERVICE}; use agent_issuance::{ application::policies::issuer_metadata_synchronization_policy::IssuerMetadataSynchronizationPolicy, services::IssuanceServices, }; -use agent_secret_manager::{service::Service as _, subject::Subject}; +use agent_secret_manager::services::{ + SecretManagerDomainService, SecretManagerServices, SECRET_MANAGER_DOMAIN_SERVICE, +}; use agent_shared::config::{config, EventStoreType}; use agent_store::{in_memory::InMemory, mongodb::MongoDB, postgres::Postgres, EventPublisher}; use agent_verification::services::VerificationServices; @@ -29,17 +32,11 @@ use tracing::info; #[tokio::main] async fn main() -> io::Result<()> { - let subject = Arc::new(Subject::new().await); - - let identity_services = Arc::new(IdentityServices::new(subject.clone())); - let authorization_services = Arc::new(AuthorizationServices::new(subject.clone())); - let issuance_services = Arc::new(IssuanceServices::new(subject.clone())); - let holder_services = Arc::new(HolderServices::new(subject.clone())); - let verification_services = Arc::new(VerificationServices::new(subject.clone())); - // TODO: Currently all these `*_event_publishers` are exactly the same, which is weird. We need some sort of layer // between `agent_application` and `agent_store` that will provide a cleaner way of initializing the event // publishers and sending them over to `agent_store`. + let secret_manager_event_publishers: Vec> = + vec![Box::new(EventPublisherHttp::load().unwrap())]; let identity_event_publishers: Vec> = vec![Box::new(EventPublisherHttp::load().unwrap())]; let issuance_event_publishers: Vec> = vec![ Box::new(EventPublisherHttp::load().unwrap()), @@ -52,120 +49,170 @@ async fn main() -> io::Result<()> { let verification_event_publishers: Vec> = vec![Box::new(EventPublisherHttp::load().unwrap())]; + let secret_manager_services = Arc::new(SecretManagerServices::new().await); + let secret_manager_state = match config().event_store.type_ { + EventStoreType::Postgres => { + let builder = Postgres::new().await; + Arc::new( + agent_store::secret_manager_state( + &builder, + secret_manager_services.clone(), + secret_manager_event_publishers, + ) + .await, + ) + } + EventStoreType::MongoDb => { + let builder = MongoDB::new().await; + Arc::new( + agent_store::secret_manager_state( + &builder, + secret_manager_services.clone(), + secret_manager_event_publishers, + ) + .await, + ) + } + EventStoreType::InMemory => Arc::new( + agent_store::secret_manager_state( + &InMemory, + secret_manager_services.clone(), + secret_manager_event_publishers, + ) + .await, + ), + }; + + let identity_services = Arc::new(IdentityServices::new(secret_manager_services.clone())); + let identity_state = match config().event_store.type_ { + EventStoreType::Postgres => { + let builder = Postgres::new().await; + Arc::new(agent_store::identity_state(&builder, identity_services.clone(), identity_event_publishers).await) + } + EventStoreType::MongoDb => { + let builder = MongoDB::new().await; + Arc::new(agent_store::identity_state(&builder, identity_services.clone(), identity_event_publishers).await) + } + EventStoreType::InMemory => { + Arc::new(agent_store::identity_state(&InMemory, identity_services.clone(), identity_event_publishers).await) + } + }; + + let identity_application_service = Arc::new( + IdentityApplicationService::new( + secret_manager_state.clone(), + secret_manager_services.clone(), + identity_state.clone(), + ) + .await, + ); + + IDENTITY_APPLICATION_SERVICE + .set(identity_application_service.clone()) + .unwrap(); + SECRET_MANAGER_DOMAIN_SERVICE + .set(Arc::new(SecretManagerDomainService::new(secret_manager_state.clone()))) + .unwrap(); + + let authorization_services = Arc::new(AuthorizationServices::new(identity_application_service.clone())); + let issuance_services = Arc::new(IssuanceServices::new(identity_application_service.clone())); + let holder_services = Arc::new(HolderServices::new(identity_application_service.clone())); + let verification_services = Arc::new(VerificationServices::new(identity_application_service.clone())); + // TODO: Refactor this to reduce code duplication. - let (identity_state, library_state, authorization_state, issuance_state, holder_state, verification_state) = - match config().event_store.type_ { - EventStoreType::Postgres => { - let builder = Postgres::new().await; - - let issuance_state = - Arc::new(agent_store::issuance_state(&builder, issuance_services, issuance_event_publishers).await); - - let issuer_metadata_synchronization_policy = - IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); - - ( - Arc::new(agent_store::identity_state(&builder, identity_services, identity_event_publishers).await), - Arc::new( - agent_store::library_state( - &builder, - library_event_publishers, - vec![Box::new(issuer_metadata_synchronization_policy)], - ) - .await, - ), - Arc::new( - agent_store::authorization_state( - &builder, - authorization_services, - authorization_event_publishers, - ) + let (library_state, authorization_state, issuance_state, holder_state, verification_state) = match config() + .event_store + .type_ + { + EventStoreType::Postgres => { + let builder = Postgres::new().await; + + let issuance_state = + Arc::new(agent_store::issuance_state(&builder, issuance_services, issuance_event_publishers).await); + + let issuer_metadata_synchronization_policy = + IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); + + ( + Arc::new( + agent_store::library_state( + &builder, + library_event_publishers, + vec![Box::new(issuer_metadata_synchronization_policy)], + ) + .await, + ), + Arc::new( + agent_store::authorization_state(&builder, authorization_services, authorization_event_publishers) .await, - ), - issuance_state, - Arc::new(agent_store::holder_state(&builder, holder_services, holder_event_publishers).await), - Arc::new( - agent_store::verification_state(&builder, verification_services, verification_event_publishers) - .await, - ), - ) - } - EventStoreType::MongoDb => { - let builder = MongoDB::new().await; - - let issuance_state = - Arc::new(agent_store::issuance_state(&builder, issuance_services, issuance_event_publishers).await); - - let issuer_metadata_synchronization_policy = - IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); - - ( - Arc::new(agent_store::identity_state(&builder, identity_services, identity_event_publishers).await), - Arc::new( - agent_store::library_state( - &builder, - library_event_publishers, - vec![Box::new(issuer_metadata_synchronization_policy)], - ) + ), + issuance_state, + Arc::new(agent_store::holder_state(&builder, holder_services, holder_event_publishers).await), + Arc::new( + agent_store::verification_state(&builder, verification_services, verification_event_publishers) .await, - ), - Arc::new( - agent_store::authorization_state( - &builder, - authorization_services, - authorization_event_publishers, - ) + ), + ) + } + EventStoreType::MongoDb => { + let builder = MongoDB::new().await; + + let issuance_state = + Arc::new(agent_store::issuance_state(&builder, issuance_services, issuance_event_publishers).await); + + let issuer_metadata_synchronization_policy = + IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); + + ( + Arc::new( + agent_store::library_state( + &builder, + library_event_publishers, + vec![Box::new(issuer_metadata_synchronization_policy)], + ) + .await, + ), + Arc::new( + agent_store::authorization_state(&builder, authorization_services, authorization_event_publishers) .await, - ), - issuance_state, - Arc::new(agent_store::holder_state(&builder, holder_services, holder_event_publishers).await), - Arc::new( - agent_store::verification_state(&builder, verification_services, verification_event_publishers) - .await, - ), - ) - } - EventStoreType::InMemory => { - let issuance_state = Arc::new( - agent_store::issuance_state(&InMemory, issuance_services, issuance_event_publishers).await, - ); - - let issuer_metadata_synchronization_policy = - IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); - - ( - Arc::new( - agent_store::identity_state(&InMemory, identity_services, identity_event_publishers).await, - ), - Arc::new( - agent_store::library_state( - &InMemory, - library_event_publishers, - vec![Box::new(issuer_metadata_synchronization_policy)], - ) + ), + issuance_state, + Arc::new(agent_store::holder_state(&builder, holder_services, holder_event_publishers).await), + Arc::new( + agent_store::verification_state(&builder, verification_services, verification_event_publishers) .await, - ), - Arc::new( - agent_store::authorization_state( - &InMemory, - authorization_services, - authorization_event_publishers, - ) + ), + ) + } + EventStoreType::InMemory => { + let issuance_state = + Arc::new(agent_store::issuance_state(&InMemory, issuance_services, issuance_event_publishers).await); + + let issuer_metadata_synchronization_policy = + IssuerMetadataSynchronizationPolicy::new(issuance_state.clone()); + + ( + Arc::new( + agent_store::library_state( + &InMemory, + library_event_publishers, + vec![Box::new(issuer_metadata_synchronization_policy)], + ) + .await, + ), + Arc::new( + agent_store::authorization_state(&InMemory, authorization_services, authorization_event_publishers) .await, - ), - issuance_state, - Arc::new(agent_store::holder_state(&InMemory, holder_services, holder_event_publishers).await), - Arc::new( - agent_store::verification_state( - &InMemory, - verification_services, - verification_event_publishers, - ) + ), + issuance_state, + Arc::new(agent_store::holder_state(&InMemory, holder_services, holder_event_publishers).await), + Arc::new( + agent_store::verification_state(&InMemory, verification_services, verification_event_publishers) .await, - ), - ) - } - }; + ), + ) + } + }; info!("{:?}", config()); @@ -176,16 +223,34 @@ async fn main() -> io::Result<()> { agent_authorization::state::initialize(&authorization_state) .await .unwrap(); - agent_identity::state::initialize(&identity_state).await.unwrap(); + agent_identity::state::initialize(&identity_state).await.ok(); agent_issuance::state::initialize(&issuance_state).await.unwrap(); + let key_generation_saga = KeyGenerationSaga::new( + secret_manager_state.clone(), + identity_state.clone(), + identity_services.clone(), + ); + + key_generation_saga.generate_default_keys().await.ok(); + + let key_removal_saga = KeyRemovalSaga::new( + secret_manager_state.clone(), + identity_state.clone(), + identity_services.clone(), + ); + let app = app(ApplicationState { + secret_manager_state: Some(secret_manager_state), identity_state: Some(identity_state), library_state: Some(library_state), authorization_state: Some(authorization_state), issuance_state: Some(issuance_state), holder_state: Some(holder_state), verification_state: Some(verification_state), + + key_generation_saga: Some(key_generation_saga), + key_removal_saga: Some(key_removal_saga), }); let metadata_state = metadata::MetadataState { diff --git a/agent_authorization/Cargo.toml b/agent_authorization/Cargo.toml index 32a6e239..8cbb1f94 100644 --- a/agent_authorization/Cargo.toml +++ b/agent_authorization/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] +agent_identity = { path = "../agent_identity" } agent_issuance = { path = "../agent_issuance" } agent_shared = { path = "../agent_shared" } agent_secret_manager = { path = "../agent_secret_manager" } diff --git a/agent_authorization/src/services.rs b/agent_authorization/src/services.rs index 139ca76b..696d96e2 100644 --- a/agent_authorization/src/services.rs +++ b/agent_authorization/src/services.rs @@ -1,12 +1,14 @@ -use agent_secret_manager::{service::Service, subject::SubjectExt}; +use agent_identity::services::IdentityApplicationService; use std::sync::Arc; pub struct AuthorizationServices { - pub signer: Arc, + pub identity_application_service: Arc, } -impl Service for AuthorizationServices { - fn new(signer: Arc) -> Self { - Self { signer } +impl AuthorizationServices { + pub fn new(identity_application_service: Arc) -> Self { + Self { + identity_application_service, + } } } diff --git a/agent_event_publisher_http/Cargo.toml b/agent_event_publisher_http/Cargo.toml index 9ca85a9c..ed90f524 100644 --- a/agent_event_publisher_http/Cargo.toml +++ b/agent_event_publisher_http/Cargo.toml @@ -10,6 +10,7 @@ agent_holder = { path = "../agent_holder" } agent_identity = { path = "../agent_identity" } agent_issuance = { path = "../agent_issuance" } agent_library = { path = "../agent_library" } +agent_secret_manager = { path = "../agent_secret_manager" } agent_shared = { path = "../agent_shared" } agent_store = { path = "../agent_store" } agent_verification = { path = "../agent_verification" } diff --git a/agent_event_publisher_http/src/lib.rs b/agent_event_publisher_http/src/lib.rs index 418ddbf1..c05d4ac7 100644 --- a/agent_event_publisher_http/src/lib.rs +++ b/agent_event_publisher_http/src/lib.rs @@ -11,13 +11,14 @@ use agent_issuance::{ credential::aggregate::Credential, offer::aggregate::Offer, server_config::aggregate::ServerConfig, }; use agent_library::template::aggregate::Template; +use agent_secret_manager::managed_key::aggregate::ManagedKey; use agent_shared::config::config; use agent_store::{ AccessTokenEventPublisher, AuthorizationCodeEventPublisher, AuthorizationRequestEventPublisher, ClientEventPublisher, ConnectionEventPublisher, CredentialEventPublisher, DocumentEventPublisher, EventPublisher, - HolderCredentialEventPublisher, OAuth2AuthorizationRequestEventPublisher, OfferEventPublisher, - PresentationEventPublisher, ProfileEventPublisher, ReceivedOfferEventPublisher, ServerConfigEventPublisher, - ServiceEventPublisher, TemplateEventPublisher, + HolderCredentialEventPublisher, ManagedKeyEventPublisher, OAuth2AuthorizationRequestEventPublisher, + OfferEventPublisher, PresentationEventPublisher, ProfileEventPublisher, ReceivedOfferEventPublisher, + ServerConfigEventPublisher, ServiceEventPublisher, TemplateEventPublisher, }; use agent_verification::authorization_request::aggregate::AuthorizationRequest; use async_trait::async_trait; @@ -41,6 +42,7 @@ pub struct EventPublisherHttp { pub document: Option>, pub profile: Option>, pub service: Option>, + pub managed_key: Option>, // Library pub template: Option>, @@ -173,6 +175,19 @@ impl EventPublisherHttp { ) }); + let managed_key = (!event_publisher_http.events.managed_key.is_empty()).then(|| { + AggregateEventPublisherHttp::::new( + event_publisher_http.target_url.clone(), + event_publisher_http.headers.clone(), + event_publisher_http + .events + .managed_key + .iter() + .map(ToString::to_string) + .collect(), + ) + }); + let template = (!event_publisher_http.events.template.is_empty()).then(|| { AggregateEventPublisherHttp::