From 194710e5a58d481b54542c47d30b8afe3a9ba272 Mon Sep 17 00:00:00 2001 From: Hermann-Core Date: Mon, 21 Oct 2024 08:16:56 +0100 Subject: [PATCH] Fixes build errors --- .gitignore | 5 +- Cargo.toml | 9 +- crates/database/src/lib.rs | 30 ++-- crates/did-utils/src/didkit.rs | 2 +- crates/keystore/src/lib.rs | 25 +-- crates/plugins/Cargo.toml | 20 +++ crates/plugins/did-endpoint/src/didgen.rs | 88 +++++++--- crates/plugins/did-endpoint/src/util.rs | 2 +- .../plugins/mediator-coordination/Cargo.toml | 8 +- .../mediator-coordination/src/client/dic.rs | 17 +- .../mediator-coordination/src/jose/jws.rs | 17 +- .../plugins/mediator-coordination/src/lib.rs | 3 +- .../mediator-coordination/src/model/coord.rs | 2 +- .../src/model/stateful/coord.rs | 2 +- .../src/web/handler/midlw.rs | 2 +- .../src/web/handler/stateful.rs | 42 ++--- crates/plugins/oob-messages/src/lib.rs | 3 +- crates/plugins/shared/Cargo.toml | 6 +- crates/plugins/shared/src/utils/filesystem.rs | 154 ++++++++++++++++++ .../plugins/shared/src/utils/plugins_utils.rs | 33 ++++ crates/plugins/src/lib.rs | 1 + crates/plugins/src/midlw.rs | 12 +- crates/plugins/src/plugin.rs | 73 +++------ crates/plugins/src/web.rs | 1 + crates/plugins/src/web/dispatcher.rs | 39 ++--- src/plugins.rs | 2 + 26 files changed, 408 insertions(+), 190 deletions(-) create mode 100644 crates/plugins/shared/src/utils/filesystem.rs diff --git a/.gitignore b/.gitignore index db279e27..ba3de5ca 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,10 @@ Cargo.lock # Environment variables files .env.example -.env.test +.env # Reference crate mediator-server + +# Test directory +test/ diff --git a/Cargo.toml b/Cargo.toml index 5707a20c..5404fab4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,16 +93,23 @@ tower-http = { workspace = true, features = ["catch-panic", "trace"] } # optional dependencies chrono = { workspace = true, optional = true } +plugins = { workspace = true, optional = true } did-endpoint = { workspace = true, optional = true } oob-messages = { workspace = true, optional = true } [features] -default = ["plugin-index", "plugin-did_endpoint", "plugin-oob_messages"] +default = [ + "plugin-index", + "plugin-did_endpoint", + "plugin-oob_messages", + "plugin-plugins", +] plugin-index = ["dep:chrono"] plugin-did_endpoint = ["dep:did-endpoint"] plugin-oob_messages = ["dep:oob-messages"] +plugin-plugins = ["dep:plugins"] [dev-dependencies] diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index d8a9b15e..1e05c34d 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -9,7 +9,7 @@ use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; use std::sync::Arc; use thiserror::Error; -use tokio::{runtime::Runtime, sync::Mutex}; +use tokio::sync::Mutex; /// A trait that ensures the entity has an `id` field. pub trait Identifiable { @@ -33,7 +33,7 @@ pub enum RepositoryError { static MONGO_DB: OnceCell>> = OnceCell::new(); /// Get a handle to a database. -/// +/// /// Many threads may call this function concurrently with different initializing functions, /// but it is guaranteed that only one function will be executed. pub fn get_or_init_database() -> Arc> { @@ -42,17 +42,17 @@ pub fn get_or_init_database() -> Arc> { let mongo_uri = std::env::var("MONGO_URI").expect("MONGO_URI env variable required"); let mongo_dbn = std::env::var("MONGO_DBN").expect("MONGO_DBN env variable required"); - // Create a runtime to run the async MongoDB initialization synchronously - let rt = Runtime::new().unwrap(); - - let db = rt.block_on(async { - let client_options = ClientOptions::parse(mongo_uri) - .await - .expect("Failed to parse Mongo URI"); - let client = - Client::with_options(client_options).expect("Failed to create MongoDB client"); - - client.database(&mongo_dbn) + // Create a handle to a database. + let db = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async move { + let client_options = ClientOptions::parse(mongo_uri) + .await + .expect("Failed to parse Mongo URI"); + let client = Client::with_options(client_options) + .expect("Failed to create MongoDB client"); + + client.database(&mongo_dbn) + }) }); // Get a handle to a database. @@ -141,9 +141,7 @@ where let collection = collection.lock().await; // Delete the entity from the database - collection - .delete_one(doc! {"_id": id}, None) - .await?; + collection.delete_one(doc! {"_id": id}, None).await?; Ok(()) } diff --git a/crates/did-utils/src/didkit.rs b/crates/did-utils/src/didkit.rs index cafc3a20..2fe92da6 100644 --- a/crates/did-utils/src/didkit.rs +++ b/crates/did-utils/src/didkit.rs @@ -70,7 +70,7 @@ impl Document { also_known_as: None, controller: None, authentication: Some(vec![]), - assertion_method: None, + assertion_method: Some(vec![]), capability_delegation: None, capability_invocation: None, key_agreement: Some(vec![]), diff --git a/crates/keystore/src/lib.rs b/crates/keystore/src/lib.rs index fcfad7fd..4fcf8bca 100644 --- a/crates/keystore/src/lib.rs +++ b/crates/keystore/src/lib.rs @@ -5,7 +5,7 @@ use mongodb::{bson::oid::ObjectId, Collection}; use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tokio::{sync::Mutex, runtime::Runtime}; +use tokio::sync::Mutex; static SECRETS_COLLECTION: OnceCell> = OnceCell::new(); @@ -42,25 +42,26 @@ where impl KeyStore { /// Create a new keystore with default Secrets type. - /// + /// /// Calling this method many times will return the same keystore instance. pub fn new() -> KeyStore { - let collection = SECRETS_COLLECTION.get_or_init(|| { - // Initialize runtime to run the async MongoDB initialization - let rt = Runtime::new().expect("Failed to create tokio runtime"); - let db = database::get_or_init_database(); - - rt.block_on(async { - let db_lock = db.lock().await; - db_lock.collection::("secrets").clone() + let collection = SECRETS_COLLECTION + .get_or_init(|| { + let db = database::get_or_init_database(); + let task = async move { + let db_lock = db.lock().await; + db_lock.collection::("secrets").clone() + }; + let collection = tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(task)); + collection }) - }).clone(); + .clone(); KeyStore { collection } } /// Retrieve the keystore instance. - /// + /// /// If there is no keystore instance, a new one will be created only once. pub fn get() -> KeyStore { Self::new() diff --git a/crates/plugins/Cargo.toml b/crates/plugins/Cargo.toml index 9b88e4ff..c800e9dd 100644 --- a/crates/plugins/Cargo.toml +++ b/crates/plugins/Cargo.toml @@ -4,9 +4,29 @@ version = "0.1.0" edition = "2021" [dependencies] +database.workspace = true +did-endpoint.workspace = true +keystore.workspace = true shared.workspace = true +plugin-api.workspace = true +filesystem.workspace = true +forward.workspace = true +pickup.workspace = true +mediator-coordination.workspace = true +mongodb.workspace = true didcomm.workspace = true +tracing.workspace = true serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } hyper = { workspace = true, features = ["full"] } axum = { workspace = true, features = ["macros"] } + +[dev-dependencies] +json-canon = "0.1.3" +shared = { workspace = true, features = ["test-utils"] } +tokio = { version = "1.27.0", default-features = false, features = [ + "macros", + "rt", +] } +tower = { version = "0.4.13", features = ["util"] } diff --git a/crates/plugins/did-endpoint/src/didgen.rs b/crates/plugins/did-endpoint/src/didgen.rs index de116b8a..113c410c 100644 --- a/crates/plugins/did-endpoint/src/didgen.rs +++ b/crates/plugins/did-endpoint/src/didgen.rs @@ -1,7 +1,7 @@ use database::Repository; use did_utils::{ crypto::{Ed25519KeyPair, Generate, PublicKeyFormat, ToMultikey, X25519KeyPair}, - didcore::{Authentication, Document, KeyAgreement, KeyFormat, Service}, + didcore::{AssertionMethod, Authentication, Document, KeyAgreement, KeyFormat, Service}, jwk::Jwk, methods::{DidPeer, Purpose, PurposedKey}, }; @@ -43,6 +43,10 @@ where purpose: Purpose::Verification, public_key_multibase: auth_keys.to_multikey(), }, + PurposedKey { + purpose: Purpose::Assertion, + public_key_multibase: auth_keys.to_multikey(), + }, ]; // Build services @@ -83,14 +87,16 @@ where // Create a new KeyStore let keystore = KeyStore::new(); - // Store the agreement key in the screts store - tokio::runtime::Runtime::new().unwrap().block_on(async { - match keystore.store(agreem_keys_secret).await { - Ok(_) => { - tracing::info!("Successfully stored agreement key.") + // Store the agreement key in the secrets store + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + match keystore.store(agreem_keys_secret).await { + Ok(_) => { + tracing::info!("Successfully stored agreement key.") + } + Err(error) => tracing::error!("Error storing agreement key: {:?}", error), } - Err(error) => tracing::error!("Error storing agreement key: {:?}", error), - } + }) }); let auth_keys_jwk: Jwk = auth_keys.try_into().expect("MediateRequestError"); @@ -108,17 +114,47 @@ where Authentication::Reference(kid) => kid, _ => unreachable!(), }, + secret_material: auth_keys_jwk.clone(), + }; + + // Store the authentication key in the secrets store + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + match keystore.store(auth_keys_secret).await { + Ok(_) => { + tracing::info!("Successfully stored authentication key.") + } + Err(error) => tracing::error!("Error storing authentication key: {:?}", error), + } + }) + }); + + let assert_keys_secret = Secrets { + id: None, + kid: match diddoc + .assertion_method + .as_ref() + .unwrap() + .get(0) + .unwrap() + .clone() + { + AssertionMethod::Reference(kid) => kid, + _ => unreachable!(), + }, secret_material: auth_keys_jwk, }; - // Store the authentication key in the screts store - tokio::runtime::Runtime::new().unwrap().block_on(async { - match keystore.store(auth_keys_secret).await { - Ok(_) => { - tracing::info!("Successfully stored authentication key.") + // Store the assertion key in the secrets store + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + match keystore.store(assert_keys_secret).await { + Ok(_) => { + tracing::info!("Successfully stored assertion key.") + } + Err(error) => tracing::error!("Error storing assertion key: {:?}", error), } - Err(error) => tracing::error!("Error storing authentication key: {:?}", error), - } + }) }); // Serialize and persist to file @@ -159,12 +195,12 @@ where _ => return Err(String::from("Unsupported key format")), }; - // Create a new KeyStore let keystore = KeyStore::get(); - let secret = tokio::runtime::Runtime::new() - .unwrap() - .block_on(async { keystore.find_one_by(doc! { "kid": method.id }).await }); + let secret = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current() + .block_on(async { keystore.find_one_by(doc! { "kid": method.id }).await }) + }); secret .map_err(|_| String::from("Error fetching secret"))? @@ -195,18 +231,20 @@ mod tests { // Verifies that the didgen function returns a DID document. // Does not validate the DID document. - #[test] - fn test_didgen() { + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_didgen() { + dotenv_flow::from_filename("../../../.env").ok(); let (storage_dirpath, server_public_domain) = setup(); - let diddoc = didgen(&storage_dirpath, &server_public_domain).unwrap(); - assert_eq!(diddoc.id, "did:web:example.com"); + let diddoc = didgen(&storage_dirpath, &server_public_domain); + assert!(diddoc.is_ok()); cleanup(&storage_dirpath); } - #[test] - fn test_validate_diddoc() { + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_validate_diddoc() { + dotenv_flow::from_filename("../../../.env").ok(); let (storage_dirpath, server_public_domain) = setup(); didgen(&storage_dirpath, &server_public_domain).unwrap(); diff --git a/crates/plugins/did-endpoint/src/util.rs b/crates/plugins/did-endpoint/src/util.rs index d3512c3c..18100b14 100644 --- a/crates/plugins/did-endpoint/src/util.rs +++ b/crates/plugins/did-endpoint/src/util.rs @@ -1,6 +1,6 @@ #[cfg(test)] pub(crate) fn dotenv_flow_read(key: &str) -> Option { - dotenv_flow::from_filename_iter(".env.example") + dotenv_flow::from_filename_iter("../../../.env") .unwrap() .find_map(|item| { let (k, v) = item.unwrap(); diff --git a/crates/plugins/mediator-coordination/Cargo.toml b/crates/plugins/mediator-coordination/Cargo.toml index 7f9e99e2..015b143a 100644 --- a/crates/plugins/mediator-coordination/Cargo.toml +++ b/crates/plugins/mediator-coordination/Cargo.toml @@ -10,6 +10,7 @@ did-endpoint.workspace = true plugin-api.workspace = true database.workspace = true filesystem.workspace = true +keystore.workspace = true chrono.workspace = true mongodb.workspace = true @@ -31,10 +32,15 @@ lazy_static.workspace = true dotenv-flow = "0.15.0" hyper = "0.14.27" json-canon = "0.1.3" -tokio = { version = "1.27.0", default-features = false, features = ["macros", "rt"] } +tokio = { version = "1.27.0", default-features = false, features = [ + "macros", + "rt", +] } tokio-test = "0.4.2" tower = { version = "0.4.13", features = ["util"] } +shared = { workspace = true, features = ["test-utils"] } [features] default = ["stateful"] stateful = [] +stateless = [] diff --git a/crates/plugins/mediator-coordination/src/client/dic.rs b/crates/plugins/mediator-coordination/src/client/dic.rs index 9c31f38f..768429b5 100644 --- a/crates/plugins/mediator-coordination/src/client/dic.rs +++ b/crates/plugins/mediator-coordination/src/client/dic.rs @@ -67,24 +67,17 @@ mod tests { use did_utils::crypto::ToPublic; use multibase::Base::Base64Url; use serde_json::Value; - use shared::utils; - use filesystem::MockFileSystem; fn setup() -> Jwk { - let mut mock_fs = MockFileSystem; - - let diddoc = utils::read_diddoc(&mock_fs, "").unwrap(); - let (_, pubkey) = utils::extract_assertion_key(&diddoc).unwrap(); - - let secret: Jwk = serde_json::from_str( + serde_json::from_str( r#"{ "kty": "OKP", - "crv": "X25519", - "x": "SHSUZ6V3x355FqCzIUfgoPzrZB0BQs0JKyag4UfMqHQ", - "d": "0A8SSFkGHg3N9gmVDRnl63ih5fcwtEvnQu9912SVplY" + "crv": "Ed25519", + "x": "Z0GqpN71rMcnAkky6_J6Bfknr8B-TBsekG3qdI0EQX4", + "d": "fI1u4riKKd99eox08GlThknq-vEJXcKBI28aiUqArLo" }"#, ) - .unwrap(); + .unwrap() } #[test] diff --git a/crates/plugins/mediator-coordination/src/jose/jws.rs b/crates/plugins/mediator-coordination/src/jose/jws.rs index 8d354f8f..6e694302 100644 --- a/crates/plugins/mediator-coordination/src/jose/jws.rs +++ b/crates/plugins/mediator-coordination/src/jose/jws.rs @@ -182,16 +182,17 @@ mod tests { use did_utils::{crypto::ToPublic, jwk::Secret}; use multibase::Base::Base64Url; use serde_json::json; - use shared::utils::{self, MockFileSystem}; fn setup() -> Jwk { - let mut mock_fs = MockFileSystem; - - let diddoc = utils::read_diddoc(&mock_fs, "").unwrap(); - let (_, pubkey) = utils::extract_assertion_key(&diddoc).unwrap(); - - let keystore = utils::read_keystore(&mut mock_fs, "").unwrap(); - keystore.find_keypair(&pubkey).unwrap() + serde_json::from_str( + r#"{ + "kty": "OKP", + "crv": "Ed25519", + "x": "Z0GqpN71rMcnAkky6_J6Bfknr8B-TBsekG3qdI0EQX4", + "d": "fI1u4riKKd99eox08GlThknq-vEJXcKBI28aiUqArLo" + }"#, + ) + .unwrap() } #[test] diff --git a/crates/plugins/mediator-coordination/src/lib.rs b/crates/plugins/mediator-coordination/src/lib.rs index 157bf1da..49cc1258 100644 --- a/crates/plugins/mediator-coordination/src/lib.rs +++ b/crates/plugins/mediator-coordination/src/lib.rs @@ -1,6 +1,5 @@ pub mod client; -pub mod plugin; +pub mod web; mod jose; mod model; -mod web; diff --git a/crates/plugins/mediator-coordination/src/model/coord.rs b/crates/plugins/mediator-coordination/src/model/coord.rs index 3f23a323..a2a789da 100644 --- a/crates/plugins/mediator-coordination/src/model/coord.rs +++ b/crates/plugins/mediator-coordination/src/model/coord.rs @@ -1,6 +1,6 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize}; -use crate::constant::MEDIATE_REQUEST_2_0; +use shared::constants::MEDIATE_REQUEST_2_0; #[cfg(feature = "stateless")] use super::stateless::coord::MediationRequest as StatelessMediationRequest; diff --git a/crates/plugins/mediator-coordination/src/model/stateful/coord.rs b/crates/plugins/mediator-coordination/src/model/stateful/coord.rs index b6eca92a..1087cb9c 100644 --- a/crates/plugins/mediator-coordination/src/model/stateful/coord.rs +++ b/crates/plugins/mediator-coordination/src/model/stateful/coord.rs @@ -305,7 +305,7 @@ mod tests { use serde_json::{json, Value}; - use crate::constant::*; + use shared::constants::*; #[test] fn can_serde_return_route_header_enum() { diff --git a/crates/plugins/mediator-coordination/src/web/handler/midlw.rs b/crates/plugins/mediator-coordination/src/web/handler/midlw.rs index 83dad24a..a13b48f8 100644 --- a/crates/plugins/mediator-coordination/src/web/handler/midlw.rs +++ b/crates/plugins/mediator-coordination/src/web/handler/midlw.rs @@ -77,7 +77,7 @@ pub fn ensure_mediation_request_type( #[cfg(test)] mod tests { use super::*; - use shared::tests::tests::*; + use shared::utils::tests_utils::tests::*; #[cfg(feature = "stateless")] use crate::model::stateless::coord::{ diff --git a/crates/plugins/mediator-coordination/src/web/handler/stateful.rs b/crates/plugins/mediator-coordination/src/web/handler/stateful.rs index 93358777..78ebdb53 100644 --- a/crates/plugins/mediator-coordination/src/web/handler/stateful.rs +++ b/crates/plugins/mediator-coordination/src/web/handler/stateful.rs @@ -4,18 +4,14 @@ use axum::{ }; use did_utils::{ crypto::{Ed25519KeyPair, Generate, ToMultikey, X25519KeyPair}, - didcore::{Document, Service}, + didcore::Service, jwk::Jwk, methods::{DidPeer, Purpose, PurposedKey}, }; -use didcomm::{ - did::DIDResolver, - secrets::{SecretMaterial, SecretType}, - Message, -}; +use didcomm::{did::DIDResolver, Message}; use mongodb::bson::doc; use serde_json::json; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use uuid::Uuid; use crate::{ @@ -29,10 +25,11 @@ use crate::{ }, }; +use keystore::Secrets; use shared::{ constants::{KEYLIST_2_0, KEYLIST_UPDATE_RESPONSE_2_0, MEDIATE_DENY_2_0, MEDIATE_GRANT_2_0}, errors::MediationError, - repository::entity::{Connection, Secrets}, + repository::entity::Connection, state::{AppState, AppStateRepository}, }; @@ -92,9 +89,7 @@ pub async fn process_mediate_request( let (routing_did, auth_keys, agreem_keys) = generate_did_peer(state.public_domain.to_string()); - let AppStateRepository { - secret_repository, .. - } = state + let AppStateRepository { keystore, .. } = state .repository .as_ref() .expect("missing persistence layer"); @@ -103,7 +98,7 @@ pub async fn process_mediate_request( .did_resolver .resolve(&routing_did) .await - .map(|doc| doc.unwrap_or(Document::default().into())) + .unwrap() .expect("Could not resolve DID"); let agreem_keys_jwk: Jwk = agreem_keys.try_into().expect("MediateRequestError"); @@ -111,13 +106,10 @@ pub async fn process_mediate_request( let agreem_keys_secret = Secrets { id: None, kid: diddoc.key_agreement.get(0).unwrap().clone(), - type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK { - private_key_jwk: json!(agreem_keys_jwk), - }, + secret_material: agreem_keys_jwk, }; - match secret_repository.store(agreem_keys_secret).await { + match keystore.store(agreem_keys_secret).await { Ok(_stored_connection) => { println!("Successfully stored connection.") } @@ -129,13 +121,10 @@ pub async fn process_mediate_request( let auth_keys_secret = Secrets { id: None, kid: diddoc.authentication.get(0).unwrap().clone(), - type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK { - private_key_jwk: json!(auth_keys_jwk), - }, + secret_material: auth_keys_jwk, }; - match secret_repository.store(auth_keys_secret).await { + match keystore.store(auth_keys_secret).await { Ok(_stored_connection) => { println!("Successfully stored connection.") } @@ -203,7 +192,7 @@ fn generate_did_peer(service_endpoint: String) -> (String, Ed25519KeyPair, X2551 let services = vec![Service { id: String::from("#didcomm"), service_type: String::from("DIDCommMessaging"), - service_endpoint: json!({"uri": service_endpoint, "accept": vec!["didcomm/v2"], "routingKeys": vec![]}), + service_endpoint: json!({"uri": service_endpoint, "accept": vec!["didcomm/v2"], "routingKeys": Vec::::new()}), ..Default::default() }]; @@ -436,7 +425,9 @@ mod tests { use super::*; - use shared::{repository::tests::MockConnectionRepository, tests::tests as global}; + use shared::{ + repository::tests::MockConnectionRepository, utils::tests_utils::tests as global, + }; #[allow(clippy::needless_update)] fn setup(initial_connections: Vec) -> Arc { @@ -936,6 +927,7 @@ mod tests { // Generate a did:peer address with a service endpoint let service_endpoint = "http://example.com/didcomm"; let (did, _, _) = generate_did_peer(service_endpoint.to_string()); + let expected_service_endpoint = json!({"uri": service_endpoint, "accept": vec!["didcomm/v2"], "routingKeys": Vec::::new()}); // Expand the generated did:peer address to a DID document let did_method = DidPeer::default(); @@ -948,7 +940,7 @@ mod tests { .unwrap() .first() .map(|s| &s.service_endpoint), - Some(service_endpoint.to_string()).as_ref() + Some(&expected_service_endpoint) ); } } diff --git a/crates/plugins/oob-messages/src/lib.rs b/crates/plugins/oob-messages/src/lib.rs index 56750356..4b0cd10b 100644 --- a/crates/plugins/oob-messages/src/lib.rs +++ b/crates/plugins/oob-messages/src/lib.rs @@ -1,5 +1,6 @@ mod constants; mod models; -pub mod plugin; mod util; mod web; + +pub mod plugin; diff --git a/crates/plugins/shared/Cargo.toml b/crates/plugins/shared/Cargo.toml index e2ab8093..f0786a72 100644 --- a/crates/plugins/shared/Cargo.toml +++ b/crates/plugins/shared/Cargo.toml @@ -7,13 +7,16 @@ edition = "2021" keystore.workspace = true did-utils.workspace = true database.workspace = true +plugin-api.workspace = true +filesystem = { workspace = true, features = ["test-utils"] } thiserror.workspace = true serde.workspace = true -filesystem.workspace = true serde_json.workspace = true async-trait.workspace = true mongodb.workspace = true +once_cell.workspace = true +tracing.workspace = true tokio = { workspace = true, features = ["full"] } axum = { workspace = true, features = ["macros"] } didcomm = { workspace = true, features = ["uniffi"] } @@ -24,7 +27,6 @@ tokio = { version = "1.27.0", default-features = false, features = [ "rt", ] } json-canon = "0.1.3" -filesystem = { workspace = true, features = ["test-utils"] } [features] test-utils = [] diff --git a/crates/plugins/shared/src/utils/filesystem.rs b/crates/plugins/shared/src/utils/filesystem.rs new file mode 100644 index 00000000..60464155 --- /dev/null +++ b/crates/plugins/shared/src/utils/filesystem.rs @@ -0,0 +1,154 @@ +use nix::fcntl::{flock, FlockArg}; +use std::{ + fs::OpenOptions, + io::{Error as IoError, ErrorKind, Result as IoResult}, + os::unix::io::AsRawFd, +}; + +#[doc(hidden)] +// Define a trait for file system operations +pub trait FileSystem: Send + 'static { + fn read_to_string(&self, path: &str) -> IoResult; + fn write(&mut self, path: &str, content: &str) -> IoResult<()>; + fn read_dir_files(&self, path: &str) -> IoResult>; + fn create_dir_all(&mut self, path: &str) -> IoResult<()>; + fn write_with_lock(&self, path: &str, content: &str) -> IoResult<()>; + // Add other file system operations as needed +} + +// Implement the trait for the actual file system +#[derive(Clone, Copy, Default)] +pub struct StdFileSystem; + +impl FileSystem for StdFileSystem { + fn read_to_string(&self, path: &str) -> IoResult { + std::fs::read_to_string(path) + } + + fn write(&mut self, path: &str, content: &str) -> IoResult<()> { + std::fs::write(path, content) + } + + fn read_dir_files(&self, path: &str) -> IoResult> { + let mut files = vec![]; + for entry in std::fs::read_dir(path)? { + let path = entry?.path(); + if path.is_file() { + files.push( + path.to_str() + .ok_or(IoError::new(ErrorKind::Other, "InvalidPath"))? + .to_string(), + ) + } + } + + Ok(files) + } + + fn create_dir_all(&mut self, path: &str) -> IoResult<()> { + std::fs::create_dir_all(path) + } + + fn write_with_lock(&self, path: &str, content: &str) -> IoResult<()> { + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?; + + // Acquire an exclusive lock before writing to the file + flock(file.as_raw_fd(), FlockArg::LockExclusive) + .map_err(|_| IoError::new(ErrorKind::Other, "Error acquiring file lock"))?; + + std::fs::write(&path, &content).expect("Error saving base64-encoded image to file"); + + // Release the lock after writing to the file + flock(file.as_raw_fd(), FlockArg::Unlock).expect("Error releasing file lock"); + Ok(()) + } + + // Implement other file system operations as needed +} + +#[cfg(any(test, feature = "test-utils"))] +#[derive(Default)] +pub struct MockFileSystem; + +#[cfg(any(test, feature = "test-utils"))] +impl FileSystem for MockFileSystem { + fn read_to_string(&self, path: &str) -> IoResult { + match path { + p if p.ends_with("did.json") => { + Ok(include_str!("../../test/storage/did.json").to_string()) + } + p if p.contains("keystore") => { + Ok(include_str!("../../test/storage/keystore/1697624245.json").to_string()) + } + _ => Err(IoError::new(ErrorKind::NotFound, "NotFound")), + } + } + + fn write(&mut self, _path: &str, _content: &str) -> IoResult<()> { + Ok(()) + } + + fn read_dir_files(&self, _path: &str) -> IoResult> { + Ok(vec!["/keystore/1697624245.json".to_string()]) + } + + fn create_dir_all(&mut self, _path: &str) -> IoResult<()> { + Ok(()) + } + + fn write_with_lock(&self, _path: &str, _content: &str) -> IoResult<()> { + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + + // Now, for testing, create a mock implementation of the trait + #[derive(Default)] + struct MockFileSystem { + map: HashMap, + } + + impl FileSystem for MockFileSystem { + fn read_to_string(&self, path: &str) -> IoResult { + Ok(self.map.get(path).cloned().unwrap_or_default()) + } + + fn write(&mut self, path: &str, content: &str) -> IoResult<()> { + self.map.insert(path.to_string(), content.to_string()); + Ok(()) + } + + fn read_dir_files(&self, _path: &str) -> IoResult> { + Ok(vec![]) + } + + fn create_dir_all(&mut self, _path: &str) -> IoResult<()> { + Ok(()) + } + + fn write_with_lock(&self, _path: &str, _content: &str) -> IoResult<()> { + Ok(()) + } + } + + #[test] + fn can_mock_fs_operations() { + let mut mock_fs = MockFileSystem::default(); + + let res = mock_fs.write("/file.txt", "2456535e-a316-4d9e-8ab4-74a33d75d1fa"); + assert!(res.is_ok()); + + let content = mock_fs.read_to_string("/file.txt").unwrap(); + assert_eq!(&content, "2456535e-a316-4d9e-8ab4-74a33d75d1fa"); + } +} diff --git a/crates/plugins/shared/src/utils/plugins_utils.rs b/crates/plugins/shared/src/utils/plugins_utils.rs index e69de29b..904179aa 100644 --- a/crates/plugins/shared/src/utils/plugins_utils.rs +++ b/crates/plugins/shared/src/utils/plugins_utils.rs @@ -0,0 +1,33 @@ +use once_cell::sync::OnceCell; +use std::sync::Arc; + +static GLOBAL_PLUGIN_UTILS: OnceCell> = OnceCell::new(); + +#[derive(Clone)] +pub struct PluginsUtils { + pub storage_dirpath: String, + pub server_public_domain: String, + pub server_local_port: String, +} + +/// Initialize common plugins utils that will be shared by all plugins +/// +/// This function will be executed only once. All subsequent calls will return the initilized value. +pub fn initialize_plugins_utils() -> Arc { + GLOBAL_PLUGIN_UTILS + .get_or_init(|| { + let storage_dirpath = + std::env::var("STORAGE_DIRPATH").expect("STORAGE_DIRPATH env variable required"); + let server_public_domain = std::env::var("SERVER_PUBLIC_DOMAIN") + .expect("SERVER_PUBLIC_DOMAIN env variable required"); + let server_local_port = std::env::var("SERVER_LOCAL_PORT") + .expect("SERVER_LOCAL_PORT env variable required"); + + Arc::new(PluginsUtils { + storage_dirpath, + server_public_domain, + server_local_port, + }) + }) + .clone() +} diff --git a/crates/plugins/src/lib.rs b/crates/plugins/src/lib.rs index b005b14a..7f90a005 100644 --- a/crates/plugins/src/lib.rs +++ b/crates/plugins/src/lib.rs @@ -1,2 +1,3 @@ mod midlw; mod web; +pub mod plugin; diff --git a/crates/plugins/src/midlw.rs b/crates/plugins/src/midlw.rs index bd7bd025..05cedbd4 100644 --- a/crates/plugins/src/midlw.rs +++ b/crates/plugins/src/midlw.rs @@ -12,7 +12,9 @@ use std::sync::Arc; // use super::{error::MediationError, AppState}; use shared::{ constants::{DIDCOMM_ENCRYPTED_MIME_TYPE, DIDCOMM_ENCRYPTED_SHORT_MIME_TYPE}, - resolvers::{LocalDIDResolver, LocalSecretsResolver}, + errors::MediationError, + state::AppState, + utils::resolvers::{LocalDIDResolver, LocalSecretsResolver}, }; /// Middleware to unpack DIDComm messages for unified handler @@ -170,13 +172,13 @@ pub async fn pack_response_message( #[cfg(test)] mod tests { use super::*; - use crate::web::handler::tests::*; + use shared::utils::tests_utils::tests::*; use serde_json::json; #[tokio::test] async fn test_pack_response_message_works() { - let (_, state) = setup(); + let state = setup(); let msg = Message::build( "urn:uuid:8f8208ae-6e16-4275-bde8-7b7cb81ffa59".to_owned(), @@ -198,7 +200,7 @@ mod tests { #[tokio::test] async fn test_pack_response_message_fails_on_any_end_missing() { - let (_, state) = setup(); + let state = setup(); macro_rules! unfinalized_msg { () => { @@ -232,7 +234,7 @@ mod tests { #[tokio::test] async fn test_pack_response_message_on_unsupported_receiving_did() { - let (_, state) = setup(); + let state = setup(); let msg = Message::build( "urn:uuid:8f8208ae-6e16-4275-bde8-7b7cb81ffa59".to_owned(), diff --git a/crates/plugins/src/plugin.rs b/crates/plugins/src/plugin.rs index 80622e23..4505f1bd 100644 --- a/crates/plugins/src/plugin.rs +++ b/crates/plugins/src/plugin.rs @@ -1,18 +1,17 @@ +use crate::web; use axum::Router; -use keystore::filesystem::StdFileSystem; -use mongodb::{options::ClientOptions, Client, Database}; +use filesystem::StdFileSystem; +use mongodb::Database; use plugin_api::{Plugin, PluginError}; -use std::sync::Arc; - use shared::{ - repository::{ - MongoConnectionRepository, MongoMessagesRepository, MongoSecretsRepository, - }, + repository::{MongoConnectionRepository, MongoMessagesRepository}, + state::{AppState, AppStateRepository}, utils, - state::{self, AppState, AppStateRepository}, }; +use std::sync::Arc; + #[derive(Default)] -pub struct MediatorCoordinationPlugin { +pub struct MediatorCoordination { env: Option, db: Option, } @@ -20,8 +19,6 @@ pub struct MediatorCoordinationPlugin { struct MediatorCoordinationPluginEnv { public_domain: String, storage_dirpath: String, - mongo_uri: String, - mongo_dbn: String, } /// Loads environment variables required for this plugin @@ -36,25 +33,13 @@ fn load_plugin_env() -> Result { PluginError::InitError })?; - let mongo_uri = std::env::var("MONGO_URI").map_err(|_| { - tracing::error!("MONGO_URI env variable required"); - PluginError::InitError - })?; - - let mongo_dbn = std::env::var("MONGO_DBN").map_err(|_| { - tracing::error!("MONGO_DBN env variable required"); - PluginError::InitError - })?; - Ok(MediatorCoordinationPluginEnv { public_domain, storage_dirpath, - mongo_uri, - mongo_dbn, }) } -impl Plugin for MediatorCoordinationPlugin { +impl Plugin for MediatorCoordination { fn name(&self) -> &'static str { "mediator_coordination" } @@ -69,7 +54,14 @@ impl Plugin for MediatorCoordinationPlugin { } // Check connectivity to database - let db = load_mongo_connector(&env.mongo_uri, &env.mongo_dbn)?; + let db = tokio::task::block_in_place(|| { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async { + let db_instance = database::get_or_init_database(); + let db_lock = db_instance.lock().await; + db_lock.clone() + }) + }); // Save the environment and MongoDB connection in the struct self.env = Some(env); @@ -90,45 +82,20 @@ impl Plugin for MediatorCoordinationPlugin { let msg = "This should not occur following successful mounting."; // Load crypto identity - let mut fs = StdFileSystem; + let fs = StdFileSystem; let diddoc = utils::read_diddoc(&fs, &env.storage_dirpath).expect(msg); // Load persistence layer let repository = AppStateRepository { connection_repository: Arc::new(MongoConnectionRepository::from_db(&db)), - secret_repository: Arc::new(MongoSecretsRepository::from_db(&db)), + keystore: Arc::new(keystore::KeyStore::get()), message_repository: Arc::new(MongoMessagesRepository::from_db(&db)), }; // Compile state - let state = AppState::from( - env.public_domain.clone(), - diddoc, - Some(repository), - ); + let state = AppState::from(env.public_domain.clone(), diddoc, Some(repository)); // Build router web::routes(Arc::new(state)) } } - -fn load_mongo_connector(mongo_uri: &str, mongo_dbn: &str) -> Result { - let task = async { - // Parse a connection string into an options struct. - let client_options = ClientOptions::parse(mongo_uri).await.map_err(|_| { - tracing::error!("Failed to parse Mongo URI"); - PluginError::InitError - })?; - - // Get a handle to the deployment. - let client = Client::with_options(client_options).map_err(|_| { - tracing::error!("Failed to create MongoDB client"); - PluginError::InitError - })?; - - // Get a handle to a database. - Ok(client.database(mongo_dbn)) - }; - - tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(task)) -} diff --git a/crates/plugins/src/web.rs b/crates/plugins/src/web.rs index e9f1d330..e5132dfc 100644 --- a/crates/plugins/src/web.rs +++ b/crates/plugins/src/web.rs @@ -1,6 +1,7 @@ pub(crate) mod dispatcher; use axum::{middleware, routing::post, Router}; +use shared::state::AppState; use std::sync::Arc; use crate::midlw; diff --git a/crates/plugins/src/web/dispatcher.rs b/crates/plugins/src/web/dispatcher.rs index 33aa4870..965c70da 100644 --- a/crates/plugins/src/web/dispatcher.rs +++ b/crates/plugins/src/web/dispatcher.rs @@ -4,17 +4,14 @@ use axum::{ Extension, Json, }; use didcomm::Message; +use forward::web::handler::mediator_forward_process; use hyper::{header::CONTENT_TYPE, StatusCode}; +use shared::{constants::{ + DIDCOMM_ENCRYPTED_MIME_TYPE, KEYLIST_QUERY_2_0, KEYLIST_UPDATE_2_0, MEDIATE_FORWARD_2_0, + MEDIATE_REQUEST_2_0, +}, errors::MediationError, state::AppState}; use std::sync::Arc; - -use crate::{ - constant::{ - DIDCOMM_ENCRYPTED_MIME_TYPE, KEYLIST_QUERY_2_0, KEYLIST_UPDATE_2_0, MEDIATE_FORWARD_2_0, - MEDIATE_REQUEST_2_0, - }, - forward::routing::mediator_forward_process, - web::{self, error::MediationError, AppState}, -}; +use mediator_coordination::web; #[axum::debug_handler] pub(crate) async fn process_didcomm_message( @@ -29,14 +26,14 @@ pub(crate) async fn process_didcomm_message( } let response = match message.type_.as_str() { KEYLIST_UPDATE_2_0 => { - web::coord::handler::stateful::process_plain_keylist_update_message( + web::handler::stateful::process_plain_keylist_update_message( Arc::clone(&state), message, ) .await } KEYLIST_QUERY_2_0 => { - web::coord::handler::stateful::process_plain_keylist_query_message( + web::handler::stateful::process_plain_keylist_query_message( Arc::clone(&state), message, ) @@ -44,7 +41,7 @@ pub(crate) async fn process_didcomm_message( } MEDIATE_REQUEST_2_0 => { - web::coord::handler::stateful::process_mediate_request(&state, &message).await + web::handler::stateful::process_mediate_request(&state, &message).await } _ => { @@ -61,7 +58,7 @@ pub(crate) async fn process_didcomm_message( async fn process_response(state: Arc, response: Result) -> Response { match response { - Ok(message) => web::midlw::pack_response_message( + Ok(message) => crate::midlw::pack_response_message( &message, &state.did_resolver, &state.secrets_resolver, @@ -80,23 +77,23 @@ async fn process_response(state: Arc, response: Result (Router, Arc) { - let (_, state) = global::setup(); + let state = global::setup(); let mut state = match Arc::try_unwrap(state) { Ok(state) => state, @@ -126,7 +123,7 @@ mod tests { }); let state = Arc::new(state); - let app = web::routes(Arc::clone(&state)); + let app = crate::web::routes(Arc::clone(&state)); (app, state) } diff --git a/src/plugins.rs b/src/plugins.rs index 3663bae4..296807e4 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -17,5 +17,7 @@ lazy_static! { Arc::new(Mutex::new(did_endpoint::plugin::DidEndpoint {})), #[cfg(feature = "plugin-oob_messages")] Arc::new(Mutex::new(oob_messages::plugin::OOBMessages {})), + #[cfg(feature = "plugin-plugins")] + Arc::new(Mutex::new(plugins::plugin::MediatorCoordination::default())), ]; }