use ldap3::{LdapConn, Mod, Scope, SearchEntry}; use ldap3::exop::{PasswordModify, PasswordModifyResp}; use std::collections::HashSet; // for teh webserver use dotenv::dotenv; use skynet_ldap_server::db_init; use sqlx::{Pool, Sqlite}; use std::env; use tide::prelude::{json, Deserialize}; use tide::Request; #[derive(Clone)] struct State { db: Pool, config: Config, } #[async_std::main] async fn main() -> tide::Result<()> { let config = get_config(); let db = db_init(&config.database).await?; let host_port = config.host_port.clone(); tide::log::start(); let state = State { db, config, }; let mut app = tide::with_state(state); app.at("/ldap/update").post(post_update_ldap); app.listen(host_port).await?; Ok(()) } #[derive(Debug, Clone)] struct Config { ldap_host: String, database: String, host_port: String, } fn get_config() -> Config { dotenv().ok(); // reasonable defaults let mut config = Config { ldap_host: "".to_string(), database: "database.db".to_string(), host_port: "127.0.0.1:8087".to_string(), }; if let Ok(x) = env::var("LDAP_HOST") { config.ldap_host = x.trim().to_string(); } if let Ok(x) = env::var("DATABASE") { config.database = x.trim().to_string(); } if let Ok(x) = env::var("HOST_PORT") { config.host_port = x.trim().to_string(); } config } #[derive(Debug, Deserialize)] struct LdapUpdate { user: String, pass: String, field: String, value: String, } async fn post_update_ldap(mut req: Request) -> tide::Result { let LdapUpdate { user, pass, field, value, } = req.body_json().await?; let config = &req.state().config; // easier to give each request its own connection let mut ldap = LdapConn::new(&config.ldap_host)?; let dn = format!("uid={},ou=users,dc=skynet,dc=ie", user); ldap.simple_bind(&dn, &pass)?.success()?; // always assume insecure let mut pw_keep_same = false; // get the users current password hash let (rs, _res) = ldap.search(&dn, Scope::Base, "(objectClass=*)", vec!["userPassword"])?.success()?; if !rs.is_empty() { let tmp = SearchEntry::construct(rs[0].clone()); if !tmp.attrs["userPassword"].is_empty() && tmp.attrs["userPassword"][0].starts_with("{SSHA512}") { pw_keep_same = true; } } // check if the password field itself is being updated let (pass_old, pass_new) = if &field != "userPassword" { // if password is not being updated then just update the required field let mods = vec![ Mod::Replace(field, HashSet::from([value])) ]; ldap.modify(&dn, mods)?.success()?; // pass back the "old" and "new" passwords (pass.clone(), pass.clone()) } else { // password is going to be updated, even if the old value is not starting with "{SSHA512}" pw_keep_same = false; (pass.clone(), value.clone()) }; if !pw_keep_same { // really easy to update password once ye know how let tmp = PasswordModify{ // none as we are staying on the same connection user_id: None, old_pass: Some(&pass_old), new_pass: Some(&pass_new), }; ldap.extended(tmp)?.success()?; }; ldap.unbind()?; Ok(json!({"result": "success"}).into()) } /* Create new account 1. Check if ID is available 2. Ask user to fill in: * uid * First Name * Surname Name * Wolves email 3. Email + link is sent to wolves email * only if its paid up and it hasn't been used before 4. Ldap entry created 5. Email with initial pw is sent to user 6. Account added to skynet-users (they are paid up) */ /* Join existing account to wolves related to above */ /* Password reset via email */ /* script to pull in all active members from wolves update the groups check if there are any pending signups */