2023-05-25 23:51:36 +00:00
|
|
|
use ldap3::{LdapConn, Mod, Scope, SearchEntry};
|
2023-05-25 21:02:42 +00:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
2023-05-25 23:51:36 +00:00
|
|
|
use base64::{engine::general_purpose, Engine as _};
|
|
|
|
use crypto::{digest::Digest, sha2::Sha512};
|
2023-05-25 21:02:42 +00:00
|
|
|
|
2023-05-25 23:02:12 +00:00
|
|
|
// for teh webserver
|
2023-05-25 23:51:36 +00:00
|
|
|
use dotenv::dotenv;
|
|
|
|
use skynet_ldap_server::db_init;
|
2023-05-25 23:02:12 +00:00
|
|
|
use sqlx::{Pool, Sqlite};
|
|
|
|
use std::env;
|
2023-05-25 23:58:18 +00:00
|
|
|
use tide::prelude::{json, Deserialize};
|
|
|
|
use tide::Request;
|
2023-05-25 23:02:12 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct State {
|
|
|
|
db: Pool<Sqlite>,
|
|
|
|
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);
|
|
|
|
|
2023-05-25 23:38:50 +00:00
|
|
|
app.at("/ldap/update").post(post_update_ldap);
|
2023-05-25 23:02:12 +00:00
|
|
|
|
|
|
|
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") {
|
2023-05-25 23:38:50 +00:00
|
|
|
config.ldap_host = x.trim().to_string();
|
2023-05-25 23:02:12 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-05-25 21:02:42 +00:00
|
|
|
//https://stackoverflow.com/a/44532957
|
|
|
|
pub fn hex_to_base64(hex: &str) -> String {
|
|
|
|
// Make vector of bytes from octets
|
|
|
|
let mut bytes = Vec::new();
|
2023-05-25 23:51:36 +00:00
|
|
|
for i in 0..(hex.len() / 2) {
|
|
|
|
let res = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16);
|
2023-05-25 21:02:42 +00:00
|
|
|
match res {
|
|
|
|
Ok(v) => bytes.push(v),
|
|
|
|
Err(e) => println!("Problem with hex: {}", e),
|
|
|
|
};
|
2023-05-25 23:51:36 +00:00
|
|
|
}
|
2023-05-25 21:02:42 +00:00
|
|
|
|
|
|
|
general_purpose::STANDARD.encode(&bytes) // now convert from Vec<u8> to b64-encoded String
|
|
|
|
}
|
|
|
|
|
2023-05-25 23:38:50 +00:00
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
struct LdapUpdate {
|
|
|
|
user: String,
|
|
|
|
pass: String,
|
|
|
|
field: String,
|
2023-05-25 23:51:36 +00:00
|
|
|
value: String,
|
2023-05-25 23:38:50 +00:00
|
|
|
}
|
2023-05-25 23:02:12 +00:00
|
|
|
async fn post_update_ldap(mut req: Request<State>) -> tide::Result {
|
2023-05-25 23:51:36 +00:00
|
|
|
let LdapUpdate {
|
|
|
|
user,
|
|
|
|
pass,
|
|
|
|
field,
|
|
|
|
value,
|
|
|
|
} = req.body_json().await?;
|
2023-05-25 23:38:50 +00:00
|
|
|
|
|
|
|
let config = &req.state().config;
|
2023-05-25 23:02:12 +00:00
|
|
|
|
2023-05-25 23:38:50 +00:00
|
|
|
// easier to give each request its own connection
|
|
|
|
let mut ldap = LdapConn::new(&config.ldap_host)?;
|
2023-05-25 23:02:12 +00:00
|
|
|
|
2023-05-25 21:02:42 +00:00
|
|
|
let dn = format!("uid={},ou=users,dc=skynet,dc=ie", user);
|
2023-05-25 23:38:50 +00:00
|
|
|
ldap.simple_bind(&dn, &pass)?.success()?;
|
2023-05-25 21:02:42 +00:00
|
|
|
|
|
|
|
// always assume insecure
|
2023-05-25 23:45:07 +00:00
|
|
|
let mut pw_keep_same = false;
|
2023-05-25 23:02:12 +00:00
|
|
|
|
2023-05-25 21:02:42 +00:00
|
|
|
// get the users current password hash
|
2023-05-25 23:51:36 +00:00
|
|
|
let (rs, _res) = ldap.search(&dn, Scope::Base, "(objectClass=*)", vec!["userPassword"])?.success()?;
|
2023-05-25 21:02:42 +00:00
|
|
|
if !rs.is_empty() {
|
|
|
|
let tmp = SearchEntry::construct(rs[0].clone());
|
|
|
|
if !tmp.attrs["userPassword"].is_empty() && tmp.attrs["userPassword"][0].starts_with("{SHA512}") {
|
2023-05-25 23:45:07 +00:00
|
|
|
pw_keep_same = true;
|
2023-05-25 21:02:42 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-25 23:51:36 +00:00
|
|
|
|
2023-05-25 23:45:07 +00:00
|
|
|
let mut mods = vec![];
|
2023-05-25 23:51:36 +00:00
|
|
|
|
2023-05-25 23:45:07 +00:00
|
|
|
// check if the password field itself is being updated
|
|
|
|
let pass_new = if &field != "userPassword" {
|
2023-05-25 23:51:36 +00:00
|
|
|
mods.push(Mod::Replace(field, HashSet::from([value])));
|
2023-05-25 23:45:07 +00:00
|
|
|
// retain the older password
|
|
|
|
pass
|
|
|
|
} else {
|
|
|
|
pw_keep_same = false;
|
|
|
|
value
|
|
|
|
};
|
2023-05-25 23:51:36 +00:00
|
|
|
|
2023-05-25 23:45:07 +00:00
|
|
|
if !pw_keep_same {
|
2023-05-25 21:02:42 +00:00
|
|
|
let mut hasher = Sha512::new();
|
|
|
|
|
2023-05-25 23:45:07 +00:00
|
|
|
hasher.input_str(&pass_new);
|
2023-05-25 21:02:42 +00:00
|
|
|
|
|
|
|
// get it as hex string
|
|
|
|
let hex = hasher.result_str();
|
2023-05-25 23:02:12 +00:00
|
|
|
|
2023-05-25 21:02:42 +00:00
|
|
|
// convert it to b64
|
2023-05-25 23:38:50 +00:00
|
|
|
let pass_tmp = format!("{{SHA512}}{}", hex_to_base64(&hex));
|
2023-05-25 21:02:42 +00:00
|
|
|
|
2023-05-25 23:38:50 +00:00
|
|
|
mods.push(Mod::Replace(String::from("userPassword"), HashSet::from([pass_tmp])));
|
2023-05-25 21:02:42 +00:00
|
|
|
};
|
2023-05-25 23:38:50 +00:00
|
|
|
|
|
|
|
ldap.modify(&dn, mods)?.success()?;
|
2023-05-25 23:51:36 +00:00
|
|
|
|
2023-05-25 23:02:12 +00:00
|
|
|
ldap.unbind()?;
|
2023-05-25 23:58:18 +00:00
|
|
|
|
|
|
|
Ok(json!({"result": "success"}).into())
|
2023-05-25 16:01:50 +00:00
|
|
|
}
|
2023-05-26 00:23:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
/** 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
|
|
|
|
|
|
|
|
*/
|