features, cleanup and bug fixes #26
4 changed files with 133 additions and 94 deletions
refactor the login handling
commit
36ba2f2ff1
101
src/auth.rs
101
src/auth.rs
|
|
@ -1,8 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use ldap3::{DerefAliases, SearchOptions};
|
|
||||||
use log::{debug, warn};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
mod login_provider;
|
mod login_provider;
|
||||||
|
|
@ -31,7 +29,7 @@ struct LoginData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub(crate) fn current(session: &Session) -> actix_web::Result<User, LoginRequired> {
|
pub(crate) fn current(session: &Session) -> Result<User, LoginRequired> {
|
||||||
session
|
session
|
||||||
.get::<String>(USER_NAME)
|
.get::<String>(USER_NAME)
|
||||||
.map_err(|_| LoginRequired::new())
|
.map_err(|_| LoginRequired::new())
|
||||||
|
|
@ -53,100 +51,19 @@ impl User {
|
||||||
provided_password: &str,
|
provided_password: &str,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
config: &ServerConfig,
|
config: &ServerConfig,
|
||||||
) -> actix_web::Result<Option<User>, AuthenticationError> {
|
) -> Result<Option<User>, AuthenticationError> {
|
||||||
const USERNAME_PATTERN: &str = "%{username}";
|
if let Some(user) = config
|
||||||
let result = match &config.config.login_provider {
|
.config
|
||||||
LoginProviderConfig::Ldap(ldap_config) => {
|
.login_provider
|
||||||
let ldap_settings =
|
.authenticate(user_name, provided_password)
|
||||||
ldap3::LdapConnSettings::new().set_starttls(ldap_config.starttls);
|
|
||||||
let (connection, mut ldap) =
|
|
||||||
ldap3::LdapConnAsync::with_settings(ldap_settings, &ldap_config.server_address)
|
|
||||||
.await?;
|
|
||||||
ldap3::drive!(connection);
|
|
||||||
|
|
||||||
let escaped_username_for_dn = ldap3::dn_escape(user_name);
|
|
||||||
let ldap_dn = ldap_config
|
|
||||||
.ldap_user_dn
|
|
||||||
.replace(USERNAME_PATTERN, &escaped_username_for_dn);
|
|
||||||
|
|
||||||
debug!("Attempting ldap simple bind for fn {}", ldap_dn);
|
|
||||||
let result = ldap.simple_bind(&ldap_dn, provided_password).await?;
|
|
||||||
|
|
||||||
match result.success() {
|
|
||||||
Ok(_) => {
|
|
||||||
let escaped_username_for_filter = ldap3::ldap_escape(user_name);
|
|
||||||
let ldap_user_filter = ldap_config
|
|
||||||
.ldap_user_filter
|
|
||||||
.replace(USERNAME_PATTERN, &escaped_username_for_filter);
|
|
||||||
|
|
||||||
debug!("Attempting ldap search: {}", ldap_user_filter);
|
|
||||||
|
|
||||||
let (search_result, _) = match ldap
|
|
||||||
.with_search_options(
|
|
||||||
SearchOptions::default().deref(DerefAliases::Never),
|
|
||||||
)
|
|
||||||
.search(&ldap_dn, ldap3::Scope::Subtree, &ldap_user_filter, ["uid"])
|
|
||||||
.await?
|
.await?
|
||||||
.success()
|
|
||||||
{
|
{
|
||||||
Ok(result) => result,
|
|
||||||
Err(err) => {
|
|
||||||
debug!("ldap search failed after successful bind {:?}", err);
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match search_result.len() {
|
|
||||||
1 => Some(User {
|
|
||||||
name: user_name.to_owned(),
|
|
||||||
}),
|
|
||||||
0 => {
|
|
||||||
warn!("No ldap entry fount after filter!");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
warn!("Multiple ldap entries found!");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
debug!("ldap bind failed: {:?}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoginProviderConfig::Simple(config) => {
|
|
||||||
let simple_login_data = tokio::fs::read_to_string(&config.file_path).await?;
|
|
||||||
let login_data: LoginData = toml::from_str(&simple_login_data)?;
|
|
||||||
if let Some(stored_password) = login_data.users.get(user_name) {
|
|
||||||
if stored_password == provided_password {
|
|
||||||
Some(User {
|
|
||||||
name: user_name.to_owned(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoginProviderConfig::Disabled => None,
|
|
||||||
#[cfg(feature = "dev_mode")]
|
|
||||||
LoginProviderConfig::Development => {
|
|
||||||
warn!("Using dev-login provider!");
|
|
||||||
Some(User {
|
|
||||||
name: user_name.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(user) = &result {
|
|
||||||
session
|
session
|
||||||
.insert(USER_NAME, &user.name)
|
.insert(USER_NAME, &user.name)
|
||||||
.map_err(AuthenticationError::SetCookie)?;
|
.map_err(AuthenticationError::SetCookie)?;
|
||||||
|
Ok(Some(user))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
use log::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::auth::login_provider::ldap::LdapLoginProviderConfig;
|
use crate::auth::login_provider::ldap::LdapLoginProviderConfig;
|
||||||
use crate::auth::login_provider::simple::SimpleLoginProviderConfig;
|
use crate::auth::login_provider::simple::SimpleLoginProviderConfig;
|
||||||
|
use crate::auth::User;
|
||||||
|
use crate::error::AuthenticationError;
|
||||||
|
|
||||||
mod ldap;
|
mod ldap;
|
||||||
mod simple;
|
mod simple;
|
||||||
|
|
@ -22,6 +25,33 @@ pub(crate) enum LoginProviderConfig {
|
||||||
Development,
|
Development,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoginProviderConfig {
|
||||||
|
pub(crate) async fn authenticate(
|
||||||
|
&self,
|
||||||
|
user_name: &str,
|
||||||
|
provided_password: &str,
|
||||||
|
) -> Result<Option<User>, AuthenticationError> {
|
||||||
|
match self {
|
||||||
|
LoginProviderConfig::Ldap(ldap_config) => {
|
||||||
|
ldap_config.authenticate(user_name, provided_password).await
|
||||||
|
}
|
||||||
|
LoginProviderConfig::Simple(simple_config) => {
|
||||||
|
simple_config
|
||||||
|
.authenticate(user_name, provided_password)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
LoginProviderConfig::Disabled => Ok(None),
|
||||||
|
#[cfg(feature = "dev_mode")]
|
||||||
|
LoginProviderConfig::Development => {
|
||||||
|
warn!("Using dev-login provider!");
|
||||||
|
Ok(Some(User {
|
||||||
|
name: user_name.to_owned(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for LoginProviderConfig {
|
impl Default for LoginProviderConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
LoginProviderConfig::Simple(SimpleLoginProviderConfig::default())
|
LoginProviderConfig::Simple(SimpleLoginProviderConfig::default())
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
use crate::auth::User;
|
||||||
|
use crate::error::AuthenticationError;
|
||||||
|
use ldap3::{DerefAliases, SearchOptions};
|
||||||
|
use log::{debug, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -11,3 +15,67 @@ pub(crate) struct LdapLoginProviderConfig {
|
||||||
#[serde(default = "super::default_true")]
|
#[serde(default = "super::default_true")]
|
||||||
pub(crate) starttls: bool,
|
pub(crate) starttls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LdapLoginProviderConfig {
|
||||||
|
pub(crate) async fn authenticate(
|
||||||
|
&self,
|
||||||
|
user_name: &str,
|
||||||
|
provided_password: &str,
|
||||||
|
) -> Result<Option<User>, AuthenticationError> {
|
||||||
|
const USERNAME_PATTERN: &str = "%{username}";
|
||||||
|
let ldap_settings = ldap3::LdapConnSettings::new().set_starttls(self.starttls);
|
||||||
|
let (connection, mut ldap) =
|
||||||
|
ldap3::LdapConnAsync::with_settings(ldap_settings, &self.server_address).await?;
|
||||||
|
ldap3::drive!(connection);
|
||||||
|
|
||||||
|
let escaped_username_for_dn = ldap3::dn_escape(user_name);
|
||||||
|
let ldap_dn = self
|
||||||
|
.ldap_user_dn
|
||||||
|
.replace(USERNAME_PATTERN, &escaped_username_for_dn);
|
||||||
|
|
||||||
|
debug!("Attempting ldap simple bind for fn {}", ldap_dn);
|
||||||
|
let result = ldap.simple_bind(&ldap_dn, provided_password).await?;
|
||||||
|
|
||||||
|
Ok(match result.success() {
|
||||||
|
Ok(_) => {
|
||||||
|
let escaped_username_for_filter = ldap3::ldap_escape(user_name);
|
||||||
|
let ldap_user_filter = self
|
||||||
|
.ldap_user_filter
|
||||||
|
.replace(USERNAME_PATTERN, &escaped_username_for_filter);
|
||||||
|
|
||||||
|
debug!("Attempting ldap search: {}", ldap_user_filter);
|
||||||
|
|
||||||
|
let (search_result, _) = match ldap
|
||||||
|
.with_search_options(SearchOptions::default().deref(DerefAliases::Never))
|
||||||
|
.search(&ldap_dn, ldap3::Scope::Subtree, &ldap_user_filter, ["uid"])
|
||||||
|
.await?
|
||||||
|
.success()
|
||||||
|
{
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(err) => {
|
||||||
|
debug!("ldap search failed after successful bind {:?}", err);
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match search_result.len() {
|
||||||
|
1 => Some(User {
|
||||||
|
name: user_name.to_owned(),
|
||||||
|
}),
|
||||||
|
0 => {
|
||||||
|
warn!("No ldap entry fount after filter!");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Multiple ldap entries found!");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("ldap bind failed: {:?}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::auth::{LoginData, User};
|
||||||
|
use crate::error::AuthenticationError;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -30,6 +32,28 @@ impl SimpleLoginProviderConfig {
|
||||||
PathBuf::from("./login.toml")
|
PathBuf::from("./login.toml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn authenticate(
|
||||||
|
&self,
|
||||||
|
user_name: &str,
|
||||||
|
provided_password: &str,
|
||||||
|
) -> Result<Option<User>, AuthenticationError> {
|
||||||
|
let simple_login_data = tokio::fs::read_to_string(&self.file_path).await?;
|
||||||
|
let login_data: LoginData = toml::from_str(&simple_login_data)?;
|
||||||
|
Ok(
|
||||||
|
if let Some(stored_password) = login_data.users.get(user_name) {
|
||||||
|
if stored_password == provided_password {
|
||||||
|
Some(User {
|
||||||
|
name: user_name.to_owned(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SimpleLoginProviderConfig {
|
impl Default for SimpleLoginProviderConfig {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue