From ba1705c7081d4c5330b5eb514289e5965ce91473 Mon Sep 17 00:00:00 2001 From: ViViDboarder Date: Fri, 29 Mar 2019 15:18:25 -0700 Subject: [PATCH] WIP: Add bw_rs client For some reason, we're unable to get the cookies from the reqwest result. --- Cargo.toml | 2 + src/bw_admin.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 16 +++++++ src/main.rs | 43 ++++++++++++----- 4 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 src/bw_admin.rs diff --git a/Cargo.toml b/Cargo.toml index 7131a89..5ac9b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ edition = "2018" ldap3 = "0.6" serde = { version = "1.0", features = ["derive"] } toml = "0.5" +reqwest = "0.9" +serde_json = "1.0" diff --git a/src/bw_admin.rs b/src/bw_admin.rs new file mode 100644 index 0000000..138a8b8 --- /dev/null +++ b/src/bw_admin.rs @@ -0,0 +1,123 @@ +extern crate reqwest; +extern crate serde_json; + +use reqwest::Response; +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +const COOKIE_LIFESPAN: Duration = Duration::from_secs(20 * 60); + +pub struct Client { + url: String, + admin_token: String, + cookie: Option, + cookie_created: Option, +} + +impl Client { + pub fn new(url: String, admin_token: String) -> Client { + Client { + url, + admin_token, + cookie: None, + cookie_created: None, + } + } + + fn auth(&mut self) -> Response { + let cookie_created = Instant::now(); + let result = reqwest::Client::new() + .post(format!("{}{}", &self.url, "/admin/").as_str()) + .form(&[("token", &self.admin_token)]) + .send() + .unwrap_or_else(|e| { + panic!("Could not authenticate with {}. {:?}", &self.url, e); + }); + + // TODO: Handle error statuses + + println!("Auth headers: {:?}", result.headers()); + + if let Some(cookie) = result.headers().get(reqwest::header::SET_COOKIE) { + self.cookie = cookie.to_str().map(|s| String::from(s)).ok(); + self.cookie_created = Some(cookie_created); + } else { + panic!("No cookie to set!") + } + + result + } + + fn ensure_auth(&mut self) { + match &self.cookie { + Some(_) => { + if self + .cookie_created + .map_or(true, |created| (created.elapsed() >= COOKIE_LIFESPAN)) + { + let response = self.auth(); + println!("Auth response: {:?}", response); + } + } + None => { + let response = self.auth(); + println!("Auth response: {:?}", response); + } + }; + // TODO: handle errors + } + + fn get(&mut self, path: &str) -> Response { + self.ensure_auth(); + + match &self.cookie { + None => { + panic!("We haven't authenticated. Must be an error"); + } + Some(cookie) => { + let url = format!("{}/admin{}", &self.url, path); + let request = reqwest::Client::new() + .get(url.as_str()) + .header(reqwest::header::COOKIE, cookie.clone()); + let response = request.send().unwrap_or_else(|e| { + panic!("Could not call with {}. {:?}", url, e); + }); + + // TODO: Handle error statuses + + return response; + } + } + } + + fn post(&mut self, path: &str, json: &HashMap) -> Response { + self.ensure_auth(); + + match &self.cookie { + None => { + panic!("We haven't authenticated. Must be an error"); + } + Some(cookie) => { + let url = format!("{}/admin{}", &self.url, path); + let request = reqwest::Client::new() + .post(url.as_str()) + .header("Cookie", cookie.clone()) + .json(&json); + let response = request.send().unwrap_or_else(|e| { + panic!("Could not call with {}. {:?}", url, e); + }); + + // TODO: Handle error statuses + + return response; + } + } + } + + pub fn invite(&mut self, email: &str) -> Response { + let mut json = HashMap::new(); + json.insert("email".to_string(), email.to_string()); + + self.post("/invite", &json) + } +} diff --git a/src/config.rs b/src/config.rs index 5be484b..5b3c12c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,15 +35,23 @@ pub fn read_config() -> Config { #[serde(deny_unknown_fields)] /// Contains all config values for LDAP syncing pub struct Config { + // Bitwarden connection config + bitwarden_url: String, + bitwarden_admin_token: String, + // LDAP Connection config ldap_host: String, ldap_scheme: Option, ldap_ssl: Option, ldap_port: Option, + // LDAP auth config ldap_bind_dn: String, ldap_bind_password: Pass, + // LDAP search config ldap_search_base_dn: String, ldap_search_filter: String, + // LDAP record attributes ldap_mail_field: Option, + // Interval syncing config ldap_sync_interval_seconds: Option, } @@ -53,6 +61,14 @@ impl Config { read_config() } + pub fn get_bitwarden_url(&self) -> String { + self.bitwarden_url.clone() + } + + pub fn get_bitwarden_admin_token(&self) -> String { + self.bitwarden_admin_token.clone() + } + pub fn get_ldap_url(&self) -> String { format!( "{}://{}:{}", diff --git a/src/main.rs b/src/main.rs index 6696716..aa2f59e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,23 +6,35 @@ use std::time::Duration; use ldap3::{DerefAliases, LdapConn, Scope, SearchEntry, SearchOptions}; +mod bw_admin; mod config; fn main() { let config = config::Config::from_file(); + let mut client = bw_admin::Client::new( + config.get_bitwarden_url().clone(), + config.get_bitwarden_admin_token().clone(), + ); + + /* + * let auth_response = client.auth(); + * println!("Auth Response: {:?}", auth_response); + */ match do_search(&config) { Ok(_) => (), Err(e) => println!("{}", e), } - if let Err(e) = invite_from_ldap(&config) { + if let Err(e) = invite_from_ldap(&config, &mut client) { println!("{}", e); } - if let Err(e) = start_sync_loop(&config) { - println!("{}", e); - } + /* + * if let Err(e) = start_sync_loop(&config, %mut client) { + * println!("{}", e); + * } + */ } /// Creates an LDAP connection, authenticating if necessary @@ -80,21 +92,28 @@ fn do_search(config: &config::Config) -> Result<(), Box> { Ok(()) } -fn invite_from_ldap(config: &config::Config) -> Result<(), Box> { +fn invite_from_ldap( + config: &config::Config, + client: &mut bw_admin::Client, +) -> Result<(), Box> { let mail_field = config.get_ldap_mail_field(); for ldap_user in search_entries(config)? { if let Some(user_email) = ldap_user.attrs[mail_field.as_str()].first() { println!("Try to invite user: {}", user_email); + let response = client.invite(user_email); + println!("Invite response: {:?}", response); } } Ok(()) } -fn start_sync_loop(config: &config::Config) -> Result<(), Box> { - let interval = Duration::from_secs(config.get_ldap_sync_interval_seconds()); - loop { - invite_from_ldap(config)?; - sleep(interval); - } -} +/* + * fn start_sync_loop(config: &config::Config) -> Result<(), Box> { + * let interval = Duration::from_secs(config.get_ldap_sync_interval_seconds()); + * loop { + * invite_from_ldap(config)?; + * sleep(interval); + * } + * } + */