From d626f3366de282a15cc4f9917e2c7d7d8da52be6 Mon Sep 17 00:00:00 2001 From: Trinity Pointard Date: Sun, 4 Oct 2020 12:18:22 +0200 Subject: [PATCH] add support for ldap --- .env.sample | 9 +++++ Cargo.lock | 83 ++++++++++++++++++++++++++++++++++++++ plume-models/Cargo.toml | 1 + plume-models/src/config.rs | 36 +++++++++++++++++ plume-models/src/users.rs | 69 ++++++++++++++++++++++++++----- src/routes/user.rs | 2 +- 6 files changed, 189 insertions(+), 11 deletions(-) diff --git a/.env.sample b/.env.sample index 27bfd6ad..8b27b0aa 100755 --- a/.env.sample +++ b/.env.sample @@ -45,3 +45,12 @@ ROCKET_ADDRESS=127.0.0.1 #PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png #PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png #PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png + +## LDAP CONFIG ## +# the object that will be bound is "${USER_NAME_ATTR}=${username},${BASE_DN}" +#LDAP_ADDR=ldap://127.0.0.1:1389 +#LDAP_BASE_DN="ou=users,dc=your-org,dc=eu" +#LDAP_USER_NAME_ATTR=cn +#LDAP_USER_MAIL_ATTR=mail +#LDAP_TLS=false + diff --git a/Cargo.lock b/Cargo.lock index 0471504c..84a06f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,16 @@ name = "askama_escape" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "async-trait" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "atom_syndication" version = "0.6.0" @@ -1680,6 +1690,39 @@ name = "lazycell" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lber" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ldap3" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-trait 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lber 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lettre" version = "0.9.3" @@ -2158,6 +2201,11 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nom" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "nom" version = "4.2.3" @@ -2604,6 +2652,7 @@ dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ldap3 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "lindera-tantivy 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3788,6 +3837,24 @@ dependencies = [ "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -3924,6 +3991,15 @@ dependencies = [ "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-native-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -4535,6 +4611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum askama_escape 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "719b48039ffac1564f67d70162109ba9341125cee0096a540e478355b3c724a7" +"checksum async-trait 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0" "checksum atom_syndication 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a9a7ab83635ff7a3b04856f4ad95324dccc9b947ab1e790fc5c769ee6d6f60c" "checksum atomicwrites 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6a2baf2feb820299c53c7ad1cc4f5914a220a1cb76d7ce321d2522a94b54651f" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" @@ -4705,6 +4782,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"checksum lber 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a749954d43fcfb8d4381aa0c6cf291065053e0590d622f4f830393a9bd8278a5" +"checksum ldap3 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a38aff2ff62165b59d3a1508f158ee166ff66e82a22ddf357e00fb51b24cf471" "checksum lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bf43f3202a879fbdab4ecafec3349b0139f81d31c626246d53bcbb546253ffaa" "checksum lettre_email 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5" "checksum levenshtein_automata 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73a004f877f468548d8d0ac4977456a249d8fabbdb8416c36db163dfc8f2e8ca" @@ -4754,6 +4833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum new_debug_unreachable 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +"checksum nom 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" "checksum notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" @@ -4911,6 +4991,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b" "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" "checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" @@ -4923,6 +5005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tokio-fs 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" "checksum tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" "checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +"checksum tokio-native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069" "checksum tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" "checksum tokio-sync 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" "checksum tokio-tcp 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" diff --git a/plume-models/Cargo.toml b/plume-models/Cargo.toml index 18d28dcc..9c368681 100644 --- a/plume-models/Cargo.toml +++ b/plume-models/Cargo.toml @@ -13,6 +13,7 @@ guid-create = "0.1" heck = "0.3.0" itertools = "0.8.0" lazy_static = "1.0" +ldap3 = "0.7.1" migrations_internals= "1.4.0" openssl = "0.10.22" rocket = "0.4.5" diff --git a/plume-models/src/config.rs b/plume-models/src/config.rs index 7d990d3e..33e6422a 100644 --- a/plume-models/src/config.rs +++ b/plume-models/src/config.rs @@ -20,6 +20,7 @@ pub struct Config { pub logo: LogoConfig, pub default_theme: String, pub media_directory: String, + pub ldap: Option, } #[derive(Debug, Clone)] @@ -240,6 +241,40 @@ impl SearchTokenizerConfig { } } +pub struct LdapConfig { + pub addr: String, + pub base_dn: String, + pub tls: bool, + pub user_name_attr: String, + pub mail_attr: String, +} + +fn get_ldap_config() -> Option { + let addr = var("LDAP_ADDR").ok(); + let base_dn = var("LDAP_BASE_DN").ok(); + if addr.is_some() && base_dn.is_some() { + let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned()); + let tls = match tls.as_ref() { + "1" | "true" | "TRUE" => true, + "0" | "false" | "FALSE" => false, + _ => panic!("Invalid LDAP configuration : tls") + }; + let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned()); + let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned()); + Some(LdapConfig { + addr: addr.unwrap(), + base_dn: base_dn.unwrap(), + tls, + user_name_attr, + mail_attr, + }) + } else if addr.is_some() && base_dn.is_some() { + panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set") + } else { + None + } +} + lazy_static! { pub static ref CONFIG: Config = Config { base_url: var("BASE_URL").unwrap_or_else(|_| format!( @@ -267,5 +302,6 @@ lazy_static! { default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()), media_directory: var("MEDIA_UPLOAD_DIRECTORY") .unwrap_or_else(|_| "static/media".to_owned()), + ldap: get_ldap_config(), }; } diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index eea12a58..900120a2 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -1,5 +1,5 @@ use crate::{ - ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow, + ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, config::CONFIG, db_conn::DbConn, follows::Follow, instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users, search::Searcher, timeline::Timeline, Connection, Error, PlumeRocket, Result, ITEMS_PER_PAGE, @@ -14,6 +14,7 @@ use activitypub::{ use bcrypt; use chrono::{NaiveDateTime, Utc}; use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; +use ldap3::{LdapConn, SearchEntry, Scope}; use openssl::{ hash::MessageDigest, pkey::{PKey, Private}, @@ -292,20 +293,65 @@ impl User { bcrypt::hash(pass, 10).map_err(Error::from) } - fn ldap_register() -> Result { - return Err(Error::NotFound); - unimplemented!() + fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result { + if let Some(ldap) = CONFIG.ldap.as_ref() { + let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?; + let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn); + let bind = ldap_conn.simple_bind(&ldap_name, password).map_err(|_| Error::NotFound)?; + if bind.success().is_ok() { + let search = ldap_conn.search(&ldap_name, Scope::Base, "(|(objectClass=person)(objectClass=user))", vec![&ldap.mail_attr]) + .map_err(|_| Error::NotFound)? + .success() + .map_err(|_| Error::NotFound)?; + for entry in search.0 { + let entry = SearchEntry::construct(entry); + let email = entry.attrs.get("mail") + .and_then(|vec| vec.first()); + if email.is_some() { + let _ = ldap_conn.unbind(); + return NewUser::new_local( + conn, + name.to_owned(), + name.to_owned(), + Role::Normal, + "", + email.unwrap().to_owned(), + None, + ); + } + } + let _ = ldap_conn.unbind(); + Err(Error::NotFound) + } else { + Err(Error::NotFound) + } + } else { + Err(Error::NotFound) + } } - fn ldap_login(&self) -> bool { - return false; + fn ldap_login(&self, password: &str) -> bool { + if let Some(ldap) = CONFIG.ldap.as_ref() { + let mut conn = if let Ok(conn) = LdapConn::new(&ldap.addr) { + conn + } else { + return false; + }; + let name = format!("{}={},{}", ldap.user_name_attr, &self.username, ldap.base_dn); + if let Ok(bind) = conn.simple_bind(&name, password) { + bind.success().is_ok() + } else { + false + } + } else { + false + } } pub fn login(conn: &Connection, ident: &str, password: &str) -> Result { let local_id = Instance::get_local()?.id; let user = User::find_by_email(conn, ident) .or_else(|_| User::find_by_name(conn, ident, local_id)) - .or_else(|_| User::ldap_register()) .and_then(|u| if u.instance_id == local_id { Ok(u) } else { @@ -321,13 +367,16 @@ impl User { } }, Ok(user) => { - if user.ldap_login() { + if user.ldap_login(password) { Ok(user) } else { Err(Error::NotFound) } }, e => { + if let Ok(user) = User::ldap_register(conn, ident, password) { + return Ok(user); + } let other = User::get(&*conn, 1).expect("No user is registered").hashed_password; other.map(|pass| bcrypt::verify(password, &pass)); e @@ -1019,7 +1068,7 @@ impl NewUser { role: Role, summary: &str, email: String, - password: String, + password: Option, ) -> Result { let (pub_key, priv_key) = gen_keypair(); let instance = Instance::get_local()?; @@ -1037,7 +1086,7 @@ impl NewUser { summary: summary.to_owned(), summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0), email: Some(email), - hashed_password: Some(password), + hashed_password: password, instance_id: instance.id, public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?, private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?), diff --git a/src/routes/user.rs b/src/routes/user.rs index 9abe1e44..39466d01 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -541,7 +541,7 @@ pub fn create( Role::Normal, "", form.email.to_string(), - User::hash_pass(&form.password).map_err(to_validation)?, + Some(User::hash_pass(&form.password).map_err(to_validation)?), ).map_err(to_validation)?; Ok(Flash::success( Redirect::to(uri!(super::session::new: m = _)),