ldapico 0.1.0

Wrapper over ldap3 for use with tarantool-module
Documentation
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
});

/// Wrapper over ldap3::Ldap. Supported only read operations.
pub struct Ldap<'a> {
    rt: &'a Runtime,
    ldap: ldap3::Ldap,
}

impl<'a> Ldap<'a> {
    /// Make Ldap instance with default runtime
    pub fn new(url: &str) -> Result<Self, Box<dyn Error>> {
        Self::new_with_runtime(url, &TOKIO_RUNTIME)
    }

    /// Make new Ldap instance with custom 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 })
    }

    /// Add search options.
    /// Example option SearchOptions::new().timelimit(3)
    pub fn with_search_options(&mut self, opts: SearchOptions) -> &mut Self {
        self.ldap.with_search_options(opts);
        self
    }

    /// Add controls.
    /// Example control ldapico::ldap3::controls::ProxyAuth{authzid: "authzid".to_string()}
    pub fn with_controls<V: IntoRawControlVec>(&mut self, ctrls: V) -> &mut Self {
        self.ldap.with_controls(ctrls);
        self
    }

    /// Add timeout.
    pub fn with_timeout(&mut self, duration: Duration) -> &mut Self {
        self.ldap.with_timeout(duration);
        self
    }

    /// Bind user on LDAP server
    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"));
        });
    }

    /// Search entries on LDAP server
    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([]))
    }

    /// Search entries on LDAP server with selected attributes
    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"));
    }
}