Skip to content
This repository has been archived by the owner on Mar 2, 2020. It is now read-only.

Commit

Permalink
ldap authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberb committed Oct 7, 2019
1 parent c91ceb6 commit 92f1919
Show file tree
Hide file tree
Showing 9 changed files with 930 additions and 22 deletions.
786 changes: 786 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ nix = "0.14"
base64 = "0.10"
task_scheduler = "0.2.0"
structopt = "0.2.18"

# Statically link SQLite (use the crate version provided by Diesel)
# The highest version which Diesel currently allows is 0.12.0
libsqlite3-sys = { version = "0.12.0", features = ["bundled"] }
ldap3 = "0.6.1"

[dev-dependencies]
serde_json = "1.0"
Expand Down
67 changes: 67 additions & 0 deletions src/env/config/ldap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Pi-hole: A black hole for Internet advertisements
// (c) 2019 Pi-hole, LLC (https://pi-hole.net)
// Network-wide ad blocking via your own hardware.
//
// API
// LDAP Config
//
// This file is copyright under the latest version of the EUPL.
// Please see LICENSE file for your rights under this license.

/// Configuration settings for LDAP authentication
#[derive(Deserialize, Clone)]
pub struct LdapConfig {
/// If LDAP should be enabled
#[serde(default = "default_enabled")]
pub enabled: bool,

/// LDAP server address
#[serde(default = "default_address")]
pub address: String,

/// Bind Dn
#[serde(default = "default_bind_dn")]
pub bind_dn: String
}

impl Default for LdapConfig {
fn default() -> Self {
LdapConfig {
enabled: default_enabled(),
address: default_address(),
bind_dn: default_bind_dn()
}
}
}

impl LdapConfig {
pub fn is_valid(&self) -> bool {
true
}
}

fn default_enabled() -> bool {
false
}

fn default_address() -> String {
"ldap://localhost:389".to_owned()
}

fn default_bind_dn() -> String {
"".to_owned()
}

#[cfg(test)]
mod test {
use super::LdapConfig;

/// The default config is valid
#[test]
fn valid_ldap() {
let ldap_config = LdapConfig::default();

assert!(ldap_config.is_valid())
}

}
2 changes: 2 additions & 0 deletions src/env/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ mod file_locations;
mod general;
mod root_config;
mod web;
mod ldap;

pub use self::root_config::Config;
pub use self::ldap::LdapConfig;
6 changes: 4 additions & 2 deletions src/env/config/root_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Please see LICENSE file for your rights under this license.

use crate::{
env::config::{file_locations::Files, general::General, web::WebConfig},
env::config::{file_locations::Files, general::General, web::WebConfig, ldap::LdapConfig},
util::{Error, ErrorKind}
};
use failure::{Fail, ResultExt};
Expand All @@ -29,7 +29,9 @@ pub struct Config {
#[serde(default)]
pub file_locations: Files,
#[serde(default)]
pub web: WebConfig
pub web: WebConfig,
#[serde(default)]
pub ldap: LdapConfig
}

impl Config {
Expand Down
2 changes: 1 addition & 1 deletion src/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ mod config;
mod env_impl;
mod file;

pub use self::{config::Config, env_impl::Env, file::PiholeFile};
pub use self::{config::Config, config::LdapConfig, env_impl::Env, file::PiholeFile};
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ extern crate rocket_contrib;
#[macro_use]
extern crate rust_embed;

extern crate ldap3;

#[cfg(test)]
#[macro_use]
extern crate lazy_static;
Expand Down
81 changes: 65 additions & 16 deletions src/routes/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
// This file is copyright under the latest version of the EUPL.
// Please see LICENSE file for your rights under this license.

use ldap3::LdapConn;
use crate::{
env::LdapConfig
};
use crate::util::{reply_success, Error, ErrorKind, Reply};
use rocket::{
http::{Cookie, Cookies},
Expand All @@ -18,6 +22,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};

const USER_ATTR: &str = "user_id";
const AUTH_HEADER: &str = "X-Pi-hole-Authenticate";
const AUTH_HEADER_USERNAME: &str = "X-Pi-hole-Authenticate-username";

/// When used as a request guard, requests must be authenticated
pub struct User {
Expand All @@ -26,6 +31,7 @@ pub struct User {

/// Stores the API key in the server state
pub struct AuthData {
ldap: LdapConfig,
key: Option<String>,
next_id: AtomicUsize
}
Expand Down Expand Up @@ -75,31 +81,74 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
None => return Error::from(ErrorKind::Unknown).into_outcome()
};

// Check if a key is required for authentication
if !auth_data.key_required() {
return Outcome::Success(User::create_and_store_user(request, &auth_data));
}
let key_opt = request.headers().get_one(AUTH_HEADER);
let ldap_config = auth_data.ldap.clone();
if ldap_config.enabled {
println!("LDAP is enabled");
match key_opt {
Some(key) => {
match request.headers().get_one(AUTH_HEADER_USERNAME) {
Some(username) => {
let ldap_address = ldap_config.address;
println!("LDAP address: {}, user: {}", ldap_address, username);
let conn = LdapConn::new(&ldap_address);
match conn {
Ok(conn) => {
let bind_dn = ldap_config.bind_dn.replace("{}", username);
let result = conn.simple_bind(&bind_dn, key);
match result {
Ok(bind_result) => {
match bind_result.success() {
Ok(_) => {
return Outcome::Success(User::create_and_store_user(request, &auth_data))
},
Err(e) => {
println!("LDAP bind result: {}", e);
}
};
},
Err(e) => {
println!("LDAP bind error: {}", e);
}
};
let _ = conn.unbind();
},
Err(e) => {
println!("LDAP connection error: {}", e);
}
};
},
None => ()
};
},
None => ()
};

// Check the user's key, if provided
if let Some(key) = request.headers().get_one(AUTH_HEADER) {
if auth_data.key_matches(key) {
// The key matches, so create and store a new user and cookie
Outcome::Success(Self::create_and_store_user(request, &auth_data))
} else {
// The key does not match
Error::from(ErrorKind::Unauthorized).into_outcome()
}
} else {
// A key is required but not provided
Error::from(ErrorKind::Unauthorized).into_outcome()

// Check if a key is required for authentication
if !auth_data.key_required() {
return Outcome::Success(User::create_and_store_user(request, &auth_data));
}

// Check the user's key, if provided
if let Some(key) = key_opt {
if auth_data.key_matches(key) {
// The key matches, so create and store a new user and cookie
return Outcome::Success(Self::create_and_store_user(request, &auth_data));
}
}

}
Error::from(ErrorKind::Unauthorized).into_outcome()
}
}

impl AuthData {
/// Create a new API key
pub fn new(key: Option<String>) -> AuthData {
pub fn new(key: Option<String>, ldap: LdapConfig) -> AuthData {
AuthData {
ldap,
key,
next_id: AtomicUsize::new(1)
}
Expand Down
4 changes: 2 additions & 2 deletions src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ fn setup(

// Create a scheduler for scheduling work (ex. disable for 10 minutes)
let scheduler = task_scheduler::Scheduler::new();

let ldap = env.config().ldap.clone();
// Set up the server
server
// Attach CORS handler
Expand All @@ -159,7 +159,7 @@ fn setup(
// Manage the environment
.manage(env)
// Manage the API key
.manage(AuthData::new(api_key))
.manage(AuthData::new(api_key, ldap))
// Manage the scheduler
.manage(scheduler)
// Mount the API
Expand Down

0 comments on commit 92f1919

Please sign in to comment.