Split lk-search/src/main.rs

- Module for arg-parsing
- New crate for ldap client code
- Introduce man-page generation
This commit is contained in:
Candifloss 2026-09-24 13:44:08 +05:30
parent e7d5da4678
commit 76eb96a96f
7 changed files with 98 additions and 33 deletions

View File

@ -21,3 +21,4 @@ ldap3 = "0.12.1"
serde = { version = "1.0.229", features = ["derive"] }
tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread"] }
toml = "1.1.6"
clap_mangen = "0.3.3"

View File

@ -1 +1,39 @@
use anyhow::Result;
use ldap3::{LdapConn, Scope, SearchEntry};
use config::ConnectionConfig;
/// LDAP client.
pub struct LdapClient {
connection: LdapConn,
}
impl LdapClient {
/// Connect to the LDAP server using the read credentials.
pub fn connect(config: &ConnectionConfig) -> Result<Self> {
let mut connection = LdapConn::new(&config.ldap.url)?;
connection
.simple_bind(&config.read.bind_dn, &config.read.bind_pw)?
.success()?;
Ok(Self { connection })
}
/// Search the LDAP directory.
///
/// Returns the matching entries as `ldap3::SearchEntry` values.
pub fn search(
&mut self,
base_dn: &str,
filter: &str,
attributes: Vec<&str>,
) -> Result<Vec<SearchEntry>> {
let (entries, _) = self
.connection
.search(base_dn, Scope::Subtree, filter, attributes)?
.success()?;
Ok(entries.into_iter().map(SearchEntry::construct).collect())
}
}

View File

@ -1,5 +1,3 @@
mod client;
mod user;
//pub use client::LdapClient;
pub use user::User;
pub use client::LdapClient;

View File

@ -9,4 +9,9 @@ repository.workspace = true
anyhow.workspace = true
clap.workspace = true
config = { path = "../../crates/config" }
ldap = { path = "../../crates/ldap" }
ldap3.workspace = true
[build-dependencies]
clap.workspace = true
clap_mangen.workspace = true

23
tools/lk-search/build.rs Normal file
View File

@ -0,0 +1,23 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use clap::CommandFactory;
use clap_mangen::Man;
#[path = "src/args.rs"]
mod args;
fn main() -> std::io::Result<()> {
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let command = args::Args::command();
let man = Man::new(command);
let mut output = Vec::new();
man.render(&mut output)?;
fs::write(out_dir.join("lk-search.1"), output)?;
Ok(())
}

View File

@ -0,0 +1,18 @@
use clap::{ArgGroup, Parser};
#[derive(Debug, Parser)]
#[command(name = "lk-search", about = "Search an LDAP directory")]
#[command(group(
ArgGroup::new("search")
.required(true)
.args(["username", "id"])
))]
pub struct Args {
/// Search by username.
#[arg(short = 'u', long = "username", value_name = "USERNAME")]
pub username: Option<String>,
/// Search by identifier.
#[arg(short = 'i', long = "id", value_name = "ID")]
pub id: Option<String>,
}

View File

@ -1,49 +1,31 @@
mod args;
use anyhow::{Result, bail};
use clap::{ArgGroup, Parser};
use ldap3::{LdapConn, Scope, SearchEntry};
use clap::Parser;
use config::ConnectionConfig;
use ldap::LdapClient;
#[derive(Debug, Parser)]
#[command(name = "lk-search", about = "Search an LDAP user")]
#[command(group(
ArgGroup::new("user")
.required(true)
.args(["username", "id_number"])
))]
struct Args {
/// Search by username.
#[arg(short = 'u', long = "username", value_name = "USERNAME")]
username: Option<String>,
/// Search by ID number.
#[arg(short = 'n', long = "id-number", value_name = "ID")]
id_number: Option<String>,
}
use args::Args;
fn main() -> Result<()> {
let args = Args::parse();
let config = ConnectionConfig::load()?;
let (search_attribute, search_value) = match (&args.username, &args.id_number) {
let (search_attribute, search_value) = match (&args.username, &args.id) {
(Some(username), None) => ("uid", username),
(None, Some(id_number)) => ("uidNumber", id_number),
(None, Some(id)) => ("uidNumber", id),
_ => unreachable!(),
};
let filter = format!("({search_attribute}={search_value})");
let mut ldap = LdapConn::new(&config.ldap.url)?;
let mut ldap = LdapClient::connect(&config)?;
ldap.simple_bind(&config.read.bind_dn, &config.read.bind_pw)?
.success()?;
let (entries, _result) = ldap
.search(&config.ldap.base_dn, Scope::Subtree, &filter, vec!["*"])?
.success()?;
let entries = ldap.search(&config.ldap.base_dn, &filter, vec!["*"])?;
if entries.is_empty() {
bail!("User not found.");
bail!("No matching entries found.");
}
if entries.len() > 1 {
@ -53,11 +35,11 @@ fn main() -> Result<()> {
);
}
let entry = SearchEntry::construct(entries.into_iter().next().unwrap());
let entry = &entries[0];
println!("DN: {}", entry.dn);
for (attribute, values) in entry.attrs {
for (attribute, values) in &entry.attrs {
println!("{attribute}:");
for value in values {