111 lines
3.5 KiB
Rust
111 lines
3.5 KiB
Rust
use ldap3::{LdapConnAsync, LdapConnSettings, Ldap, Scope, exop::WhoAmI, SearchEntry};
|
|
use snafu::prelude::*;
|
|
use tokio::sync::RwLock;
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use crate::credentials::{Username, Password};
|
|
use crate::error::*;
|
|
|
|
const LDAP_PATH: &'static str = "ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi";
|
|
|
|
/// Opens a new LDAP connection. Does not guarantee it will stay alive
|
|
async fn new_ldap(timeout: Duration) -> Result<Ldap, Error> {
|
|
let settings = LdapConnSettings::new().set_conn_timeout(timeout);
|
|
log::info!("Opening new LDAP connection with timeout: {}ms", timeout.as_millis());
|
|
|
|
let (conn, ldap) = LdapConnAsync::with_settings(
|
|
settings,
|
|
LDAP_PATH
|
|
).await.context(LdapInitSnafu { uri: LDAP_PATH.to_string() })?;
|
|
|
|
tokio::spawn(async move {
|
|
if let Err(e) = conn.drive().await {
|
|
log::error!("{}", e);
|
|
}
|
|
});
|
|
|
|
Ok(ldap)
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct YunohostUsers {
|
|
timeout: Duration,
|
|
inner: Arc<RwLock<Ldap>>,
|
|
}
|
|
|
|
impl YunohostUsers {
|
|
/// Open a new connection to Yunohost LDAP database with a specific timeout in milliseconds
|
|
pub async fn new(timeout: u64) -> Result<YunohostUsers, Error> {
|
|
let timeout = Duration::from_millis(timeout);
|
|
Ok(YunohostUsers {
|
|
timeout: timeout.clone(),
|
|
inner: Arc::new(RwLock::new(
|
|
new_ldap(timeout).await?
|
|
)),
|
|
})
|
|
}
|
|
|
|
pub async fn keepalive(&self) -> Result<(), Error> {
|
|
{
|
|
let mut ldap = self.inner.write().await;
|
|
if ! ldap.is_closed() {
|
|
// check connection is still alive with WhoAmI
|
|
ldap.with_timeout(self.timeout.clone());
|
|
if ldap.extended(WhoAmI).await.is_ok() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
log::warn!("LDAP connection has been closed. Opening again.");
|
|
let mut ldap = self.inner.clone().write_owned().await;
|
|
*ldap = new_ldap(self.timeout.clone()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn check_credentials(&self, username: &Username, password: &Password) -> Result<bool, Error> {
|
|
self.keepalive().await?;
|
|
let mut ldap = self.inner.write().await;
|
|
|
|
let username = username.to_dn(& [ "yunohost", "org" ]);
|
|
let password = password.ldap_escape();
|
|
|
|
log::debug!("Attempting LDAP login for {}", &username);
|
|
ldap.with_timeout(self.timeout.clone());
|
|
let reply = ldap.simple_bind(
|
|
&username,
|
|
&password,
|
|
).await.context(LdapBindSnafu)?;
|
|
log::debug!("{:#?}", reply);
|
|
|
|
// Successful auth if return code is 0
|
|
Ok(reply.rc == 0)
|
|
}
|
|
|
|
pub async fn get_user(&self, username: &Username) -> Result<bool, Error> {
|
|
self.keepalive().await?;
|
|
let mut ldap = self.inner.write().await;
|
|
ldap.with_timeout(self.timeout.clone());
|
|
|
|
let res = ldap.search(
|
|
&username.to_dn(& [ "yunohost", "org" ]),
|
|
Scope::Base,
|
|
"(objectclass=*)",
|
|
[ "+" ],
|
|
).await.context(LdapSearchSnafu)?;
|
|
|
|
if let Ok((res, _)) = res.success() {
|
|
// There should be only one user with this uid
|
|
let res = res.into_iter().take(1).next().unwrap();
|
|
let res = SearchEntry::construct(res);
|
|
log::debug!("Found:\n{:#?}", res);
|
|
} else {
|
|
return Err(Error::LdapNoSuchUser { username: username.clone() });
|
|
}
|
|
|
|
|
|
Ok(true)
|
|
}
|
|
}
|