ldap_backend/src/methods/account_update.rs

154 lines
4.5 KiB
Rust

use crate::{methods::account_new::email::get_wolves_mail, update_group, Config, State};
use ldap3::{exop::PasswordModify, LdapConn, Mod, Scope, SearchEntry};
use sqlx::{Pool, Sqlite};
use std::collections::{HashMap, HashSet};
use tide::{
prelude::{json, Deserialize, Serialize},
Request,
};
#[derive(Debug, Deserialize)]
pub struct LdapUpdate {
user: String,
pass: String,
field: String,
value: String,
}
#[derive(Debug, Serialize)]
pub struct ModifyResult {
mail: Option<String>,
#[serde(rename = "sshPublicKey")]
ssh_public_key: Option<String>,
cn: Option<String>,
#[serde(rename = "skDiscord")]
sk_discord: Option<String>,
}
/// Handles updating a single field with the users own password
pub async fn submit(mut req: Request<State>) -> tide::Result {
let LdapUpdate {
user,
pass,
field,
value,
} = req.body_json().await?;
// check that any mail is not using @skynet.ie
if field == "mail" && value.trim().ends_with("@skynet.ie") {
return Ok(json!({"result": "error", "error": "Skynet email not valid contact address"}).into());
}
let config = &req.state().config;
let db = &req.state().db;
// 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;
let mut is_skynet_user = false;
// get the users current password hash
let (rs, _res) = ldap.search(&dn, Scope::Base, "(objectClass=*)", vec!["userPassword", "memberOf"])?.success()?;
if !rs.is_empty() {
let tmp = SearchEntry::construct(rs[0].clone());
if tmp.attrs.contains_key("userPassword") && !tmp.attrs["userPassword"].is_empty() && tmp.attrs["userPassword"][0].starts_with("{SSHA512}") {
pw_keep_same = true;
}
if tmp.attrs.contains_key("memberOf") {
for group in tmp.attrs["memberOf"].clone() {
if group.contains("skynet-users") {
is_skynet_user = true;
}
}
}
}
// check if the password field itself is being updated
let pass_new = if &field != "userPassword" {
if !is_skynet_user && &field == "mail" {
activate_group(db, config, &user, &value).await;
}
// if password is not being updated then just update the required field
let mods = vec![
// the value we are updating
Mod::Replace(field, HashSet::from([value])),
];
ldap.modify(&dn, mods)?.success()?;
// pass back the "old" and "new" passwords
// using this means we can create teh vars without them needing to be mutable
pass.clone()
} else {
// password is going to be updated, even if the old value is not starting with "{SSHA512}"
pw_keep_same = false;
value
};
// changing teh password because of an explicit request or upgrading teh security.
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),
new_pass: Some(&pass_new),
};
ldap.extended(tmp)?.success()?;
};
let result = get_result(&mut ldap, &dn);
ldap.unbind()?;
Ok(json!({"result": "success", "success": result}).into())
}
fn get_result(ldap: &mut LdapConn, dn: &str) -> ModifyResult {
let mut result = ModifyResult {
mail: None,
ssh_public_key: None,
cn: None,
sk_discord: None,
};
if let Ok(temp) = ldap.search(dn, Scope::Base, "(objectClass=*)", vec!["mail", "sshPublicKey", "cn", "skDiscord"]) {
if let Ok((rs, _res)) = temp.success() {
if !rs.is_empty() {
let tmp = SearchEntry::construct(rs[0].clone());
result.mail = get_result_values(&tmp.attrs, "mail");
result.ssh_public_key = get_result_values(&tmp.attrs, "sshPublicKey");
result.cn = get_result_values(&tmp.attrs, "cn");
result.sk_discord = get_result_values(&tmp.attrs, "skDiscord");
}
}
}
result
}
fn get_result_values(attrs: &HashMap<String, Vec<String>>, field: &str) -> Option<String> {
if let Some(field) = attrs.get(field) {
if !field.is_empty() {
return Some(field[0].clone());
}
}
None
}
async fn activate_group(db: &Pool<Sqlite>, config: &Config, user: &str, mail: &str) {
// check if user has this mail in teh wolves db
if !get_wolves_mail(db, mail).await.is_empty() {
// if so then activate
if let Err(e) = update_group(config, "skynet-users", &vec![user.to_string()], false).await {
println!("Couldnt add {} to skynet-users: {:?}", user, e)
}
}
}