Multi-tenancy: queue visibility (MT-5)

A tenant sees queued mail with a recipient on its domains, and mail its own
authenticated senders sent from them, in get, query, update and destroy.
This commit is contained in:
2026-09-18 15:20:04 -07:00
parent 5a22e79992
commit 3896f720a1
@@ -45,7 +45,7 @@ use store::{
}; };
use trc::AddContext; use trc::AddContext;
use types::{blob::BlobId, blob_hash::BlobHash, id::Id}; use types::{blob::BlobId, blob_hash::BlobHash, id::Id};
use utils::{DomainPart, map::vec_map::VecMap}; use utils::map::vec_map::VecMap;
pub(crate) async fn queued_message_set( pub(crate) async fn queued_message_set(
mut set: RegistrySetResponse<'_>, mut set: RegistrySetResponse<'_>,
@@ -69,13 +69,11 @@ pub(crate) async fn queued_message_set(
continue; continue;
}; };
let archived_message = archive.to_unarchived::<Message>()?; let archived_message = archive.to_unarchived::<Message>()?;
if !tenant_domains.as_ref().is_none_or(|domains| { // inbuxa: MT-5
archived_message if !tenant_domains
.inner .as_ref()
.return_path .is_none_or(|domains| tenant_sees_archived(domains, archived_message.inner))
.try_domain_part() {
.is_some_and(|domain| domains.contains(domain))
}) {
set.response.not_updated.append(id, SetError::not_found()); set.response.not_updated.append(id, SetError::not_found());
continue; continue;
} }
@@ -243,13 +241,11 @@ pub(crate) async fn queued_message_set(
continue; continue;
}; };
if tenant_domains.as_ref().is_none_or(|domains| { // inbuxa: MT-5
message if tenant_domains
.message .as_ref()
.return_path .is_none_or(|domains| tenant_sees(domains, &message.message))
.try_domain_part() {
.is_some_and(|domain| domains.contains(domain))
}) {
if message.remove(set.server, None).await { if message.remove(set.server, None).await {
set.response.destroyed.push(id); set.response.destroyed.push(id);
} else { } else {
@@ -295,12 +291,11 @@ pub(crate) async fn queued_message_get(
continue; continue;
}; };
let message_in = message_archive.unarchive::<Message>()?; let message_in = message_archive.unarchive::<Message>()?;
if tenant_domains.as_ref().is_none_or(|domains| { // inbuxa: MT-5
message_in if tenant_domains
.return_path .as_ref()
.try_domain_part() .is_none_or(|domains| tenant_sees_archived(domains, message_in))
.is_some_and(|domain| domains.contains(domain)) {
}) {
get.insert(id, map_message(message_in).into_value()); get.insert(id, map_message(message_in).into_value());
} else if client_ids { } else if client_ids {
get.not_found(id); get.not_found(id);
@@ -421,9 +416,10 @@ pub(crate) async fn queued_message_query(
.add_context(|ctx| ctx.ctx(trc::Key::Key, key))?; .add_context(|ctx| ctx.ctx(trc::Key::Key, key))?;
if let Some(due) = message.next_delivery_event(queue_name) if let Some(due) = message.next_delivery_event(queue_name)
// inbuxa: MT-5
&& tenant_domains && tenant_domains
.as_ref() .as_ref()
.is_none_or(|domains| message.has_domain(domains)) .is_none_or(|domains| tenant_sees_archived(domains, message))
&& (due_from..=due_to).contains(&due) && (due_from..=due_to).contains(&due)
&& queue_name && queue_name
.as_ref() .as_ref()
@@ -565,10 +561,27 @@ pub(crate) async fn queued_message_query(
} }
} }
// inbuxa: MT-5: a tenant sees mail to its domains, and its own people's sent mail
async fn tenant_domains(server: &Server, tenant_id: u32) -> trc::Result<AHashSet<String>> {
inbuxa_features::tenancy::queue::tenant_domains(server.registry(), tenant_id).await
}
#[cfg(not(feature = "enterprise"))] fn tenant_sees(domains: &AHashSet<String>, message: &Message) -> bool {
async fn tenant_domains(_server: &Server, _tenant_id: u32) -> trc::Result<AHashSet<String>> { inbuxa_features::tenancy::queue::sees(
Ok(AHashSet::new()) domains,
message.recipients.iter().map(|rcpt| rcpt.address()),
&message.return_path,
message.flags & FROM_AUTHENTICATED != 0,
)
}
fn tenant_sees_archived(domains: &AHashSet<String>, message: &ArchivedMessage) -> bool {
inbuxa_features::tenancy::queue::sees(
domains,
message.recipients.iter().map(|rcpt| rcpt.address()),
&message.return_path,
message.flags.to_native() & FROM_AUTHENTICATED != 0,
)
} }
fn map_message(message_in: &ArchivedMessage) -> QueuedMessage { fn map_message(message_in: &ArchivedMessage) -> QueuedMessage {