Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{SqlDirectory, SqlMappings};
|
||||
use crate::{Account, Credentials, Recipient, core::secret::verify_secret_hash};
|
||||
use store::{NamedRows, Rows, Value};
|
||||
use trc::AddContext;
|
||||
use utils::sanitize_email;
|
||||
|
||||
impl SqlDirectory {
|
||||
pub async fn authenticate(&self, credentials: &Credentials) -> trc::Result<Account> {
|
||||
let (username, secret) = match credentials {
|
||||
Credentials::Basic {
|
||||
username, secret, ..
|
||||
} => (username, secret),
|
||||
Credentials::Bearer { .. } => {
|
||||
return Err(trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details("Unsupported credentials type for SQL authentication"));
|
||||
}
|
||||
};
|
||||
if secret.is_empty() {
|
||||
return Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
.details("Empty secret rejected")
|
||||
.ctx(trc::Key::AccountName, username.to_string()));
|
||||
}
|
||||
|
||||
let Recipient::Account(mut account) = self.mappings.row_to_account(
|
||||
self.sql_store
|
||||
.sql_query::<NamedRows>(&self.mappings.query_login, vec![username.into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
) else {
|
||||
return Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
.details("SQL login query did not return an account")
|
||||
.ctx(trc::Key::AccountName, username.to_string()));
|
||||
};
|
||||
|
||||
// Validate secret
|
||||
if let Some(account_secret) = &account.secret {
|
||||
if !verify_secret_hash(account_secret, secret.as_bytes()).await? {
|
||||
return Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
.details("Invalid credentials")
|
||||
.ctx(trc::Key::AccountName, username.to_string()));
|
||||
}
|
||||
} else {
|
||||
return Err(trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details("Account does not have a secret")
|
||||
.ctx(trc::Key::AccountName, username.to_string()));
|
||||
}
|
||||
|
||||
// Obtain members
|
||||
if let Some(query) = &self.mappings.query_member_of {
|
||||
let members = account.groups.get_or_insert_default();
|
||||
for row in self
|
||||
.sql_store
|
||||
.sql_query::<Rows>(query, vec![username.into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
{
|
||||
if let Some(Value::Text(address)) = row.values.first()
|
||||
&& let Some(email) = sanitize_email(address)
|
||||
{
|
||||
members.push(email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain emails
|
||||
if let Some(query) = &self.mappings.query_email_aliases {
|
||||
account.email_aliases.extend(
|
||||
self.sql_store
|
||||
.sql_query::<Rows>(query, vec![username.into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
.into_iter()
|
||||
.flat_map(|v| {
|
||||
v.values
|
||||
.into_iter()
|
||||
.filter_map(|v| sanitize_email(v.to_str().as_ref()))
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if account.email.is_empty() {
|
||||
account.email = sanitize_email(username).unwrap_or_else(|| username.to_lowercase());
|
||||
}
|
||||
|
||||
Ok(account)
|
||||
}
|
||||
|
||||
pub async fn recipient(&self, address: &str) -> trc::Result<Recipient> {
|
||||
let recipient = self.mappings.row_to_account(
|
||||
self.sql_store
|
||||
.sql_query::<NamedRows>(&self.mappings.query_recipient, vec![address.into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
);
|
||||
|
||||
match recipient {
|
||||
Recipient::Account(mut account) => {
|
||||
// Obtain members
|
||||
if let Some(query) = &self.mappings.query_member_of {
|
||||
let members = account.groups.get_or_insert_default();
|
||||
for row in self
|
||||
.sql_store
|
||||
.sql_query::<Rows>(query, vec![account.email.as_str().into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
{
|
||||
if let Some(Value::Text(address)) = row.values.first()
|
||||
&& let Some(email) = sanitize_email(address)
|
||||
{
|
||||
members.push(email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain emails
|
||||
if let Some(query) = &self.mappings.query_email_aliases {
|
||||
account.email_aliases.extend(
|
||||
self.sql_store
|
||||
.sql_query::<Rows>(query, vec![account.email.as_str().into()])
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
.into_iter()
|
||||
.flat_map(|v| {
|
||||
v.values
|
||||
.into_iter()
|
||||
.filter_map(|v| sanitize_email(v.to_str().as_ref()))
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Recipient::Account(account))
|
||||
}
|
||||
Recipient::Group(group) => Ok(Recipient::Group(group)),
|
||||
Recipient::Invalid => Ok(Recipient::Invalid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SqlMappings {
|
||||
pub fn row_to_account(&self, rows: NamedRows) -> Recipient {
|
||||
if rows.rows.is_empty() {
|
||||
return Recipient::Invalid;
|
||||
}
|
||||
|
||||
let mut account = Account::default();
|
||||
let mut is_group = false;
|
||||
|
||||
if let Some(row) = rows.rows.into_iter().next() {
|
||||
for (name, value) in rows.names.into_iter().zip(row.values) {
|
||||
if name.eq_ignore_ascii_case(&self.column_email) {
|
||||
if let Value::Text(text) = value
|
||||
&& let Some(email) = sanitize_email(&text)
|
||||
{
|
||||
account.email = email;
|
||||
}
|
||||
} else if name.eq_ignore_ascii_case(&self.column_secret) {
|
||||
if let Value::Text(text) = value
|
||||
&& !text.is_empty()
|
||||
{
|
||||
account.secret = Some(text.into_owned());
|
||||
}
|
||||
} else if let Some(column_type) = &self.column_type
|
||||
&& name.eq_ignore_ascii_case(column_type)
|
||||
{
|
||||
is_group = value.to_str().eq_ignore_ascii_case("group");
|
||||
} else if let Some(column_description) = &self.column_description
|
||||
&& name.eq_ignore_ascii_case(column_description)
|
||||
&& let Value::Text(text) = value
|
||||
&& !text.is_empty()
|
||||
{
|
||||
account.description = Some(text.into_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_group {
|
||||
Recipient::Account(account)
|
||||
} else {
|
||||
Recipient::Group(crate::Group {
|
||||
email: account.email,
|
||||
email_aliases: account.email_aliases,
|
||||
description: account.description,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user