attempt to add support for ldap
Blind attempt Don't create account for existing ldap that is unknown yet Include connection pooling
This commit is contained in:
@@ -12,6 +12,7 @@ guid-create = "0.1"
|
||||
heck = "0.3.0"
|
||||
itertools = "0.8.0"
|
||||
lazy_static = "*"
|
||||
ldap3 = "0.6.1"
|
||||
migrations_internals= "1.4.0"
|
||||
openssl = "0.10.22"
|
||||
rocket = "0.4.0"
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct Config {
|
||||
pub search_index: String,
|
||||
pub rocket: Result<RocketConfig, RocketError>,
|
||||
pub logo: LogoConfig,
|
||||
pub ldap: LdapConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -184,6 +185,27 @@ impl Default for LogoConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LdapConfig {
|
||||
pub url: Option<String>,
|
||||
pub bind_dn: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for LdapConfig {
|
||||
fn default() -> Self {
|
||||
let url = var("LDAP_URL").ok();
|
||||
let bind_dn = var("LDAP_BIND_DN").ok();
|
||||
if url.is_some() ^ bind_dn.is_some() {
|
||||
panic!(
|
||||
r#"Invalid configuration :
|
||||
You must provide both LDAP_URL and LDAP_BIND_DN, or neither"#
|
||||
);
|
||||
} else {
|
||||
LdapConfig { url, bind_dn }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = Config {
|
||||
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
||||
@@ -199,5 +221,6 @@ lazy_static! {
|
||||
search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()),
|
||||
rocket: get_rocket_config(),
|
||||
logo: LogoConfig::default(),
|
||||
ldap: LdapConfig::default(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::CONFIG;
|
||||
use ldap3::LdapConn;
|
||||
use std::io;
|
||||
use std::sync::{mpsc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
type Message = (String, String, mpsc::Sender<io::Result<bool>>);
|
||||
pub struct Ldap {
|
||||
channel: mpsc::Sender<Message>,
|
||||
}
|
||||
|
||||
impl Ldap {
|
||||
pub fn get_shared() -> Self {
|
||||
Ldap {
|
||||
channel: CHANNEL.lock().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(&self, username: String, password: String) -> LdapResult {
|
||||
let (s, r) = mpsc::channel();
|
||||
self.channel.send((username, password, s)).unwrap(); //we know the remote end was not closed
|
||||
LdapResult { channel: r }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LdapResult {
|
||||
channel: mpsc::Receiver<io::Result<bool>>,
|
||||
}
|
||||
|
||||
impl LdapResult {
|
||||
pub fn get(self) -> io::Result<bool> {
|
||||
self.channel.recv().unwrap() //we know some message must have been send, be it an error
|
||||
}
|
||||
}
|
||||
|
||||
/// This function loop indefinitelly, handling requests
|
||||
fn handle(url: &str, bind_dn: &str, channel: mpsc::Receiver<Message>) {
|
||||
let mut conn = LdapConn::new(url).expect("Error connecting to ldap server");
|
||||
for (user, password, channel) in channel.iter() {
|
||||
let res = conn
|
||||
.simple_bind(&format!("uid={},{}", user, bind_dn), &password)
|
||||
.map(|r| r.rc == 0);
|
||||
let err = res.is_err();
|
||||
channel.send(res).ok(); //we can't assume the other end did not drop it's handle
|
||||
let err = conn.unbind().is_err() || err;
|
||||
if err {
|
||||
if let Ok(c) = LdapConn::new(url) {
|
||||
conn = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ignore(channel: mpsc::Receiver<Message>) {
|
||||
for (_user, _password, channel) in channel.iter() {
|
||||
channel.send(Ok(false)).ok();
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CHANNEL: Mutex<mpsc::Sender<Message>> = {
|
||||
let (s, r) = mpsc::channel();
|
||||
|
||||
let builder = thread::Builder::new().name("ldap_handler".into());
|
||||
builder
|
||||
.spawn(move || {
|
||||
if CONFIG.ldap.url.is_some() && CONFIG.ldap.bind_dn.is_some() {
|
||||
handle(
|
||||
CONFIG.ldap.url.as_ref().unwrap(),
|
||||
CONFIG.ldap.bind_dn.as_ref().unwrap(),
|
||||
r,
|
||||
)
|
||||
} else {
|
||||
ignore(r);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Mutex::new(s)
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,7 @@ extern crate heck;
|
||||
extern crate itertools;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate ldap3;
|
||||
extern crate migrations_internals;
|
||||
extern crate openssl;
|
||||
extern crate plume_api;
|
||||
@@ -363,6 +364,7 @@ pub mod follows;
|
||||
pub mod headers;
|
||||
pub mod inbox;
|
||||
pub mod instance;
|
||||
pub mod ldap;
|
||||
pub mod likes;
|
||||
pub mod medias;
|
||||
pub mod mentions;
|
||||
|
||||
@@ -41,6 +41,7 @@ use blogs::Blog;
|
||||
use db_conn::DbConn;
|
||||
use follows::Follow;
|
||||
use instance::*;
|
||||
use ldap::Ldap;
|
||||
use medias::Media;
|
||||
use post_authors::PostAuthor;
|
||||
use posts::Post;
|
||||
@@ -349,7 +350,13 @@ impl User {
|
||||
.or_else(|_| User::find_by_fqn(&rocket, &name));
|
||||
match user {
|
||||
Ok(user) => {
|
||||
if user.auth(password) {
|
||||
let ldap_conn = Ldap::get_shared().connect(name.to_owned(), password.to_owned());
|
||||
let local_conn = user.auth(password);
|
||||
let ldap_conn = ldap_conn.get().unwrap_or(false);
|
||||
if ldap_conn && local_conn {
|
||||
user.clear_password(&rocket.conn).ok();
|
||||
}
|
||||
if ldap_conn || local_conn {
|
||||
Ok(user)
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
@@ -369,6 +376,13 @@ impl User {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_password(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(users::hashed_password.eq::<Option<String>>(None))
|
||||
.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||
|
||||
Reference in New Issue
Block a user