diff --git a/Cargo.lock b/Cargo.lock index 266327e..1ae21aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2549,6 +2549,7 @@ dependencies = [ "lldap_frontend_options", "lldap_ldap", "lldap_opaque_handler", + "lldap_sql_backend_handler", "lldap_test_utils", "lldap_validation", "log", @@ -2778,6 +2779,39 @@ dependencies = [ "serde_json", ] +[[package]] +name = "lldap_sql_backend_handler" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "bincode", + "chrono", + "itertools", + "ldap3_proto", + "lldap_access_control", + "lldap_auth", + "lldap_domain", + "lldap_domain_handlers", + "lldap_domain_model", + "lldap_opaque_handler", + "lldap_test_utils", + "log", + "mockall", + "orion", + "pretty_assertions", + "rand 0.8.5", + "sea-orm", + "secstr", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "uuid 1.11.0", +] + [[package]] name = "lldap_test_utils" version = "0.1.0" diff --git a/crates/auth/src/opaque.rs b/crates/auth/src/opaque.rs index 84233fa..d805475 100644 --- a/crates/auth/src/opaque.rs +++ b/crates/auth/src/opaque.rs @@ -132,6 +132,12 @@ pub mod server { pub use super::*; pub type ServerRegistration = opaque_ke::ServerRegistration; pub type ServerSetup = opaque_ke::ServerSetup; + + pub fn generate_random_private_key() -> ServerSetup { + let mut rng = rand::rngs::OsRng; + ServerSetup::new(&mut rng) + } + /// Methods to register a new user, from the server side. pub mod registration { pub use super::*; diff --git a/crates/ldap/src/compare.rs b/crates/ldap/src/compare.rs index 8aa3211..361d5e7 100644 --- a/crates/ldap/src/compare.rs +++ b/crates/ldap/src/compare.rs @@ -61,7 +61,6 @@ mod tests { use lldap_domain_handlers::handler::{GroupRequestFilter, UserRequestFilter}; use lldap_test_utils::MockTestBackendHandler; use pretty_assertions::assert_eq; - #[tokio::test] async fn test_compare_user() { diff --git a/crates/ldap/src/create.rs b/crates/ldap/src/create.rs index 7ce225e..b7d0ef4 100644 --- a/crates/ldap/src/create.rs +++ b/crates/ldap/src/create.rs @@ -172,7 +172,6 @@ mod tests { use lldap_test_utils::MockTestBackendHandler; use mockall::predicate::eq; use pretty_assertions::assert_eq; - #[tokio::test] async fn test_create_user() { diff --git a/crates/ldap/src/delete.rs b/crates/ldap/src/delete.rs index 912b7db..c5cd02f 100644 --- a/crates/ldap/src/delete.rs +++ b/crates/ldap/src/delete.rs @@ -115,7 +115,6 @@ mod tests { use lldap_test_utils::MockTestBackendHandler; use mockall::predicate::eq; use pretty_assertions::assert_eq; - #[tokio::test] async fn test_delete_user() { diff --git a/crates/ldap/src/modify.rs b/crates/ldap/src/modify.rs index 1a88fcf..08ec987 100644 --- a/crates/ldap/src/modify.rs +++ b/crates/ldap/src/modify.rs @@ -140,7 +140,6 @@ mod tests { use mockall::predicate::eq; use pretty_assertions::assert_eq; use std::collections::HashSet; - fn setup_target_user_groups( mock: &mut MockTestBackendHandler, diff --git a/crates/ldap/src/search.rs b/crates/ldap/src/search.rs index ffa1f6a..956f07f 100644 --- a/crates/ldap/src/search.rs +++ b/crates/ldap/src/search.rs @@ -333,7 +333,6 @@ mod tests { use lldap_test_utils::MockTestBackendHandler; use mockall::predicate::eq; use pretty_assertions::assert_eq; - #[tokio::test] async fn test_search_root_dse() { diff --git a/crates/sql-backend-handler/Cargo.toml b/crates/sql-backend-handler/Cargo.toml new file mode 100644 index 0000000..c9558bc --- /dev/null +++ b/crates/sql-backend-handler/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "lldap_sql_backend_handler" +version = "0.1.0" +description = "SQL backend for LLDAP" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true + +[features] +test = [] + +[dependencies] +anyhow = "*" +async-trait = "0.1" +base64 = "0.21" +bincode = "1.3" +itertools = "0.10" +ldap3_proto = "0.6.0" +orion = "0.17" +serde_json = "1" +tracing = "*" + +[dependencies.chrono] +features = ["serde"] +version = "*" + +[dependencies.rand] +features = ["small_rng", "getrandom"] +version = "0.8" + +[dependencies.sea-orm] +workspace = true +features = [ + "macros", + "with-chrono", + "with-uuid", + "sqlx-all", + "runtime-actix-rustls", +] + +[dependencies.secstr] +features = ["serde"] +version = "*" + +[dependencies.serde] +workspace = true + +[dependencies.uuid] +version = "1" +features = ["v1", "v3"] + +[dependencies.lldap_access_control] +path = "../access-control" + +[dependencies.lldap_auth] +path = "../auth" +features = ["opaque_server", "opaque_client", "sea_orm"] + +[dependencies.lldap_domain] +path = "../domain" + +[dependencies.lldap_domain_handlers] +path = "../domain-handlers" + +[dependencies.lldap_domain_model] +path = "../domain-model" + +[dependencies.lldap_opaque_handler] +path = "../opaque-handler" + +[dev-dependencies.lldap_test_utils] +path = "../test-utils" + +[dev-dependencies] +log = "*" +mockall = "0.11.4" +pretty_assertions = "1" + +[dev-dependencies.tokio] +features = ["full"] +version = "1.25" + +[dev-dependencies.tracing-subscriber] +version = "0.3" +features = ["env-filter", "tracing-log"] diff --git a/crates/sql-backend-handler/src/lib.rs b/crates/sql-backend-handler/src/lib.rs new file mode 100644 index 0000000..f3d3b64 --- /dev/null +++ b/crates/sql-backend-handler/src/lib.rs @@ -0,0 +1,11 @@ +pub(crate) mod logging; +pub(crate) mod sql_backend_handler; +pub(crate) mod sql_group_backend_handler; +pub(crate) mod sql_opaque_handler; +pub(crate) mod sql_schema_backend_handler; +pub(crate) mod sql_user_backend_handler; + +pub use sql_backend_handler::SqlBackendHandler; +pub use sql_opaque_handler::register_password; +pub mod sql_migrations; +pub mod sql_tables; diff --git a/crates/sql-backend-handler/src/logging.rs b/crates/sql-backend-handler/src/logging.rs new file mode 100644 index 0000000..c0cae69 --- /dev/null +++ b/crates/sql-backend-handler/src/logging.rs @@ -0,0 +1,10 @@ +#[cfg(test)] +pub fn init_for_tests() { + if let Err(e) = tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::DEBUG) + .with_test_writer() + .try_init() + { + log::warn!("Could not set up test logging: {:#}", e); + } +} diff --git a/server/src/domain/sql_backend_handler.rs b/crates/sql-backend-handler/src/sql_backend_handler.rs similarity index 88% rename from server/src/domain/sql_backend_handler.rs rename to crates/sql-backend-handler/src/sql_backend_handler.rs index 1938e87..8e160f7 100644 --- a/server/src/domain/sql_backend_handler.rs +++ b/crates/sql-backend-handler/src/sql_backend_handler.rs @@ -1,16 +1,24 @@ -use crate::{domain::sql_tables::DbConnection, infra::configuration::Configuration}; +use crate::sql_tables::DbConnection; use async_trait::async_trait; +use lldap_auth::opaque::server::ServerSetup; use lldap_domain_handlers::handler::BackendHandler; #[derive(Clone)] pub struct SqlBackendHandler { - pub(crate) config: Configuration, + pub(crate) opaque_setup: ServerSetup, pub(crate) sql_pool: DbConnection, } impl SqlBackendHandler { - pub fn new(config: Configuration, sql_pool: DbConnection) -> Self { - SqlBackendHandler { config, sql_pool } + pub fn new(opaque_setup: ServerSetup, sql_pool: DbConnection) -> Self { + SqlBackendHandler { + opaque_setup, + sql_pool, + } + } + + pub fn pool(&self) -> &DbConnection { + &self.sql_pool } } @@ -20,8 +28,11 @@ impl BackendHandler for SqlBackendHandler {} #[cfg(test)] pub mod tests { use super::*; - use crate::{domain::sql_tables::init_table, infra::configuration::ConfigurationBuilder}; - use lldap_auth::{opaque, registration}; + use crate::sql_tables::init_table; + use lldap_auth::{ + opaque::{self, server::generate_random_private_key}, + registration, + }; use lldap_domain::{ requests::{CreateGroupRequest, CreateUserRequest}, types::{Attribute as DomainAttribute, GroupId, UserId}, @@ -32,12 +43,8 @@ pub mod tests { use pretty_assertions::assert_eq; use sea_orm::Database; - pub fn get_default_config() -> Configuration { - ConfigurationBuilder::for_tests() - } - pub async fn get_in_memory_db() -> DbConnection { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); let mut sql_opt = sea_orm::ConnectOptions::new("sqlite::memory:".to_owned()); sql_opt .max_connections(1) @@ -139,8 +146,7 @@ pub mod tests { impl TestFixture { pub async fn new() -> Self { let sql_pool = get_initialized_db().await; - let config = get_default_config(); - let handler = SqlBackendHandler::new(config, sql_pool); + let handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool); insert_user_no_password(&handler, "bob").await; insert_user_no_password(&handler, "patrick").await; insert_user_no_password(&handler, "John").await; @@ -160,8 +166,7 @@ pub mod tests { #[tokio::test] async fn test_sql_injection() { let sql_pool = get_initialized_db().await; - let config = get_default_config(); - let handler = SqlBackendHandler::new(config, sql_pool); + let handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool); let user_name = UserId::new(r#"bob"e"i'o;aü"#); insert_user_no_password(&handler, user_name.as_str()).await; { diff --git a/server/src/domain/sql_group_backend_handler.rs b/crates/sql-backend-handler/src/sql_group_backend_handler.rs similarity index 99% rename from server/src/domain/sql_group_backend_handler.rs rename to crates/sql-backend-handler/src/sql_group_backend_handler.rs index 9110e4c..4ec2f6f 100644 --- a/server/src/domain/sql_group_backend_handler.rs +++ b/crates/sql-backend-handler/src/sql_group_backend_handler.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_backend_handler::SqlBackendHandler; +use crate::sql_backend_handler::SqlBackendHandler; use async_trait::async_trait; use lldap_access_control::UserReadableBackendHandler; use lldap_domain::{ @@ -334,7 +334,7 @@ impl SqlBackendHandler { #[cfg(test)] mod tests { use super::*; - use crate::domain::sql_backend_handler::tests::*; + use crate::sql_backend_handler::tests::*; use lldap_domain::{ requests::CreateAttributeRequest, types::{Attribute, AttributeType, GroupName, UserId}, diff --git a/server/src/domain/sql_migrations.rs b/crates/sql-backend-handler/src/sql_migrations.rs similarity index 98% rename from server/src/domain/sql_migrations.rs rename to crates/sql-backend-handler/src/sql_migrations.rs index 5297b7c..eab8e6f 100644 --- a/server/src/domain/sql_migrations.rs +++ b/crates/sql-backend-handler/src/sql_migrations.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_tables::{DbConnection, LAST_SCHEMA_VERSION, SchemaVersion}; +use crate::sql_tables::{DbConnection, LAST_SCHEMA_VERSION, SchemaVersion}; use itertools::Itertools; use lldap_domain::types::{AttributeType, GroupId, JpegPhoto, Serialized, UserId, Uuid}; use sea_orm::{ @@ -30,7 +30,7 @@ pub enum Users { } #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum Groups { +pub(crate) enum Groups { Table, GroupId, DisplayName, @@ -40,7 +40,7 @@ pub enum Groups { } #[derive(DeriveIden, Clone, Copy)] -pub enum Memberships { +pub(crate) enum Memberships { Table, UserId, GroupId, @@ -48,7 +48,7 @@ pub enum Memberships { #[allow(clippy::enum_variant_names)] // The table names are generated from the enum. #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum UserAttributeSchema { +pub(crate) enum UserAttributeSchema { Table, UserAttributeSchemaName, UserAttributeSchemaType, @@ -59,7 +59,7 @@ pub enum UserAttributeSchema { } #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum UserAttributes { +pub(crate) enum UserAttributes { Table, UserAttributeUserId, UserAttributeName, @@ -68,7 +68,7 @@ pub enum UserAttributes { #[allow(clippy::enum_variant_names)] // The table names are generated from the enum. #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum GroupAttributeSchema { +pub(crate) enum GroupAttributeSchema { Table, GroupAttributeSchemaName, GroupAttributeSchemaType, @@ -79,7 +79,7 @@ pub enum GroupAttributeSchema { } #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum GroupAttributes { +pub(crate) enum GroupAttributes { Table, GroupAttributeGroupId, GroupAttributeName, @@ -87,14 +87,14 @@ pub enum GroupAttributes { } #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum UserObjectClasses { +pub(crate) enum UserObjectClasses { Table, LowerObjectClass, ObjectClass, } #[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)] -pub enum GroupObjectClasses { +pub(crate) enum GroupObjectClasses { Table, LowerObjectClass, ObjectClass, @@ -102,7 +102,7 @@ pub enum GroupObjectClasses { // Metadata about the SQL DB. #[derive(DeriveIden)] -pub enum Metadata { +pub(crate) enum Metadata { Table, // Which version of the schema we're at. Version, @@ -111,12 +111,12 @@ pub enum Metadata { } #[derive(FromQueryResult, PartialEq, Eq, Debug)] -pub struct JustSchemaVersion { - pub version: SchemaVersion, +pub(crate) struct JustSchemaVersion { + pub(crate) version: SchemaVersion, } #[instrument(skip_all, level = "debug", ret)] -pub async fn get_schema_version(pool: &DbConnection) -> Option { +pub(crate) async fn get_schema_version(pool: &DbConnection) -> Option { JustSchemaVersion::find_by_statement( pool.get_database_backend().build( Query::select() @@ -131,7 +131,7 @@ pub async fn get_schema_version(pool: &DbConnection) -> Option { .map(|j| j.version) } -pub async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_orm::DbErr> { +pub(crate) async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_orm::DbErr> { let builder = pool.get_database_backend(); // SQLite needs this pragma to be turned on. Other DB might not understand this, so ignore the // error. @@ -1121,7 +1121,7 @@ macro_rules! to_sync { }; } -pub async fn migrate_from_version( +pub(crate) async fn migrate_from_version( pool: &DbConnection, version: SchemaVersion, last_version: SchemaVersion, diff --git a/server/src/domain/sql_opaque_handler.rs b/crates/sql-backend-handler/src/sql_opaque_handler.rs similarity index 90% rename from server/src/domain/sql_opaque_handler.rs rename to crates/sql-backend-handler/src/sql_opaque_handler.rs index 01e38b4..a1c57ba 100644 --- a/server/src/domain/sql_opaque_handler.rs +++ b/crates/sql-backend-handler/src/sql_opaque_handler.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_backend_handler::SqlBackendHandler; +use crate::SqlBackendHandler; use async_trait::async_trait; use base64::Engine; use lldap_auth::opaque; @@ -19,7 +19,7 @@ type SqlOpaqueHandler = SqlBackendHandler; fn passwords_match( password_file_bytes: &[u8], clear_password: &str, - server_setup: &opaque::server::ServerSetup, + opaque_setup: &opaque::server::ServerSetup, username: &UserId, ) -> Result<()> { use opaque::{client, server}; @@ -30,7 +30,7 @@ fn passwords_match( .map_err(opaque::AuthenticationError::ProtocolError)?; let server_login_start_result = server::login::start_login( &mut rng, - server_setup, + opaque_setup, Some(password_file), client_login_start_result.message, username, @@ -45,7 +45,7 @@ fn passwords_match( impl SqlBackendHandler { fn get_orion_secret_key(&self) -> Result { Ok(orion::aead::SecretKey::from_slice( - self.config.get_server_keys().private(), + self.opaque_setup.keypair().private(), )?) } @@ -74,7 +74,7 @@ impl LoginHandler for SqlBackendHandler { if passwords_match( &password_hash, &request.password, - self.config.get_server_setup(), + &self.opaque_setup, &request.name, ) .is_ok() @@ -117,7 +117,7 @@ impl OpaqueHandler for SqlOpaqueHandler { // Get the CredentialResponse for the user, or a dummy one if no user/no password. let start_response = opaque::server::login::start_login( &mut rng, - self.config.get_server_setup(), + &self.opaque_setup, maybe_password_file, request.login_start_request, &user_id, @@ -168,7 +168,7 @@ impl OpaqueHandler for SqlOpaqueHandler { ) -> Result { // Generate the server-side key and derive the data to send back. let start_response = opaque::server::registration::start_registration( - self.config.get_server_setup(), + &self.opaque_setup, request.registration_start_request, &request.username, )?; @@ -210,7 +210,7 @@ impl OpaqueHandler for SqlOpaqueHandler { /// Convenience function to set a user's password. #[instrument(skip_all, level = "debug", err, fields(username = %username.as_str()))] -pub(crate) async fn register_password( +pub async fn register_password( opaque_handler: &SqlOpaqueHandler, username: UserId, password: &SecUtf8, @@ -240,8 +240,12 @@ pub(crate) async fn register_password( #[cfg(test)] mod tests { + use self::opaque::server::generate_random_private_key; + use super::*; - use crate::domain::sql_backend_handler::tests::*; + use crate::sql_backend_handler::tests::{ + get_initialized_db, insert_user, insert_user_no_password, + }; async fn attempt_login( opaque_handler: &SqlOpaqueHandler, @@ -273,33 +277,30 @@ mod tests { #[tokio::test] async fn test_opaque_flow() -> Result<()> { let sql_pool = get_initialized_db().await; - crate::infra::logging::init_for_tests(); - let config = get_default_config(); - let backend_handler = SqlBackendHandler::new(config.clone(), sql_pool.clone()); - let opaque_handler = SqlOpaqueHandler::new(config, sql_pool); + crate::logging::init_for_tests(); + let backend_handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool); insert_user_no_password(&backend_handler, "bob").await; insert_user_no_password(&backend_handler, "john").await; - attempt_login(&opaque_handler, "bob", "bob00") + attempt_login(&backend_handler, "bob", "bob00") .await .unwrap_err(); register_password( - &opaque_handler, + &backend_handler, UserId::new("bob"), &secstr::SecUtf8::from("bob00"), ) .await?; - attempt_login(&opaque_handler, "bob", "wrong_password") + attempt_login(&backend_handler, "bob", "wrong_password") .await .unwrap_err(); - attempt_login(&opaque_handler, "bob", "bob00").await?; + attempt_login(&backend_handler, "bob", "bob00").await?; Ok(()) } #[tokio::test] async fn test_bind_user() { let sql_pool = get_initialized_db().await; - let config = get_default_config(); - let handler = SqlOpaqueHandler::new(config, sql_pool.clone()); + let handler = SqlOpaqueHandler::new(generate_random_private_key(), sql_pool.clone()); insert_user(&handler, "bob", "bob00").await; handler @@ -328,8 +329,7 @@ mod tests { #[tokio::test] async fn test_user_no_password() { let sql_pool = get_initialized_db().await; - let config = get_default_config(); - let handler = SqlBackendHandler::new(config, sql_pool.clone()); + let handler = SqlBackendHandler::new(generate_random_private_key(), sql_pool.clone()); insert_user_no_password(&handler, "bob").await; handler diff --git a/server/src/domain/sql_schema_backend_handler.rs b/crates/sql-backend-handler/src/sql_schema_backend_handler.rs similarity index 99% rename from server/src/domain/sql_schema_backend_handler.rs rename to crates/sql-backend-handler/src/sql_schema_backend_handler.rs index ae6d10a..78d3860 100644 --- a/server/src/domain/sql_schema_backend_handler.rs +++ b/crates/sql-backend-handler/src/sql_schema_backend_handler.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_backend_handler::SqlBackendHandler; +use crate::sql_backend_handler::SqlBackendHandler; use async_trait::async_trait; use lldap_domain::{ requests::CreateAttributeRequest, @@ -175,7 +175,7 @@ impl SqlBackendHandler { #[cfg(test)] mod tests { use super::*; - use crate::domain::sql_backend_handler::tests::*; + use crate::sql_backend_handler::tests::*; use lldap_domain::requests::UpdateUserRequest; use lldap_domain::schema::AttributeList; use lldap_domain::types::{Attribute, AttributeType}; diff --git a/server/src/domain/sql_tables.rs b/crates/sql-backend-handler/src/sql_tables.rs similarity index 97% rename from server/src/domain/sql_tables.rs rename to crates/sql-backend-handler/src/sql_tables.rs index cc527f7..e1c4365 100644 --- a/server/src/domain/sql_tables.rs +++ b/crates/sql-backend-handler/src/sql_tables.rs @@ -1,6 +1,4 @@ -use crate::domain::sql_migrations::{ - Metadata, get_schema_version, migrate_from_version, upgrade_to_v1, -}; +use crate::sql_migrations::{Metadata, get_schema_version, migrate_from_version, upgrade_to_v1}; use sea_orm::{ ConnectionTrait, DeriveValueType, Iden, QueryResult, TryGetable, Value, sea_query::Query, }; @@ -68,7 +66,6 @@ pub enum PrivateKeyLocation { KeySeed(ConfigLocation), KeyFile(ConfigLocation, std::ffi::OsString), Default, - #[cfg(test)] Tests, } @@ -123,7 +120,7 @@ pub async fn set_private_key_info(pool: &DbConnection, info: PrivateKeyInfo) -> #[cfg(test)] mod tests { - use crate::domain::sql_migrations; + use crate::sql_migrations; use lldap_domain::types::{GroupId, JpegPhoto, Serialized, Uuid}; use pretty_assertions::assert_eq; @@ -185,7 +182,7 @@ mod tests { #[tokio::test] async fn test_already_init_table() { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); let sql_pool = get_in_memory_db().await; init_table(&sql_pool).await.unwrap(); init_table(&sql_pool).await.unwrap(); @@ -193,7 +190,7 @@ mod tests { #[tokio::test] async fn test_migrate_tables() { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); // Test that we add the column creation_date to groups and uuid to users and groups. let sql_pool = get_in_memory_db().await; sql_pool @@ -324,7 +321,7 @@ mod tests { #[tokio::test] async fn test_migration_to_v4() { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); let sql_pool = get_in_memory_db().await; upgrade_to_v1(&sql_pool).await.unwrap(); migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(3)) @@ -387,7 +384,7 @@ mod tests { #[tokio::test] async fn test_migration_to_v5() { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); let sql_pool = get_in_memory_db().await; upgrade_to_v1(&sql_pool).await.unwrap(); migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(4)) @@ -473,7 +470,7 @@ mod tests { #[tokio::test] async fn test_migration_to_v6() { - crate::infra::logging::init_for_tests(); + crate::logging::init_for_tests(); let sql_pool = get_in_memory_db().await; upgrade_to_v1(&sql_pool).await.unwrap(); migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(5)) diff --git a/server/src/domain/sql_user_backend_handler.rs b/crates/sql-backend-handler/src/sql_user_backend_handler.rs similarity index 99% rename from server/src/domain/sql_user_backend_handler.rs rename to crates/sql-backend-handler/src/sql_user_backend_handler.rs index c94e97b..dff420f 100644 --- a/server/src/domain/sql_user_backend_handler.rs +++ b/crates/sql-backend-handler/src/sql_user_backend_handler.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_backend_handler::SqlBackendHandler; +use crate::sql_backend_handler::SqlBackendHandler; use async_trait::async_trait; use lldap_domain::{ requests::{CreateUserRequest, UpdateUserRequest}, @@ -414,7 +414,8 @@ impl UserBackendHandler for SqlBackendHandler { #[cfg(test)] mod tests { use super::*; - use crate::domain::sql_backend_handler::tests::*; + use crate::sql_backend_handler::tests::*; + use lldap_auth::opaque::server::generate_random_private_key; use lldap_domain::types::{Attribute, JpegPhoto}; use lldap_domain_handlers::handler::SubStringFilter; use lldap_domain_model::model::UserColumn; @@ -734,7 +735,8 @@ mod tests { #[tokio::test] async fn test_get_user_details() { - let handler = SqlBackendHandler::new(get_default_config(), get_initialized_db().await); + let handler = + SqlBackendHandler::new(generate_random_private_key(), get_initialized_db().await); insert_user_no_password(&handler, "bob").await; { let user = handler.get_user_details(&UserId::new("bob")).await.unwrap(); @@ -750,7 +752,8 @@ mod tests { #[tokio::test] async fn test_user_lowercase() { - let handler = SqlBackendHandler::new(get_default_config(), get_initialized_db().await); + let handler = + SqlBackendHandler::new(generate_random_private_key(), get_initialized_db().await); insert_user_no_password(&handler, "Bob").await; { let user = handler.get_user_details(&UserId::new("bOb")).await.unwrap(); diff --git a/server/Cargo.toml b/server/Cargo.toml index 38e88c0..94beac5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -105,6 +105,9 @@ path = "../crates/frontend-options" [dependencies.lldap_ldap] path = "../crates/ldap" +[dependencies.lldap_sql_backend_handler] +path = "../crates/sql-backend-handler" + [dependencies.lldap_opaque_handler] path = "../crates/opaque-handler" @@ -195,6 +198,10 @@ features = ["test"] [dev-dependencies.lldap_test_utils] path = "../crates/test-utils" +[dev-dependencies.lldap_sql_backend_handler] +path = "../crates/sql-backend-handler" +features = ["test"] + [dev-dependencies.reqwest] version = "*" default-features = false diff --git a/server/src/domain/mod.rs b/server/src/domain/mod.rs deleted file mode 100644 index 9ec6996..0000000 --- a/server/src/domain/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod sql_backend_handler; -pub mod sql_group_backend_handler; -pub mod sql_migrations; -pub mod sql_opaque_handler; -pub mod sql_schema_backend_handler; -pub mod sql_tables; -pub mod sql_user_backend_handler; diff --git a/server/src/infra/configuration.rs b/server/src/infra/configuration.rs index 00e2c67..0c82a82 100644 --- a/server/src/infra/configuration.rs +++ b/server/src/infra/configuration.rs @@ -1,14 +1,9 @@ -use std::collections::HashSet; - -use crate::{ - domain::sql_tables::{ConfigLocation, PrivateKeyHash, PrivateKeyInfo, PrivateKeyLocation}, - infra::{ - cli::{ - GeneralConfigOpts, LdapsOpts, RunOpts, SmtpEncryption, SmtpOpts, TestEmailOpts, - TrueFalseAlways, - }, - database_string::DatabaseUrl, +use crate::infra::{ + cli::{ + GeneralConfigOpts, LdapsOpts, RunOpts, SmtpEncryption, SmtpOpts, TestEmailOpts, + TrueFalseAlways, }, + database_string::DatabaseUrl, }; use anyhow::{Context, Result, bail}; use figment::{ @@ -16,10 +11,17 @@ use figment::{ providers::{Env, Format, Serialized, Toml}, }; use figment_file_provider_adapter::FileAdapter; -use lldap_auth::opaque::{KeyPair, server::ServerSetup}; +use lldap_auth::opaque::{ + KeyPair, + server::{ServerSetup, generate_random_private_key}, +}; use lldap_domain::types::{AttributeName, UserId}; +use lldap_sql_backend_handler::sql_tables::{ + ConfigLocation, PrivateKeyHash, PrivateKeyInfo, PrivateKeyLocation, +}; use secstr::SecUtf8; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::path::PathBuf; use url::Url; @@ -157,18 +159,6 @@ impl ConfigurationBuilder { )?; Ok(self.server_setup(Some(server_setup)).private_build()?) } - - #[cfg(test)] - pub fn for_tests() -> Configuration { - ConfigurationBuilder::default() - .verbose(true) - .server_setup(Some(ServerSetupConfig { - server_setup: generate_random_private_key(), - private_key_location: PrivateKeyLocation::Tests, - })) - .private_build() - .unwrap() - } } fn stable_hash(val: &[u8]) -> [u8; 32] { @@ -242,6 +232,9 @@ pub fn compare_private_key_hashes( ); } } + (PrivateKeyLocation::Tests, _) | (_, PrivateKeyLocation::Tests) => { + panic!("Test keys unexpected") + } (old_location, new_location) => { bail!( "The private key has changed. It used to come from {old_location:?}, but now it comes from {new_location:?}." @@ -253,11 +246,6 @@ pub fn compare_private_key_hashes( } } -fn generate_random_private_key() -> ServerSetup { - let mut rng = rand::rngs::OsRng; - ServerSetup::new(&mut rng) -} - #[cfg(unix)] fn set_mode(permissions: &mut std::fs::Permissions) { use std::os::unix::fs::PermissionsExt; diff --git a/server/src/infra/db_cleaner.rs b/server/src/infra/db_cleaner.rs index b5d3c00..47b6a74 100644 --- a/server/src/infra/db_cleaner.rs +++ b/server/src/infra/db_cleaner.rs @@ -1,4 +1,4 @@ -use crate::domain::sql_tables::DbConnection; +use crate::sql_tables::DbConnection; use actix::prelude::{Actor, AsyncContext, Context}; use cron::Schedule; use lldap_domain_model::model::{ diff --git a/server/src/infra/graphql/api.rs b/server/src/infra/graphql/api.rs index e9660ab..a8b8571 100644 --- a/server/src/infra/graphql/api.rs +++ b/server/src/infra/graphql/api.rs @@ -86,8 +86,8 @@ fn schema() -> Schema { } pub fn export_schema(opts: ExportGraphQLSchemaOpts) -> anyhow::Result<()> { - use crate::domain::sql_backend_handler::SqlBackendHandler; use anyhow::Context; + use lldap_sql_backend_handler::SqlBackendHandler; let output = schema::().as_schema_language(); match opts.output_file { None => println!("{}", output), diff --git a/server/src/infra/jwt_sql_tables.rs b/server/src/infra/jwt_sql_tables.rs index db3056d..3cc1514 100644 --- a/server/src/infra/jwt_sql_tables.rs +++ b/server/src/infra/jwt_sql_tables.rs @@ -3,7 +3,7 @@ use sea_orm::{ sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Table}, }; -pub use crate::domain::{sql_migrations::Users, sql_tables::DbConnection}; +pub use lldap_sql_backend_handler::{sql_migrations::Users, sql_tables::DbConnection}; /// Contains the refresh tokens for a given user. #[derive(DeriveIden)] diff --git a/server/src/infra/logging.rs b/server/src/infra/logging.rs index 4a84e48..003d648 100644 --- a/server/src/infra/logging.rs +++ b/server/src/infra/logging.rs @@ -53,14 +53,3 @@ pub fn init(config: &Configuration) -> anyhow::Result<()> { } Ok(()) } - -#[cfg(test)] -pub fn init_for_tests() { - if let Err(e) = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(tracing::Level::DEBUG) - .with_test_writer() - .try_init() - { - log::warn!("Could not set up test logging: {:#}", e); - } -} diff --git a/server/src/infra/sql_backend_handler.rs b/server/src/infra/sql_backend_handler.rs index c2068a2..33a7ff7 100644 --- a/server/src/infra/sql_backend_handler.rs +++ b/server/src/infra/sql_backend_handler.rs @@ -1,5 +1,4 @@ -use super::tcp_backend_handler::TcpBackendHandler; -use crate::domain::sql_backend_handler::SqlBackendHandler; +use crate::infra::tcp_backend_handler::TcpBackendHandler; use async_trait::async_trait; use chrono::NaiveDateTime; use lldap_domain::types::UserId; @@ -7,6 +6,7 @@ use lldap_domain_model::{ error::*, model::{self, JwtRefreshStorageColumn, JwtStorageColumn, PasswordResetTokensColumn}, }; +use lldap_sql_backend_handler::SqlBackendHandler; use sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QuerySelect, sea_query::{Cond, Expr}, @@ -33,7 +33,7 @@ impl TcpBackendHandler for SqlBackendHandler { .column(JwtStorageColumn::JwtHash) .filter(JwtStorageColumn::Blacklisted.eq(true)) .into_tuple::<(i64,)>() - .all(&self.sql_pool) + .all(self.pool()) .await? .into_iter() .map(|m| m.0 as u64) @@ -59,7 +59,7 @@ impl TcpBackendHandler for SqlBackendHandler { expiry_date: chrono::Utc::now().naive_utc() + duration, } .into_active_model(); - new_token.insert(&self.sql_pool).await?; + new_token.insert(self.pool()).await?; Ok((refresh_token, duration)) } @@ -78,7 +78,7 @@ impl TcpBackendHandler for SqlBackendHandler { expiry_date, } .into_active_model(); - new_token.insert(&self.sql_pool).await?; + new_token.insert(self.pool()).await?; Ok(()) } @@ -88,7 +88,7 @@ impl TcpBackendHandler for SqlBackendHandler { Ok( model::JwtRefreshStorage::find_by_id(refresh_token_hash as i64) .filter(JwtRefreshStorageColumn::UserId.eq(user)) - .one(&self.sql_pool) + .one(self.pool()) .await? .is_some(), ) @@ -106,7 +106,7 @@ impl TcpBackendHandler for SqlBackendHandler { .add(JwtStorageColumn::Blacklisted.eq(false)), ) .into_tuple::<(i64,)>() - .all(&self.sql_pool) + .all(self.pool()) .await? .into_iter() .map(|t| t.0 as u64) @@ -114,7 +114,7 @@ impl TcpBackendHandler for SqlBackendHandler { model::JwtStorage::update_many() .col_expr(JwtStorageColumn::Blacklisted, Expr::value(true)) .filter(JwtStorageColumn::UserId.eq(user)) - .exec(&self.sql_pool) + .exec(self.pool()) .await?; Ok(valid_tokens) } @@ -122,7 +122,7 @@ impl TcpBackendHandler for SqlBackendHandler { #[instrument(skip_all, level = "debug")] async fn delete_refresh_token(&self, refresh_token_hash: u64) -> Result<()> { model::JwtRefreshStorage::delete_by_id(refresh_token_hash as i64) - .exec(&self.sql_pool) + .exec(self.pool()) .await?; Ok(()) } @@ -131,7 +131,7 @@ impl TcpBackendHandler for SqlBackendHandler { async fn start_password_reset(&self, user: &UserId) -> Result> { debug!(?user); if model::User::find_by_id(user.clone()) - .one(&self.sql_pool) + .one(self.pool()) .await? .is_none() { @@ -148,7 +148,7 @@ impl TcpBackendHandler for SqlBackendHandler { expiry_date: chrono::Utc::now().naive_utc() + duration, } .into_active_model(); - new_token.insert(&self.sql_pool).await?; + new_token.insert(self.pool()).await?; Ok(Some(token)) } @@ -156,7 +156,7 @@ impl TcpBackendHandler for SqlBackendHandler { async fn get_user_id_for_password_reset_token(&self, token: &str) -> Result { Ok(model::PasswordResetTokens::find_by_id(token.to_owned()) .filter(PasswordResetTokensColumn::ExpiryDate.gt(chrono::Utc::now().naive_utc())) - .one(&self.sql_pool) + .one(self.pool()) .await? .ok_or_else(|| DomainError::EntityNotFound("Invalid reset token".to_owned()))? .user_id) @@ -165,7 +165,7 @@ impl TcpBackendHandler for SqlBackendHandler { #[instrument(skip_all, level = "debug")] async fn delete_password_reset_token(&self, token: &str) -> Result<()> { let result = model::PasswordResetTokens::delete_by_id(token.to_owned()) - .exec(&self.sql_pool) + .exec(self.pool()) .await?; if result.rows_affected == 0 { return Err(DomainError::EntityNotFound(format!( diff --git a/server/src/infra/tcp_backend_handler.rs b/server/src/infra/tcp_backend_handler.rs index e146951..1daf4b5 100644 --- a/server/src/infra/tcp_backend_handler.rs +++ b/server/src/infra/tcp_backend_handler.rs @@ -1,9 +1,8 @@ use async_trait::async_trait; use chrono::NaiveDateTime; -use std::collections::HashSet; - use lldap_domain::types::UserId; use lldap_domain_model::error::Result; +use std::collections::HashSet; #[async_trait] pub trait TcpBackendHandler: Sync { diff --git a/server/src/main.rs b/server/src/main.rs index 0374546..e143106 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -3,27 +3,23 @@ // TODO: Remove next line when it stops warning about async functions. #![allow(clippy::blocks_in_conditions)] -use std::time::Duration; - -use crate::{ - domain::{ - sql_backend_handler::SqlBackendHandler, - sql_opaque_handler::register_password, - sql_tables::{get_private_key_info, set_private_key_info}, - }, - infra::{ - cli::*, - configuration::{Configuration, compare_private_key_hashes}, - database_string::DatabaseUrl, - db_cleaner::Scheduler, - healthcheck, mail, - }, +use crate::infra::{ + cli::*, + configuration::{Configuration, compare_private_key_hashes}, + database_string::DatabaseUrl, + db_cleaner::Scheduler, + healthcheck, mail, }; use actix::Actor; use actix_server::ServerBuilder; use anyhow::{Context, Result, anyhow, bail}; use futures_util::TryFutureExt; +use lldap_sql_backend_handler::{ + SqlBackendHandler, register_password, + sql_tables::{self, get_private_key_info, set_private_key_info}, +}; use sea_orm::{Database, DatabaseConnection}; +use std::time::Duration; use tracing::{Instrument, Level, debug, error, info, instrument, span, warn}; use lldap_domain::requests::{CreateGroupRequest, CreateUserRequest}; @@ -32,7 +28,6 @@ use lldap_domain_handlers::handler::{ UserListerBackendHandler, UserRequestFilter, }; -mod domain; mod infra; const ADMIN_PASSWORD_MISSING_ERROR: &str = "The LDAP admin password must be initialized. \ @@ -109,7 +104,7 @@ async fn setup_sql_tables(database_url: &DatabaseUrl) -> Result Result { return Err(anyhow!("The private key encoding the passwords has changed since last successful startup. Changing the private key will invalidate all existing passwords. If you want to proceed, restart the server with the CLI arg --force-update-private-key=true or the env variable LLDAP_FORCE_UPDATE_PRIVATE_KEY=true. You probably also want --force-ldap-user-pass-reset / LLDAP_FORCE_LDAP_USER_PASS_RESET=true to reset the admin password to the value in the configuration.").context(e)); } } - let backend_handler = SqlBackendHandler::new(config.clone(), sql_pool.clone()); + let backend_handler = + SqlBackendHandler::new(config.get_server_setup().clone(), sql_pool.clone()); ensure_group_exists(&backend_handler, "lldap_admin").await?; ensure_group_exists(&backend_handler, "lldap_password_manager").await?; ensure_group_exists(&backend_handler, "lldap_strict_readonly").await?;