server: extract the sql backend handler to a separate crate

This commit is contained in:
Valentin Tolmer
2025-04-04 23:43:25 -05:00
committed by nitnelave
parent ee21d83056
commit 55de3ac329
27 changed files with 276 additions and 156 deletions

34
Cargo.lock generated
View File

@@ -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"

View File

@@ -132,6 +132,12 @@ pub mod server {
pub use super::*;
pub type ServerRegistration = opaque_ke::ServerRegistration<DefaultSuite>;
pub type ServerSetup = opaque_ke::ServerSetup<DefaultSuite>;
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::*;

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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"]

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
{

View File

@@ -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},

View File

@@ -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<SchemaVersion> {
pub(crate) async fn get_schema_version(pool: &DbConnection) -> Option<SchemaVersion> {
JustSchemaVersion::find_by_statement(
pool.get_database_backend().build(
Query::select()
@@ -131,7 +131,7 @@ pub async fn get_schema_version(pool: &DbConnection) -> Option<SchemaVersion> {
.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,

View File

@@ -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<orion::aead::SecretKey> {
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<registration::ServerRegistrationStartResponse> {
// 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

View File

@@ -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};

View File

@@ -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))

View File

@@ -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();

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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::{

View File

@@ -86,8 +86,8 @@ fn schema<Handler: BackendHandler>() -> Schema<Handler> {
}
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::<SqlBackendHandler>().as_schema_language();
match opts.output_file {
None => println!("{}", output),

View File

@@ -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)]

View File

@@ -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);
}
}

View File

@@ -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<Option<String>> {
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<UserId> {
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!(

View File

@@ -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 {

View File

@@ -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<DatabaseConnecti
.sqlx_logging_level(log::LevelFilter::Debug);
Database::connect(sql_opt).await?
};
domain::sql_tables::init_table(&sql_pool)
sql_tables::init_table(&sql_pool)
.await
.context("while creating base tables")?;
infra::jwt_sql_tables::init_table(&sql_pool)
@@ -145,7 +140,8 @@ async fn set_up_server(config: Configuration) -> Result<ServerBuilder> {
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?;