#![warn(missing_docs)]
use dircpy::copy_dir;
use std::convert::AsRef;
use std::path::Path;
use tempfile::TempDir;
use tokio::process::{Child, Command};
use tokio::task;
use tracing::{debug, warn};
mod builder;
pub use builder::LdapServerBuilder;
#[derive(Debug)]
pub struct LdapServerConn {
url: String,
host: String,
port: u16,
ssl_url: String,
ssl_port: u16,
ssl_cert_pem: String,
#[allow(unused)]
dir: TempDir,
base_dn: String,
root_dn: String,
root_pw: String,
server: Child,
}
impl LdapServerConn {
pub fn url(&self) -> &str {
&self.url
}
pub fn host(&self) -> &str {
&self.host
}
pub fn port(&self) -> u16 {
self.port
}
pub fn ssl_url(&self) -> &str {
&self.ssl_url
}
pub fn ssl_port(&self) -> u16 {
self.ssl_port
}
pub fn ssl_cert_pem(&self) -> &str {
&self.ssl_cert_pem
}
pub fn base_dn(&self) -> &str {
&self.base_dn
}
pub fn root_dn(&self) -> &str {
&self.root_dn
}
pub fn root_pw(&self) -> &str {
&self.root_pw
}
pub fn server_dir(&self) -> &Path {
self.dir.path()
}
pub async fn clone_to_dir<P: AsRef<Path>>(&self, desc: P) {
let src = self.dir.path().to_path_buf();
let dst = desc.as_ref().to_path_buf();
task::spawn_blocking(move || {
copy_dir(&src, &dst).unwrap();
})
.await
.unwrap();
}
pub async fn add(&self, ldif_text: &str) -> &Self {
let tmp_ldif = self.dir.path().join("tmp.ldif");
tokio::fs::write(&tmp_ldif, ldif_text).await.unwrap();
self.add_file(tmp_ldif).await
}
pub async fn add_file<P: AsRef<Path>>(&self, file: P) -> &Self {
self.load_ldif_file("ldapadd", file, self.root_dn(), self.root_pw())
.await
}
pub async fn modify(&self, ldif_text: &str) -> &Self {
let tmp_ldif = self.dir.path().join("tmp.ldif");
tokio::fs::write(&tmp_ldif, ldif_text).await.unwrap();
self.modify_file(tmp_ldif).await
}
pub async fn modify_file<P: AsRef<Path>>(&self, file: P) -> &Self {
self.load_ldif_file("ldapmodify", file, self.root_dn(), self.root_pw())
.await
}
pub async fn delete(&self, ldif_text: &str) -> &Self {
let tmp_ldif = self.dir.path().join("tmp.ldif");
tokio::fs::write(&tmp_ldif, ldif_text).await.unwrap();
self.modify_file(tmp_ldif).await
}
pub async fn delete_file<P: AsRef<Path>>(&self, file: P) -> &Self {
self.load_ldif_file("ldapdelete", file, self.root_dn(), self.root_pw())
.await
}
async fn load_ldif_file<P: AsRef<Path>>(
&self,
command: &str,
file: P,
binddn: &str,
password: &str,
) -> &Self {
let file = file.as_ref();
let output = Command::new(command)
.args(["-x", "-D", binddn, "-w", password, "-H", self.url(), "-f"])
.arg(file)
.output()
.await
.expect("failed to load ldap file");
if !output.status.success() {
panic!(
"{command} command exited with error {}, stdout: {}, stderr: {} on file {}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
file.display()
);
}
self
}
}
impl Drop for LdapServerConn {
fn drop(&mut self) {
if let Err(e) = self.server.start_kill() {
warn!(
"failed to kill slapd server: {}, pid: {:?}",
e,
self.server.id()
);
} else {
debug!("killed slapd server pid: {:?}", self.server.id());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[tokio::test]
async fn run_slapd() {
let started = Instant::now();
let server = LdapServerBuilder::new("dc=planetexpress,dc=com")
.add_system_file(0, "pmi.ldif")
.add(
1,
"dn: dc=planetexpress,dc=com
objectclass: dcObject
objectclass: organization
o: Planet Express
dc: planetexpress
dn: ou=people,dc=planetexpress,dc=com
objectClass: top
objectClass: organizationalUnit
description: Planet Express crew
ou: people",
)
.add_file(1, concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fry.ldif"))
.run()
.await;
server
.add(
"dn: cn=Amy Wong+sn=Kroker,ou=people,dc=planetexpress,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Amy Wong
sn: Kroker
description: Human
givenName: Amy
mail: amy@planetexpress.com
ou: Intern
uid: amy",
)
.await;
println!("Server started in {} ms", started.elapsed().as_millis());
}
}