use core::fmt; use std::{collections::{HashMap, HashSet}, vec}; use base64::{engine::general_purpose::STANDARD, Engine as _}; use deadpool::managed::Pool; use ldap3::{Mod, SearchEntry}; use regex::{Captures, Regex}; use ring::rand::{self, SecureRandom}; use serde_derive::Serialize; use sha2::{Sha512, Digest}; pub mod pool; use pool::{get_ldap_pool, LdapConfig, LdapManager}; # [derive(Serialize)] pub enum Error { Authfailed {message: String}, LdapServerError {message: String}, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Authfailed { message } => write!(f, "{}", message), Error::LdapServerError { message } => write!(f, "LDAP server error: {}", message), } } } pub enum ManageSSHOps { ADD, DEL } #[derive(Clone)] pub struct LdapWrapper { ldap_pool: Pool, config: LdapConfig, } pub struct User { pub uid: String, pub is_ssh: bool, pub groups: Vec, } impl LdapWrapper { pub fn new(config: LdapConfig) -> LdapWrapper { let ldap_pool = get_ldap_pool(config.clone()); LdapWrapper{ldap_pool, config} } async fn get_user_infos(&self, uid: &String) -> Result<(bool, Vec), Error> { let mut ldap = self.ldap_pool.get().await.unwrap(); let search_res = ldap.search(&self.config.basedn, ldap3::Scope::Subtree, format!("(uid={})", uid).as_str(), vec!["memberOf", "objectClass"]) .await.map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; let (result_entry, _ldap_res) = search_res.success() .map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; if result_entry.is_empty() { return Err(Error::LdapServerError {message: format!("Error contact admins: dn shouldn't be empty")}); } let records = SearchEntry::construct(result_entry.first().unwrap().to_owned()); let is_ssh = records.attrs.get("objectClass").unwrap().contains(&"ldapPublicKey".to_string()); let groups: Vec = match records.attrs.get("memberOf") { Some(memberof_records) => memberof_records.iter() .map(|elem| { elem.to_lowercase().replace(&format!(",{}", self.config.groupsdn.to_lowercase()).to_string(), "") .replace("cn=", "") }).collect(), None => Vec::new(), }; Ok((is_ssh, groups)) } pub async fn auth(&self, username: String, passwd: String) -> Result { let mut ldap = self.ldap_pool.get().await.unwrap(); let mut uid = username.clone(); let email = Regex::new(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,6}*").unwrap(); if email.is_match(&username) { let search_res = ldap.search(&self.config.basedn, ldap3::Scope::Subtree, format!("(mail={})", username).as_str(), vec!["uid"]) .await.map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; let (result_entry, _ldap_res) = search_res.success().map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; if result_entry.is_empty() { return Err(Error::Authfailed {message: format!("Wrong username or password")}); } if result_entry.len() > 1 { return Err(Error::Authfailed {message: format!("Error contact admins")}); } let records = SearchEntry::construct(result_entry.first().unwrap().to_owned()); uid = records.attrs.get("uid").unwrap().first().unwrap().to_owned(); } ldap .simple_bind(format!("uid={},{}", uid, self.config.basedn).as_str(), &passwd) .await .map_err(|_| { Error::Authfailed {message: format!("Wrong username or password")} }) .and_then(|r| { r.success().map_err(|_| { Error::Authfailed {message: format!("Wrong username or password")} }) })?; let (is_ssh, groups) = self.get_user_infos(&uid).await?; Ok(User{uid: uid.clone(), is_ssh, groups}) } pub async fn change_password(&self, username: String, password: String, new_password: String) -> Result<(), Error> { let mut salt = [0u8; 4]; let salt_generator = rand::SystemRandom::new(); let _ = salt_generator.fill(&mut salt); let mut password_bytes = new_password.into_bytes(); //adding salt to the end of the password salt.map(|elem| { password_bytes.push(elem); }); //Getting sha512 sum let mut hasher = Sha512::new(); hasher.update(password_bytes); let mut hash = hasher.finalize().as_slice().to_vec(); //Adding salt to the end of hash salt.map(|elem| { hash.push(elem); }); let b64_new_password = format!("{{SSHA512}}{}", STANDARD.encode(hash)); let mut ldap = self.ldap_pool.get().await.unwrap(); let _ = ldap.simple_bind(format!("uid={},{}", username, self.config.basedn).as_str(), &password) .await .map_err(|_| { Error::Authfailed {message: format!("Wrong current password")} }).and_then(|r| { r.success().map_err(|_| { Error::Authfailed {message: format!("Wrong current password")} }) })?; let hashset= HashSet::from([b64_new_password.as_str()]); let data = vec![ldap3::Mod::Replace("userPassword", hashset)]; ldap.modify(format!("uid={},{}", username, self.config.basedn).as_str(), data) .await .map_err(|e| { Error::LdapServerError {message: format!("An error occured, contact admins: {}", e)} }) .and_then(|r| { r.success().map_err(|e| { Error::LdapServerError {message: format!("An error occured, contact admins: {}", e)} }) })?; Ok(()) } pub async fn get_ssh_keys(&self, username: String) -> Result>, Error> { let mut ldap = self.ldap_pool.get().await.unwrap(); let search_res = ldap.search(&self.config.basedn, ldap3::Scope::Subtree, format!("(uid={})", username).as_str(), vec!["ldapPublicKey"]) .await.map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; let (result_entry, _ldap_res) = search_res.success() .map_err(|e| {Error::LdapServerError {message: format!("Error: {}", e)}})?; if result_entry.is_empty() { return Err(Error::LdapServerError {message: format!("Error contact admins: dn shouldn't be empty")}); } let records = SearchEntry::construct(result_entry.first().unwrap().to_owned()); let ssh_keys: Vec> = match records.attrs.get("sshPublicKey") { Some(keys) => { let mut keys_res = Vec::new(); let key_name_re = Regex::new(r"\S+").unwrap(); for key in keys { let key_split: Vec = key_name_re.captures_iter(&key).collect(); let key_name: String; if key_split.len() < 3 { key_name = String::from("unamed key"); } else { key_name = key_split[2][0].to_string(); } keys_res.push(HashMap::from([(key_name, key.to_string())])); } keys_res }, None => vec![HashMap::new()], }; Ok(ssh_keys) } pub async fn manage_ssh_key(&self, username: String, ssh_key: String, op: ManageSSHOps) -> Result<(), Error> { let mut ldap = self.ldap_pool.get().await.unwrap(); let mods: Vec>; match op { ManageSSHOps::ADD => { mods = vec![Mod::Add("sshPublicKey", HashSet::from([ssh_key.as_str()]))]; }, ManageSSHOps::DEL => { mods = vec![Mod::Delete("sshPublicKey", HashSet::from([ssh_key.as_str()]))]; }, } let res = ldap .modify(format!("uid={},{}", username, self.config.basedn).as_str(), mods).await; if let Err(e) = res { return Err(Error::LdapServerError { message: format!("An error occured, contact admins: {}", e)}); } match res.unwrap().success() { Ok(_) => Ok(()), Err(e) => Err(Error::LdapServerError { message: format!("An error occured, contact admins: {}", e)}), } } }