bitwarden_rs_ldap/src/main.rs

181 lines
5.6 KiB
Rust
Raw Normal View History

extern crate anyhow;
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::thread::sleep;
use std::time::Duration;
use anyhow::Context as _;
use anyhow::Error as AnyError;
use anyhow::Result;
use ldap3::{DerefAliases, LdapConn, LdapConnSettings, Scope, SearchEntry, SearchOptions};
2019-03-29 18:18:56 +00:00
mod config;
2021-05-07 19:55:29 +00:00
mod vw_admin;
2019-03-29 18:18:56 +00:00
fn main() {
let config = config::Config::from_file();
2021-05-07 19:55:29 +00:00
let mut client = vw_admin::Client::new(
config.get_vaultwarden_url(),
config.get_vaultwarden_admin_token(),
config.get_vaultwarden_root_cert_file(),
);
invite_users(&config, &mut client, config.get_ldap_sync_loop())
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 vw_admin::Client, start_loop: bool) {
2019-04-11 19:01:06 +00:00
if start_loop {
start_sync_loop(config, client).expect("Failed to start invite sync loop");
2019-04-11 19:01:06 +00:00
} else {
invite_from_ldap(config, client).expect("Failed to invite users");
2019-03-29 18:18:56 +00:00
}
2019-04-11 19:01:06 +00:00
}
/// Creates set of email addresses for users that already exist in Bitwarden
fn get_existing_users(client: &mut vw_admin::Client) -> Result<HashSet<String>, AnyError> {
let all_users = client
.users()
.context("Could not get list of existing users from server")?;
2019-04-11 19:01:06 +00:00
let mut user_emails = HashSet::with_capacity(all_users.len());
for user in all_users {
user_emails.insert(user.get_email().to_lowercase());
if user.is_disabled() {
2019-04-12 23:45:23 +00:00
println!(
"Existing disabled user found with email: {}",
user.get_email()
);
} else {
2019-04-12 23:45:23 +00:00
println!(
"Existing user or invite found with email: {}",
user.get_email()
);
}
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,
no_tls_verify: bool,
2020-12-27 15:50:10 +00:00
starttls: bool,
) -> Result<LdapConn, AnyError> {
2020-12-27 15:50:10 +00:00
let settings = LdapConnSettings::new()
.set_starttls(starttls)
.set_no_tls_verify(no_tls_verify);
let mut ldap = LdapConn::with_settings(settings, ldap_url.as_str())
.context("Failed to connect to LDAP server")?;
ldap.simple_bind(bind_dn.as_str(), bind_pw.as_str())
.context("Could not bind to LDAP server")?;
2019-03-29 18:18:56 +00:00
Ok(ldap)
}
/// Retrieves search results from ldap
fn search_entries(config: &config::Config) -> Result<Vec<SearchEntry>, AnyError> {
let mut ldap = ldap_client(
2019-03-29 18:18:56 +00:00
config.get_ldap_url(),
config.get_ldap_bind_dn(),
config.get_ldap_bind_password(),
config.get_ldap_no_tls_verify(),
2020-12-27 15:50:10 +00:00
config.get_ldap_starttls(),
)
.context("LDAP client initialization failed")?;
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()];
// Something something error handling
let (results, _res) = ldap
2019-03-29 18:18:56 +00:00
.with_search_options(SearchOptions::new().deref(DerefAliases::Always))
.search(
2021-09-21 00:01:52 +00:00
config.get_ldap_search_base_dn().as_str(),
2019-03-29 18:18:56 +00:00
Scope::Subtree,
2021-09-21 00:01:52 +00:00
config.get_ldap_search_filter().as_str(),
2019-03-29 18:18:56 +00:00
fields,
)
.context("LDAP search failure")?
.success()
.context("LDAP search usucessful")?;
2019-03-29 18:18:56 +00:00
// 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
fn invite_from_ldap(
config: &config::Config,
2021-05-07 19:55:29 +00:00
client: &mut vw_admin::Client,
) -> Result<(), AnyError> {
let existing_users =
get_existing_users(client).context("Failed to get existing users from server")?;
let mail_field = config.get_ldap_mail_field();
let mut num_users = 0;
for ldap_user in search_entries(config)? {
//
// 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()))
{
if existing_users.contains(&user_email.to_lowercase()) {
println!("User with email already exists: {}", user_email);
} else {
println!("Try to invite user: {}", user_email);
client
.invite(user_email)
.context(format!("Failed to invite user {}", user_email))?;
num_users += 1;
}
} else {
match ldap_user.attrs.get("uid").and_then(|l| l.first()) {
Some(user_uid) => println!(
"Warning: Email field, {:?}, not found on user {}",
mail_field, user_uid
),
None => println!("Warning: Email field, {:?}, not found on user", mail_field),
}
2019-03-29 18:18:56 +00:00
}
}
// Maybe think about returning this value for some other use
println!("Sent invites to {} user(s).", num_users);
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 vw_admin::Client) -> Result<(), AnyError> {
2019-03-29 22:40:26 +00:00
let interval = Duration::from_secs(config.get_ldap_sync_interval_seconds());
let mut fail_count = 0;
let fail_limit = 5;
2019-03-29 22:40:26 +00:00
loop {
if let Err(err) = invite_from_ldap(config, client) {
println!(
"Error inviting users from ldap. Count {}: {:?}",
fail_count, err
);
fail_count += 1;
if fail_count > fail_limit {
return Err(err);
}
} else {
fail_count = 0
}
2019-03-29 22:40:26 +00:00
sleep(interval);
}
}