2019-03-29 18:18:56 +00:00
|
|
|
extern crate ldap3;
|
|
|
|
|
2019-04-11 19:01:06 +00:00
|
|
|
use std::collections::HashSet;
|
2019-03-29 18:18:56 +00:00
|
|
|
use std::error::Error;
|
|
|
|
use std::thread::sleep;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
2020-07-10 03:14:12 +00:00
|
|
|
use ldap3::{DerefAliases, LdapConn, LdapConnSettings, Scope, SearchEntry, SearchOptions};
|
2019-03-29 18:18:56 +00:00
|
|
|
|
2019-03-29 22:18:25 +00:00
|
|
|
mod bw_admin;
|
2019-03-29 18:18:56 +00:00
|
|
|
mod config;
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let config = config::Config::from_file();
|
2019-03-29 22:18:25 +00:00
|
|
|
let mut client = bw_admin::Client::new(
|
|
|
|
config.get_bitwarden_url().clone(),
|
|
|
|
config.get_bitwarden_admin_token().clone(),
|
2020-07-10 03:14:12 +00:00
|
|
|
config.get_bitwarden_root_cert_file().clone(),
|
2019-03-29 22:18:25 +00:00
|
|
|
);
|
|
|
|
|
2019-04-11 23:20:00 +00:00
|
|
|
if let Err(e) = invite_users(&config, &mut client, config.get_ldap_sync_loop()) {
|
2019-04-11 19:01:06 +00:00
|
|
|
panic!("{}", e);
|
2019-04-05 18:49:32 +00:00
|
|
|
}
|
2019-04-11 19:01:06 +00:00
|
|
|
}
|
2019-04-05 18:49:32 +00:00
|
|
|
|
2019-04-11 19:01:06 +00:00
|
|
|
/// Invites new users to Bitwarden from LDAP
|
|
|
|
fn invite_users(
|
|
|
|
config: &config::Config,
|
|
|
|
client: &mut bw_admin::Client,
|
|
|
|
start_loop: bool,
|
2020-03-05 21:07:14 +00:00
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2019-04-11 19:01:06 +00:00
|
|
|
if start_loop {
|
2019-04-11 23:20:00 +00:00
|
|
|
start_sync_loop(config, client)?;
|
2019-04-11 19:01:06 +00:00
|
|
|
} else {
|
2019-04-11 23:20:00 +00:00
|
|
|
invite_from_ldap(config, client)?;
|
2019-03-29 18:18:56 +00:00
|
|
|
}
|
|
|
|
|
2019-04-11 19:01:06 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates set of email addresses for users that already exist in Bitwarden
|
2020-03-05 21:07:14 +00:00
|
|
|
fn get_existing_users(client: &mut bw_admin::Client) -> Result<HashSet<String>, Box<dyn Error>> {
|
2019-04-12 23:40:50 +00:00
|
|
|
let all_users = client.users()?;
|
2019-04-11 19:01:06 +00:00
|
|
|
let mut user_emails = HashSet::with_capacity(all_users.len());
|
2019-04-11 23:20:00 +00:00
|
|
|
for user in all_users {
|
2020-03-05 21:07:26 +00:00
|
|
|
user_emails.insert(user.get_email().to_lowercase());
|
2019-04-12 23:40:50 +00:00
|
|
|
if user.is_disabled() {
|
2019-04-12 23:45:23 +00:00
|
|
|
println!(
|
|
|
|
"Existing disabled user found with email: {}",
|
|
|
|
user.get_email()
|
|
|
|
);
|
2019-04-12 23:40:50 +00:00
|
|
|
} else {
|
2019-04-12 23:45:23 +00:00
|
|
|
println!(
|
|
|
|
"Existing user or invite found with email: {}",
|
|
|
|
user.get_email()
|
|
|
|
);
|
2019-04-11 23:20:00 +00:00
|
|
|
}
|
2019-03-29 22:40:26 +00:00
|
|
|
}
|
2019-04-11 19:01:06 +00:00
|
|
|
|
|
|
|
Ok(user_emails)
|
2019-03-29 18:18:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates an LDAP connection, authenticating if necessary
|
2020-03-05 21:07:14 +00:00
|
|
|
fn ldap_client(
|
|
|
|
ldap_url: String,
|
|
|
|
bind_dn: String,
|
|
|
|
bind_pw: String,
|
2020-07-10 03:14:12 +00:00
|
|
|
no_tls_verify: bool,
|
2020-03-05 21:07:14 +00:00
|
|
|
) -> Result<LdapConn, Box<dyn Error>> {
|
2020-07-10 03:14:12 +00:00
|
|
|
let settings = LdapConnSettings::new().set_no_tls_verify(no_tls_verify);
|
2020-07-09 06:24:36 +00:00
|
|
|
let ldap = LdapConn::with_settings(settings, ldap_url.as_str())?;
|
2019-03-29 18:18:56 +00:00
|
|
|
match ldap.simple_bind(bind_dn.as_str(), bind_pw.as_str()) {
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(ldap)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieves search results from ldap
|
2020-03-05 21:07:14 +00:00
|
|
|
fn search_entries(config: &config::Config) -> Result<Vec<SearchEntry>, Box<dyn Error>> {
|
2019-03-29 18:18:56 +00:00
|
|
|
let ldap = ldap_client(
|
|
|
|
config.get_ldap_url(),
|
|
|
|
config.get_ldap_bind_dn(),
|
|
|
|
config.get_ldap_bind_password(),
|
2020-07-10 03:14:12 +00:00
|
|
|
config.get_ldap_no_tls_verify(),
|
2019-03-29 18:18:56 +00:00
|
|
|
);
|
|
|
|
|
2019-04-11 23:20:00 +00:00
|
|
|
if ldap.is_err() {
|
2019-04-12 00:07:59 +00:00
|
|
|
println!("Error: Could not bind to ldap server");
|
2019-04-11 23:20:00 +00:00
|
|
|
}
|
|
|
|
|
2019-03-29 18:18:56 +00:00
|
|
|
let mail_field = config.get_ldap_mail_field();
|
|
|
|
let fields = vec!["uid", "givenname", "sn", "cn", mail_field.as_str()];
|
|
|
|
|
|
|
|
// TODO: Something something error handling
|
|
|
|
let (results, _res) = ldap?
|
|
|
|
.with_search_options(SearchOptions::new().deref(DerefAliases::Always))
|
|
|
|
.search(
|
|
|
|
&config.get_ldap_search_base_dn().as_str(),
|
|
|
|
Scope::Subtree,
|
|
|
|
&config.get_ldap_search_filter().as_str(),
|
|
|
|
fields,
|
|
|
|
)?
|
|
|
|
.success()?;
|
|
|
|
|
|
|
|
// Build list of entries
|
|
|
|
let mut entries = Vec::new();
|
|
|
|
for result in results {
|
|
|
|
entries.push(SearchEntry::construct(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(entries)
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Invite all LDAP users to Bitwarden
|
2019-03-29 22:18:25 +00:00
|
|
|
fn invite_from_ldap(
|
|
|
|
config: &config::Config,
|
|
|
|
client: &mut bw_admin::Client,
|
2020-03-05 21:07:14 +00:00
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2019-04-12 00:07:59 +00:00
|
|
|
match get_existing_users(client) {
|
|
|
|
Ok(existing_users) => {
|
|
|
|
let mail_field = config.get_ldap_mail_field();
|
|
|
|
let mut num_users = 0;
|
|
|
|
for ldap_user in search_entries(config)? {
|
2019-10-02 20:02:51 +00:00
|
|
|
// Safely get first email from list of emails in field
|
|
|
|
if let Some(user_email) = ldap_user
|
|
|
|
.attrs
|
|
|
|
.get(mail_field.as_str())
|
|
|
|
.and_then(|l| (l.first()))
|
|
|
|
{
|
2020-03-05 21:07:26 +00:00
|
|
|
if existing_users.contains(&user_email.to_lowercase()) {
|
2019-04-12 00:07:59 +00:00
|
|
|
println!("User with email already exists: {}", user_email);
|
|
|
|
} else {
|
|
|
|
println!("Try to invite user: {}", user_email);
|
2019-04-12 23:45:23 +00:00
|
|
|
// TODO: Validate response
|
|
|
|
let _response = client.invite(user_email);
|
2019-04-12 00:07:59 +00:00
|
|
|
num_users = num_users + 1;
|
2019-04-12 23:42:31 +00:00
|
|
|
// println!("Invite response: {:?}", response);
|
2019-04-12 00:07:59 +00:00
|
|
|
}
|
2019-10-02 20:02:51 +00:00
|
|
|
} else {
|
|
|
|
println!("Warning: Email field, {:?}, not found on user", mail_field);
|
2019-04-12 00:07:59 +00:00
|
|
|
}
|
2019-04-11 23:20:00 +00:00
|
|
|
}
|
2019-04-12 00:07:59 +00:00
|
|
|
|
|
|
|
// Maybe think about returning this value for some other use
|
|
|
|
println!("Sent invites to {} user(s).", num_users);
|
2019-04-12 23:45:23 +00:00
|
|
|
}
|
2019-04-12 00:07:59 +00:00
|
|
|
Err(e) => {
|
|
|
|
println!("Error: Failed to get existing users from Bitwarden");
|
|
|
|
return Err(e);
|
2019-03-29 18:18:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Begin sync loop to invite LDAP users to Bitwarden
|
|
|
|
fn start_sync_loop(
|
|
|
|
config: &config::Config,
|
|
|
|
client: &mut bw_admin::Client,
|
2020-03-05 21:07:14 +00:00
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2019-03-29 22:40:26 +00:00
|
|
|
let interval = Duration::from_secs(config.get_ldap_sync_interval_seconds());
|
|
|
|
loop {
|
|
|
|
invite_from_ldap(config, client)?;
|
|
|
|
sleep(interval);
|
|
|
|
}
|
|
|
|
}
|