use anyhow::{Context, Result}; use figment::{ providers::{Env, Format, Serialized, Toml}, Figment, }; use lldap_auth::opaque::{server::ServerSetup, KeyPair}; use log::*; use serde::{Deserialize, Serialize}; use crate::infra::cli::RunOpts; #[derive(Clone, Debug, Deserialize, Serialize, derive_builder::Builder)] #[builder( pattern = "owned", default = "Configuration::default()", build_fn(name = "private_build", validate = "Self::validate") )] pub struct Configuration { pub ldap_port: u16, pub ldaps_port: u16, pub http_port: u16, pub jwt_secret: String, pub ldap_base_dn: String, pub ldap_user_dn: String, pub ldap_user_pass: String, pub database_url: String, pub verbose: bool, pub key_file: String, #[serde(skip)] #[builder(field(private), setter(strip_option))] server_setup: Option, } impl ConfigurationBuilder { #[cfg(test)] pub fn build(self) -> Result { let server_setup = get_server_setup(self.key_file.as_deref().unwrap_or("server_key"))?; Ok(self.server_setup(server_setup).private_build()?) } fn validate(&self) -> Result<(), String> { if self.server_setup.is_none() { Err("Don't use `private_build`, use `build` instead".to_string()) } else { Ok(()) } } } impl Configuration { pub fn get_server_setup(&self) -> &ServerSetup { self.server_setup.as_ref().unwrap() } pub fn get_server_keys(&self) -> &KeyPair { self.get_server_setup().keypair() } fn merge_with_cli(mut self: Configuration, cli_opts: RunOpts) -> Configuration { if cli_opts.verbose { self.verbose = true; } if let Some(port) = cli_opts.ldap_port { self.ldap_port = port; } if let Some(port) = cli_opts.ldaps_port { self.ldaps_port = port; } self } pub(super) fn default() -> Self { Configuration { ldap_port: 3890, ldaps_port: 6360, http_port: 17170, jwt_secret: String::from("secretjwtsecret"), ldap_base_dn: String::from("dc=example,dc=com"), // cn=admin,dc=example,dc=com ldap_user_dn: String::from("admin"), ldap_user_pass: String::from("password"), database_url: String::from("sqlite://users.db?mode=rwc"), verbose: false, key_file: String::from("server_key"), server_setup: None, } } } fn get_server_setup(file_path: &str) -> Result { use std::path::Path; let path = Path::new(file_path); if path.exists() { let bytes = std::fs::read(file_path).context(format!("Could not read key file `{}`", file_path))?; Ok(ServerSetup::deserialize(&bytes)?) } else { let mut rng = rand::rngs::OsRng; let server_setup = ServerSetup::new(&mut rng); std::fs::write(path, server_setup.serialize()).context(format!( "Could not write the generated server setup to file `{}`", file_path, ))?; Ok(server_setup) } } pub fn init(cli_opts: RunOpts) -> Result { let config_file = cli_opts.config_file.clone(); info!("Loading configuration from {}", cli_opts.config_file); let config: Configuration = Figment::from(Serialized::defaults(Configuration::default())) .merge(Toml::file(config_file)) .merge(Env::prefixed("LLDAP_")) .extract()?; let mut config = config.merge_with_cli(cli_opts); config.server_setup = Some(get_server_setup(&config.key_file)?); Ok(config) }