mirror of
https://github.com/ViViDboarder/bitwarden_rs_ldap.git
synced 2024-11-24 04:06:26 +00:00
LDAP querying complete
This commit is contained in:
parent
cd68ef6f46
commit
6d79984a65
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ Cargo.lock
|
|||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
config.toml
|
||||||
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "bitwarden_rs_ldap"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["ViViDboarder <vividboarder@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ldap3 = "0.6"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
toml = "0.5"
|
@ -1,2 +1,2 @@
|
|||||||
# bwrs_ldap_directory
|
# bitwarden_rs_ldap
|
||||||
LDAP directory connector for bitwarden_rs
|
LDAP directory connector for bitwarden_rs
|
||||||
|
29
docker-compose-ldap-server.yml
Normal file
29
docker-compose-ldap-server.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
ldap:
|
||||||
|
image: osixia/openldap
|
||||||
|
ports:
|
||||||
|
- 389:389
|
||||||
|
- 636:636
|
||||||
|
volumes:
|
||||||
|
- /var/lib/ldap
|
||||||
|
- /etc/ldap/slapd.d
|
||||||
|
environment:
|
||||||
|
LDAP_READONLY_USER: 'true'
|
||||||
|
LDAP_READONLY_USER_USERNAME: readonly
|
||||||
|
LDAP_READONLY_USER_PASSWORD: readonly
|
||||||
|
admin:
|
||||||
|
image: osixia/phpldapadmin
|
||||||
|
ports:
|
||||||
|
- 8001:80
|
||||||
|
environment:
|
||||||
|
PHPLDAPADMIN_HTTPS: 'false'
|
||||||
|
PHPLDAPADMIN_LDAP_HOSTS: ldap
|
||||||
|
admin-host:
|
||||||
|
image: osixia/phpldapadmin
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
PHPLDAPADMIN_HTTPS: 'false'
|
||||||
|
PHPLDAPADMIN_LDAP_HOSTS: 0.0.0.0
|
126
src/config.rs
Normal file
126
src/config.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub type Pass = String;
|
||||||
|
|
||||||
|
const CONFIG_PATH_DEFAULT: &str = "config.toml";
|
||||||
|
|
||||||
|
/// Returns config path from envioronment or a provided default value
|
||||||
|
pub fn get_config_path() -> String {
|
||||||
|
match env::var("CONFIG_PATH") {
|
||||||
|
Ok(config_path) => config_path,
|
||||||
|
Err(_) => String::from(CONFIG_PATH_DEFAULT),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads configuration from file and panics if it can't
|
||||||
|
pub fn read_config() -> Config {
|
||||||
|
let config_path = get_config_path();
|
||||||
|
|
||||||
|
let contents = fs::read_to_string(&config_path).unwrap_or_else(|_| {
|
||||||
|
panic!("Failed to open config file at {}", config_path);
|
||||||
|
});
|
||||||
|
let config: Config = toml::from_str(contents.as_str()).unwrap_or_else(|_| {
|
||||||
|
panic!("Failed to parse config file at {}", config_path);
|
||||||
|
});
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
/// Contains all config values for LDAP syncing
|
||||||
|
pub struct Config {
|
||||||
|
ldap_host: String,
|
||||||
|
ldap_scheme: Option<String>,
|
||||||
|
ldap_ssl: Option<bool>,
|
||||||
|
ldap_port: Option<u16>,
|
||||||
|
ldap_bind_dn: String,
|
||||||
|
ldap_bind_password: Pass,
|
||||||
|
ldap_search_base_dn: String,
|
||||||
|
ldap_search_filter: String,
|
||||||
|
ldap_mail_field: Option<String>,
|
||||||
|
ldap_sync_interval_seconds: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Create a config instance from file
|
||||||
|
pub fn from_file() -> Config {
|
||||||
|
read_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_url(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{}://{}:{}",
|
||||||
|
self.get_ldap_scheme(),
|
||||||
|
self.get_ldap_host(),
|
||||||
|
self.get_ldap_port()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_host(&self) -> String {
|
||||||
|
self.ldap_host.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_scheme(&self) -> String {
|
||||||
|
match &self.ldap_scheme {
|
||||||
|
Some(ldap_scheme) => ldap_scheme.clone(),
|
||||||
|
None => {
|
||||||
|
if self.get_ldap_ssl() {
|
||||||
|
String::from("ldaps")
|
||||||
|
} else {
|
||||||
|
String::from("ldap")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_ssl(&self) -> bool {
|
||||||
|
self.ldap_ssl.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_port(&self) -> u16 {
|
||||||
|
match self.ldap_port {
|
||||||
|
Some(ldap_port) => ldap_port,
|
||||||
|
None => {
|
||||||
|
if self.get_ldap_ssl() {
|
||||||
|
636
|
||||||
|
} else {
|
||||||
|
389
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_bind_dn(&self) -> String {
|
||||||
|
self.ldap_bind_dn.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_bind_password(&self) -> String {
|
||||||
|
self.ldap_bind_password.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_search_base_dn(&self) -> String {
|
||||||
|
self.ldap_search_base_dn.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_search_filter(&self) -> String {
|
||||||
|
self.ldap_search_filter.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_mail_field(&self) -> String {
|
||||||
|
let default = String::from("mail");
|
||||||
|
match &self.ldap_mail_field {
|
||||||
|
Some(mail_field) => mail_field.clone(),
|
||||||
|
None => default.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ldap_sync_interval_seconds(&self) -> u64 {
|
||||||
|
self.ldap_sync_interval_seconds.unwrap_or(60)
|
||||||
|
}
|
||||||
|
}
|
100
src/main.rs
Normal file
100
src/main.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
extern crate ldap3;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use ldap3::{DerefAliases, LdapConn, Scope, SearchEntry, SearchOptions};
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let config = config::Config::from_file();
|
||||||
|
|
||||||
|
match do_search(&config) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => println!("{}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = invite_from_ldap(&config) {
|
||||||
|
println!("{}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = start_sync_loop(&config) {
|
||||||
|
println!("{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an LDAP connection, authenticating if necessary
|
||||||
|
fn ldap_client(ldap_url: String, bind_dn: String, bind_pw: String) -> Result<LdapConn, Box<Error>> {
|
||||||
|
let ldap = LdapConn::new(ldap_url.as_str())?;
|
||||||
|
match ldap.simple_bind(bind_dn.as_str(), bind_pw.as_str()) {
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ldap)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves search results from ldap
|
||||||
|
fn search_entries(config: &config::Config) -> Result<Vec<SearchEntry>, Box<Error>> {
|
||||||
|
let ldap = ldap_client(
|
||||||
|
config.get_ldap_url(),
|
||||||
|
config.get_ldap_bind_dn(),
|
||||||
|
config.get_ldap_bind_password(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a simple search and list users
|
||||||
|
fn do_search(config: &config::Config) -> Result<(), Box<Error>> {
|
||||||
|
let mail_field = config.get_ldap_mail_field();
|
||||||
|
let entries = search_entries(config)?;
|
||||||
|
for user in entries {
|
||||||
|
println!("{:?}", user);
|
||||||
|
if let Some(user_email) = user.attrs[mail_field.as_str()].first() {
|
||||||
|
println!("{}", user_email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invite_from_ldap(config: &config::Config) -> Result<(), Box<Error>> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_sync_loop(config: &config::Config) -> Result<(), Box<Error>> {
|
||||||
|
let interval = Duration::from_secs(config.get_ldap_sync_interval_seconds());
|
||||||
|
loop {
|
||||||
|
invite_from_ldap(config)?;
|
||||||
|
sleep(interval);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user