This commit is contained in:
@@ -17,9 +17,8 @@ tokio-rustls = { version = "0.25.0"}
|
||||
rustls = "0.22"
|
||||
rustls-pki-types = { version = "1" }
|
||||
ldap3 = { version = "0.11.1", default-features = false, features = ["tls-rustls"] }
|
||||
deadpool = { version = "0.10.0", features = ["managed", "rt_tokio_1"] }
|
||||
deadpool = { version = "0.12", features = ["managed", "rt_tokio_1"] }
|
||||
parking_lot = "0.12"
|
||||
async-trait = "0.1.68"
|
||||
ahash = { version = "0.8" }
|
||||
tracing = "0.1"
|
||||
lru-cache = "0.1.2"
|
||||
@@ -34,6 +33,7 @@ md5 = "0.7.0"
|
||||
futures = "0.3"
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
totp-rs = { version = "5.5.1", features = ["otpauth"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23", features = ["full"] }
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use deadpool::managed;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::client::TlsStream;
|
||||
|
||||
use super::{ImapClient, ImapConnectionManager, ImapError};
|
||||
|
||||
#[async_trait]
|
||||
impl managed::Manager for ImapConnectionManager {
|
||||
type Type = ImapClient<TlsStream<TcpStream>>;
|
||||
type Error = ImapError;
|
||||
|
||||
@@ -59,7 +59,7 @@ impl DirectoryStore for Store {
|
||||
.await?,
|
||||
secret,
|
||||
) {
|
||||
(Some(mut principal), Some(secret)) if principal.verify_secret(secret).await => {
|
||||
(Some(mut principal), Some(secret)) if principal.verify_secret(secret).await? => {
|
||||
if return_member_of {
|
||||
principal.member_of = self.get_member_of(principal.id).await?;
|
||||
}
|
||||
|
||||
@@ -414,6 +414,35 @@ impl ManageDirectory for Store {
|
||||
) => {
|
||||
principal.inner.secrets = secrets;
|
||||
}
|
||||
(
|
||||
PrincipalAction::AddItem,
|
||||
PrincipalField::Secrets,
|
||||
PrincipalValue::String(secret),
|
||||
) => {
|
||||
let mut do_add = true;
|
||||
let mut new_secrets = Vec::with_capacity(principal.inner.secrets.len() + 1);
|
||||
for prev_secret in principal.inner.secrets {
|
||||
if prev_secret == secret {
|
||||
do_add = false;
|
||||
} else if prev_secret.starts_with("otpauth://")
|
||||
|| prev_secret == "$disabled$"
|
||||
|| prev_secret.starts_with("$app$")
|
||||
{
|
||||
new_secrets.push(prev_secret);
|
||||
}
|
||||
}
|
||||
if do_add {
|
||||
new_secrets.push(secret);
|
||||
}
|
||||
principal.inner.secrets = new_secrets;
|
||||
}
|
||||
(
|
||||
PrincipalAction::RemoveItem,
|
||||
PrincipalField::Secrets,
|
||||
PrincipalValue::String(secret),
|
||||
) => {
|
||||
principal.inner.secrets.retain(|v| *v != secret);
|
||||
}
|
||||
(
|
||||
PrincipalAction::Set,
|
||||
PrincipalField::Description,
|
||||
|
||||
@@ -87,7 +87,7 @@ impl LdapDirectory {
|
||||
.find_principal(&mut conn, &self.mappings.filter_name.build(username))
|
||||
.await?
|
||||
{
|
||||
if principal.verify_secret(secret).await {
|
||||
if principal.verify_secret(secret).await? {
|
||||
principal
|
||||
} else {
|
||||
tracing::debug!(
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use async_trait::async_trait;
|
||||
use deadpool::managed;
|
||||
use ldap3::{exop::WhoAmI, Ldap, LdapConnAsync, LdapError};
|
||||
|
||||
use super::LdapConnectionManager;
|
||||
|
||||
#[async_trait]
|
||||
impl managed::Manager for LdapConnectionManager {
|
||||
type Type = Ldap;
|
||||
type Error = LdapError;
|
||||
|
||||
@@ -36,7 +36,7 @@ impl MemoryDirectory {
|
||||
|
||||
for principal in &self.principals {
|
||||
if &principal.name == username {
|
||||
return if principal.verify_secret(secret).await {
|
||||
return if principal.verify_secret(secret).await? {
|
||||
Ok(Some(principal.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use async_trait::async_trait;
|
||||
use deadpool::managed;
|
||||
use mail_send::{smtp::AssertReply, Error};
|
||||
|
||||
use super::{SmtpClient, SmtpConnectionManager};
|
||||
|
||||
#[async_trait]
|
||||
impl managed::Manager for SmtpConnectionManager {
|
||||
type Type = SmtpClient;
|
||||
type Error = Error;
|
||||
@@ -45,8 +43,10 @@ impl managed::Manager for SmtpConnectionManager {
|
||||
.map(|_| ())
|
||||
.map_err(managed::RecycleError::Backend)
|
||||
} else {
|
||||
Err(managed::RecycleError::StaticMessage(
|
||||
"No longer valid: Too many authentication failures",
|
||||
Err(managed::RecycleError::Message(
|
||||
"No longer valid: Too many authentication failures"
|
||||
.to_string()
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl SqlDirectory {
|
||||
|
||||
// Validate password
|
||||
if let Some(secret) = secret {
|
||||
if !principal.verify_secret(secret).await {
|
||||
if !principal.verify_secret(secret).await? {
|
||||
tracing::debug!(
|
||||
context = "directory",
|
||||
event = "invalid_password",
|
||||
|
||||
@@ -16,17 +16,51 @@ use sha1::Sha1;
|
||||
use sha2::Sha256;
|
||||
use sha2::Sha512;
|
||||
use tokio::sync::oneshot;
|
||||
use totp_rs::TOTP;
|
||||
|
||||
use crate::DirectoryError;
|
||||
use crate::Principal;
|
||||
|
||||
impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
pub async fn verify_secret(&self, secret: &str) -> bool {
|
||||
for hashed_secret in &self.secrets {
|
||||
if verify_secret_hash(hashed_secret, secret).await {
|
||||
return true;
|
||||
pub async fn verify_secret(&self, mut code: &str) -> crate::Result<bool> {
|
||||
let mut totp_token = None;
|
||||
|
||||
for secret in &self.secrets {
|
||||
let mut secret = secret.as_str();
|
||||
|
||||
if secret == "$disabled$" {
|
||||
return Ok(false);
|
||||
} else if secret.starts_with("otpauth://") && totp_token.is_none() {
|
||||
let totp_token = if let Some(totp_token) = totp_token {
|
||||
totp_token
|
||||
} else {
|
||||
let (_code, _totp_token) = code
|
||||
.rsplit_once('$')
|
||||
.filter(|(c, t)| !c.is_empty() && !t.is_empty())
|
||||
.ok_or(DirectoryError::MissingTotpCode)?;
|
||||
totp_token = Some(_totp_token);
|
||||
code = _code;
|
||||
_totp_token
|
||||
};
|
||||
if !TOTP::from_url(secret)
|
||||
.map_err(DirectoryError::InvalidTotpUrl)?
|
||||
.check_current(totp_token)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
} else if let Some((_, app_secret)) =
|
||||
secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
|
||||
{
|
||||
secret = app_secret;
|
||||
}
|
||||
|
||||
if verify_secret_hash(secret, code).await {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use deadpool::managed::PoolError;
|
||||
use ldap3::LdapError;
|
||||
use mail_send::Credentials;
|
||||
use store::Store;
|
||||
use totp_rs::TotpUrlError;
|
||||
|
||||
pub mod backend;
|
||||
pub mod core;
|
||||
@@ -81,6 +82,8 @@ pub enum DirectoryError {
|
||||
Management(ManagementError),
|
||||
TimedOut,
|
||||
Unsupported,
|
||||
InvalidTotpUrl(TotpUrlError),
|
||||
MissingTotpCode,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -309,6 +312,8 @@ impl Display for DirectoryError {
|
||||
Self::Management(error) => write!(f, "Management error: {:?}", error),
|
||||
Self::TimedOut => write!(f, "Directory timed out"),
|
||||
Self::Unsupported => write!(f, "Method not supported by directory"),
|
||||
Self::InvalidTotpUrl(error) => write!(f, "Invalid TOTP URL: {}", error),
|
||||
Self::MissingTotpCode => write!(f, "Missing TOTP code"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user