2023-08-18 10:59:50 +02:00

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)
}
}