/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * 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 { 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::(&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::(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::(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 { let recipient = self.mappings.row_to_account( self.sql_store .sql_query::(&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::(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::(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, }) } } }