From 8a2b9d095238196431bbf939ecfa6ccd101722a9 Mon Sep 17 00:00:00 2001 From: Candifloss Date: Tue, 14 Jul 2026 16:41:25 +0530 Subject: [PATCH] Password reset config lib - First part to the config lib - LDAP connection module - Constants --- crates/config/Cargo.toml | 3 + crates/config/src/connection.rs | 129 +++++++++++++++++++++++++++++ crates/config/src/constants.rs | 13 +++ crates/config/src/lib.rs | 30 ++++--- crates/config/src/pswdreset.rs | 142 ++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+), 12 deletions(-) create mode 100644 crates/config/src/connection.rs create mode 100644 crates/config/src/constants.rs create mode 100644 crates/config/src/pswdreset.rs diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index bd0c86b..3904103 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -6,3 +6,6 @@ license.workspace = true repository.workspace = true [dependencies] +anyhow.workspace = true +serde = { workspace = true, features = ["derive"] } +toml.workspace = true diff --git a/crates/config/src/connection.rs b/crates/config/src/connection.rs new file mode 100644 index 0000000..71c7b77 --- /dev/null +++ b/crates/config/src/connection.rs @@ -0,0 +1,129 @@ +//! LDAP connection configuration. +//! +//! Loads `/etc/ldap-kit/connection.toml`, applies default values, +//! validates required fields, and applies write credential fallbacks. + +use anyhow::{Result, bail}; +use serde::Deserialize; +use std::fs; + +use crate::constants::{CONNECTION_CONFIG, DEFAULT_LDAP_URL}; + +/// Final connection configuration. +/// +/// All defaults and fallbacks have already been applied. +#[derive(Debug, Clone)] +pub struct ConnectionConfig { + pub ldap: LdapSettings, + pub read: BindCredentials, + pub write: BindCredentials, +} + +/// LDAP server settings. +#[derive(Debug, Clone)] +pub struct LdapSettings { + pub url: String, + pub base_dn: String, +} + +/// LDAP bind credentials. +#[derive(Debug, Clone)] +pub struct BindCredentials { + pub bind_dn: String, + pub bind_pw: String, +} + +/// Structure used only while parsing the TOML file. +#[derive(Debug, Deserialize)] +struct RawConnectionConfig { + ldap: RawLdapSettings, + read: RawBindCredentials, + + #[serde(default)] + write: Option, +} + +/// Raw LDAP settings from TOML. +#[derive(Debug, Deserialize)] +struct RawLdapSettings { + #[serde(default = "default_ldap_url")] + url: String, + + base_dn: String, +} + +/// Raw bind credentials from TOML. +#[derive(Debug, Deserialize)] +struct RawBindCredentials { + bind_dn: String, + + #[serde(default)] + bind_pw: String, +} + +/// Default LDAP URL. +fn default_ldap_url() -> String { + DEFAULT_LDAP_URL.to_owned() +} + +impl ConnectionConfig { + /// Load the default connection configuration. + pub fn load() -> Result { + Self::load_from(CONNECTION_CONFIG) + } + + /// Load connection configuration from a specific file. + pub fn load_from(path: &str) -> Result { + let contents = fs::read_to_string(path)?; + + let raw: RawConnectionConfig = toml::from_str(&contents)?; + + Self::from_raw(raw) + } + + /// Convert parsed configuration into the final validated structure. + fn from_raw(raw: RawConnectionConfig) -> Result { + // Required fields + if raw.ldap.base_dn.trim().is_empty() { + bail!("ldap.base_dn is required"); + } + + if raw.read.bind_dn.trim().is_empty() { + bail!("read.bind_dn is required"); + } + + let read = BindCredentials { + bind_dn: raw.read.bind_dn, + bind_pw: raw.read.bind_pw, + }; + + // Apply write fallbacks. + let write = match raw.write { + Some(write) => BindCredentials { + bind_dn: if write.bind_dn.trim().is_empty() { + read.bind_dn.clone() + } else { + write.bind_dn + }, + + bind_pw: if write.bind_pw.is_empty() { + read.bind_pw.clone() + } else { + write.bind_pw + }, + }, + + None => read.clone(), + }; + + Ok(ConnectionConfig { + ldap: LdapSettings { + url: raw.ldap.url, + base_dn: raw.ldap.base_dn, + }, + + read, + write, + }) + } +} diff --git a/crates/config/src/constants.rs b/crates/config/src/constants.rs new file mode 100644 index 0000000..12ea40c --- /dev/null +++ b/crates/config/src/constants.rs @@ -0,0 +1,13 @@ +//! Configuration file locations and default values. + +/// Configuration directory. +pub const CONFIG_DIR: &str = "/etc/ldap-kit"; + +/// Shared LDAP connection configuration. +pub const CONNECTION_CONFIG: &str = "/etc/ldap-kit/connection.toml"; + +/// Password reset configuration. +pub const PSWDRESET_CONFIG: &str = "/etc/ldap-kit/pswdreset.toml"; + +/// Default LDAP server URL. +pub const DEFAULT_LDAP_URL: &str = "ldap://localhost:389"; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b93cf3f..7361dc3 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,14 +1,20 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +//! Shared configuration library for ldap-kit. +//! +//! Loads and validates the configuration files used by the ldap-kit +//! command line tools. +//! +//! Each configuration file has its own module. Additional modules can +//! be added as new tools are implemented. -#[cfg(test)] -mod tests { - use super::*; +pub mod connection; +pub mod constants; +pub mod pswdreset; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +// Re-export the public configuration types so callers can write: +// +// use config::{ConnectionConfig, PswdResetConfig}; +// +// instead of importing each module separately. +pub use connection::{BindCredentials, ConnectionConfig, LdapSettings}; + +pub use pswdreset::{AttributeSettings, PasswordMethod, PasswordSettings, PswdResetConfig}; diff --git a/crates/config/src/pswdreset.rs b/crates/config/src/pswdreset.rs new file mode 100644 index 0000000..9658403 --- /dev/null +++ b/crates/config/src/pswdreset.rs @@ -0,0 +1,142 @@ +//! Password reset configuration. +//! +//! Loads `/etc/ldap-kit/pswdreset.toml` and applies default values. + +use anyhow::Result; +use serde::Deserialize; +use std::fs; + +use crate::constants::PSWDRESET_CONFIG; + +/// Password reset configuration. +#[derive(Debug, Clone)] +pub struct PswdResetConfig { + pub attributes: AttributeSettings, + pub password: PasswordSettings, +} + +/// LDAP attribute names used by this tool. +#[derive(Debug, Clone)] +pub struct AttributeSettings { + pub username: String, + pub id_number: String, +} + +/// Password reset settings. +#[derive(Debug, Clone)] +pub struct PasswordSettings { + pub method: PasswordMethod, + pub attribute: String, + pub default_attribute: String, + pub fallback_password: String, +} + +/// Password update method. +#[derive(Debug, Clone, Copy, Default, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PasswordMethod { + /// Try Password Modify first, then fall back to attribute replacement. + #[default] + Auto, + + /// Always use the LDAP Password Modify extended operation. + Passwd, + + /// Replace the password attribute directly. + Attribute, +} + +/// Raw TOML structure. +#[derive(Debug, Deserialize)] +struct RawPswdResetConfig { + #[serde(default)] + attributes: RawAttributeSettings, + + #[serde(default)] + password: RawPasswordSettings, +} + +/// Raw attribute settings. +#[derive(Debug, Deserialize, Default)] +struct RawAttributeSettings { + #[serde(default = "default_username_attribute")] + username: String, + + #[serde(default = "default_id_number_attribute")] + id_number: String, +} + +/// Raw password settings. +#[derive(Debug, Deserialize)] +struct RawPasswordSettings { + #[serde(default)] + method: PasswordMethod, + + #[serde(default = "default_password_attribute")] + attribute: String, + + #[serde(default = "default_default_attribute")] + default_attribute: String, + + #[serde(default)] + fallback_password: String, +} + +impl Default for RawPasswordSettings { + fn default() -> Self { + Self { + method: PasswordMethod::default(), + attribute: default_password_attribute(), + default_attribute: default_default_attribute(), + fallback_password: String::new(), + } + } +} + +fn default_username_attribute() -> String { + "uid".to_owned() +} + +fn default_id_number_attribute() -> String { + "uidNumber".to_owned() +} + +fn default_password_attribute() -> String { + "userPassword".to_owned() +} + +fn default_default_attribute() -> String { + "uid".to_owned() +} + +impl PswdResetConfig { + /// Load the default password reset configuration. + pub fn load() -> Result { + Self::load_from(PSWDRESET_CONFIG) + } + + /// Load configuration from a specific TOML file. + pub fn load_from(path: &str) -> Result { + let contents = fs::read_to_string(path)?; + + let raw: RawPswdResetConfig = toml::from_str(&contents)?; + + Ok(Self::from_raw(raw)) + } + + fn from_raw(raw: RawPswdResetConfig) -> Self { + Self { + attributes: AttributeSettings { + username: raw.attributes.username, + id_number: raw.attributes.id_number, + }, + + password: PasswordSettings { + method: raw.password.method, + attribute: raw.password.attribute, + default_attribute: raw.password.default_attribute, + fallback_password: raw.password.fallback_password, + }, + } + } +}