use chrono::{Datelike, Utc}; use dotenv::dotenv; use ldap3::{LdapConn, Mod, Scope, SearchEntry}; use skynet_ldap_backend::{get_config, Config}; use std::collections::{HashMap, HashSet}; use std::env; use std::error::Error; #[async_std::main] async fn main() -> tide::Result<()> { let config = get_config(); update_users(&config).await?; update_admin(&config).await?; update_committee(&config).await?; Ok(()) } async fn update_users(config: &Config) -> tide::Result<()> { let mut users_tmp = HashSet::new(); // default user to ensure group is never empty users_tmp.insert(String::from("compsoc")); if let Ok(x) = env::var("USERS_LIFETIME") { for user in x.split(',').collect::>() { users_tmp.insert(user.to_string()); } } /* pull in data from wolves (csv or api (hopefully api) pull entire ldap data for every valid user in wolves match to ldap add to users */ println!("{:#?}", users_tmp); // pull from wolves csv for user in from_csv(config).await.unwrap_or_default() { users_tmp.insert(user); } // sorting makes it easier/faster if let Ok(x) = env::var("USERS_BANNED") { for user in x.split(',').collect::>() { users_tmp.remove(user); } } println!("{:#?}", users_tmp); // easier to work with Strings above but easier to work with &str below let users: Vec<&str> = users_tmp.iter().map(|s| &**s).collect(); update_group(config, "skynet-users", &users, true).await?; Ok(()) } fn uid_to_dn(uid: &str) -> String { format!("uid={},ou=users,dc=skynet,dc=ie", uid) } async fn update_admin(config: &Config) -> tide::Result<()> { dotenv().ok(); // read from teh env if let Ok(x) = env::var("USERS_ADMIN") { let users = x.split(',').collect::>(); update_group(config, "skynet-admins", &users, true).await?; // admins automatically get added as users update_group(config, "skynet-users", &users, false).await?; } Ok(()) } async fn update_committee(config: &Config) -> tide::Result<()> { dotenv().ok(); // read from teh env if let Ok(x) = env::var("USERS_COMMITTEE") { let users = x.split(',').collect::>(); update_group(config, "skynet-committee", &users, true).await?; // admins automatically get added as users update_group(config, "skynet-users", &users, false).await?; } Ok(()) } async fn update_group(config: &Config, group: &str, users: &[&str], replace: bool) -> tide::Result<()> { if users.is_empty() { return Ok(()); } let mut ldap = LdapConn::new(&config.ldap_host)?; // use the admin account ldap.simple_bind(&config.ldap_admin, &config.ldap_admin_pw)?.success()?; let dn = format!("cn={},ou=groups,dc=skynet,dc=ie", group); let members = users.iter().map(|uid| uid_to_dn(uid)).collect(); let mods = if replace { vec![Mod::Replace("member".to_string(), members)] } else { vec![Mod::Add("member".to_string(), members)] }; if let Err(x) = ldap.modify(&dn, mods) { println!("{:?}", x); } let dn_linux = format!("cn={}-linux,ou=groups,dc=skynet,dc=ie", group); let members_linux = users.iter().map(|uid| uid.to_string()).collect(); let mods = if replace { vec![Mod::Replace("memberUid".to_string(), members_linux)] } else { vec![Mod::Add("memberUid".to_string(), members_linux)] }; if let Err(x) = ldap.modify(&dn_linux, mods) { println!("{:?}", x); }; // tidy up ldap.unbind()?; Ok(()) } async fn ldap_get_accounts(config: &Config) -> Result<(HashMap, HashMap), Box> { // connect to ldap let mut ldap = LdapConn::new(&config.ldap_host)?; ldap.simple_bind(&config.ldap_admin, &config.ldap_admin_pw)?.success()?; let mut uid_idstudent: HashMap = HashMap::new(); let mut uid_email: HashMap = HashMap::new(); let (rs, _res) = ldap .search("ou=users,dc=skynet,dc=ie", Scope::OneLevel, "(objectClass=*)", vec!["uid", "mail", "skID", "skSecure"]) .unwrap() .success() .unwrap(); for entry in rs { let tmp = SearchEntry::construct(entry); // skSecure is a standin for teh password, only 1 if the password is SSHA512 if !tmp.attrs.contains_key("skSecure") { continue; } if tmp.attrs["skSecure"].is_empty() { continue; } // make sure there is an id; let uid = if !tmp.attrs["uid"].is_empty() { tmp.attrs["uid"][0].clone() } else { continue; }; if !tmp.attrs["skID"].is_empty() { let skid = tmp.attrs["skID"][0].clone(); if &skid != "00000000" { uid_idstudent.insert(skid, uid.clone()); } } if !tmp.attrs["mail"].is_empty() { let mail = tmp.attrs["mail"][0].clone(); if &mail != "nomail@skynet.ie" { uid_email.insert(mail, uid.clone()); } } } ldap.unbind()?; Ok((uid_idstudent, uid_email)) } async fn from_csv(config: &Config) -> Result, Box> { let mut uids = HashSet::new(); let (uid_idstudent, uid_email) = ldap_get_accounts(config).await?; let records = read_csv()?; let now = Utc::now(); let today = format!("{}-{:02}-{:02}", now.year(), now.month(), now.day()); for record in records { // only import users if it is actually active. if record.expiry < today { continue; } if let Some(uid) = uid_email.get(&record.email) { uids.insert(uid.clone()); } if let Some(uid) = uid_idstudent.get(&record.id_student) { uids.insert(uid.clone()); } } Ok(uids) } #[derive(Debug, serde::Deserialize)] struct Record { // #[serde(rename = "MemID")] // id_wolves: String, #[serde(rename = "Student Num")] id_student: String, #[serde(rename = "Contact Email")] email: String, #[serde(rename = "Expiry")] expiry: String, } fn read_csv() -> Result, Box> { let mut records: Vec = vec![]; if let Ok(mut rdr) = csv::Reader::from_path("tmp.csv") { for result in rdr.deserialize() { // Notice that we need to provide a type hint for automatic // deserialization. let record: Record = result?; records.push(record); } } Ok(records) }