use ldap3::controls::IntoRawControlVec;
use ldap3::result::Result as LDAPResult;
use ldap3::{LdapConnAsync, LdapResult, Scope, SearchOptions, SearchResult};
use log::error;
use once_cell::sync::Lazy;
use std::error::Error;
use std::time::Duration;
use tokio::runtime::{Builder as TokioBuilder, Runtime};
use tokio::sync::oneshot::Sender;
static TOKIO_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
let runtime = TokioBuilder::new_multi_thread()
.worker_threads(4)
.enable_io()
.enable_time()
.thread_keep_alive(Duration::from_secs(60))
.build()
.unwrap();
runtime
});
pub struct Ldap<'a> {
rt: &'a Runtime,
ldap: ldap3::Ldap,
}
impl<'a> Ldap<'a> {
pub fn new(url: &str) -> Result<Self, Box<dyn Error>> {
Self::new_with_runtime(url, &TOKIO_RUNTIME)
}
pub fn new_with_runtime(url: &str, rt: &'a Runtime) -> Result<Self, Box<dyn Error>> {
let (conn, ldap) = rt.block_on(LdapConnAsync::new(url))?;
rt.spawn(async move {
if let Err(e) = conn.drive().await {
error!("LDAP connection error: {}", e);
}
});
Ok(Self { rt, ldap })
}
pub fn with_search_options(&mut self, opts: SearchOptions) -> &mut Self {
self.ldap.with_search_options(opts);
self
}
pub fn with_controls<V: IntoRawControlVec>(&mut self, ctrls: V) -> &mut Self {
self.ldap.with_controls(ctrls);
self
}
pub fn with_timeout(&mut self, duration: Duration) -> &mut Self {
self.ldap.with_timeout(duration);
self
}
pub fn simple_bind(
&self,
tx: Sender<LDAPResult<LdapResult>>,
bind_dn: impl Into<String>,
bind_pw: impl Into<String>,
) {
let mut ldap = self.ldap.clone();
let bind_dn = bind_dn.into();
let bind_pw = bind_pw.into();
self.rt.spawn(async move {
let result = ldap.simple_bind(&bind_dn, &bind_pw).await;
tx.send(result)
.unwrap_or_else(|_| error!("Error send ldap response to channel"));
});
}
pub fn search(
&self,
tx: Sender<LDAPResult<SearchResult>>,
base: impl Into<String>,
scope: Scope,
filter: impl Into<String>,
) {
self.search_with_attrs(tx, base, scope, filter, Box::new([]))
}
pub fn search_with_attrs(
&self,
tx: Sender<LDAPResult<SearchResult>>,
base: impl Into<String>,
scope: Scope,
filter: impl Into<String>,
attrs: Box<[String]>,
) {
let mut ldap = self.ldap.clone();
let base = base.into();
let filter = filter.into();
self.rt.spawn(async move {
let result = ldap.search(&base, scope, &filter, attrs).await;
tx.send(result)
.unwrap_or_else(|_| error!("Error send ldap response to channel"));
});
}
}
impl<'a> Drop for Ldap<'a> {
fn drop(&mut self) {
self.rt
.block_on(self.ldap.unbind())
.unwrap_or_else(|_| error!("Error unbind ldap connection"));
}
}