Files
inbuxa-server/tests/src/system/directory.rs
T
jcoffey-dev 7dae9b29fd 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.
2026-09-18 10:21:56 -07:00

681 lines
21 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::utils::{jmap::JmapUtils, server::TestServer};
use common::{
auth::{ACCOUNT_IS_USER, EmailAddress, EmailCache},
network::RcptResolution,
};
use jmap_proto::error::set::SetErrorType;
use registry::{
schema::{
enums::{AccountType, StorageQuota},
prelude::{ObjectType, Property},
structs::{
Account, CertificateManagement, Credential, DkimManagement, DnsManagement, Domain,
EmailAlias, Expression, ExpressionMatch, GroupAccount, MailingList, PasswordCredential,
SubAddressing, SubAddressingCustom, UserAccount,
},
},
types::{EnumImpl, list::List, map::Map},
};
use serde_json::json;
use std::sync::Arc;
use utils::map::vec_map::VecMap;
pub async fn test(test: &TestServer) {
println!("Running Directory tests...");
let account = test.account("[email protected]");
// Create a domain and make sure it's in the cache
let domain_id = account
.registry_create_object(Domain {
name: "example.com".to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
aliases: Map::new(vec!["beispiel.de".to_string()]),
is_enabled: true,
catch_all_address: Some("[email protected]".to_string()),
sub_addressing: SubAddressing::Enabled,
..Default::default()
})
.await;
let domain_cache = test
.server
.domain_by_id(domain_id.document_id())
.await
.unwrap()
.unwrap();
assert_eq!(
&domain_cache.names,
&Box::from_iter(["example.com".into(), "beispiel.de".into()])
);
assert_eq!(domain_cache.id, domain_id.document_id());
assert_eq!(
domain_cache.catch_all.as_deref(),
Some("[email protected]")
);
// Multiple domains with the same name should not be allowed
account
.registry_create_object_expect_err(Domain {
name: "example.com".to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
..Default::default()
})
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
// An alias matching another domain's name should not be allowed
account
.registry_create_object_expect_err(Domain {
name: "example.net".to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
aliases: Map::new(vec!["example.com".to_string()]),
..Default::default()
})
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
// An alias matching another domain's alias should not be allowed
account
.registry_create_object_expect_err(Domain {
name: "example.net".to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
aliases: Map::new(vec!["beispiel.de".to_string()]),
..Default::default()
})
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
// A domain name matching another domain's alias should not be allowed
account
.registry_create_object_expect_err(Domain {
name: "beispiel.de".to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
..Default::default()
})
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
// Invalid local part should not be allowed
account
.registry_create_object_expect_err(Account::User(UserAccount {
name: "!invalid".to_string(),
domain_id,
credentials: List::from_iter([Credential::Password(PasswordCredential {
secret: "hello world".to_string(),
..Default::default()
})]),
aliases: List::from_iter([EmailAlias {
name: "inva..lid".to_string(),
domain_id,
enabled: true,
..Default::default()
}]),
..Default::default()
}))
.await
.assert_type(SetErrorType::InvalidPatch)
.assert_description_contains("Invalid email local part");
// Valid account creation with local part sanitization
let account_id = account
.registry_create_object(Account::User(UserAccount {
name: " john doe".to_string(),
domain_id,
description: "John 'Johnny-D' Doe".to_string().into(),
credentials: List::from_iter([Credential::Password(PasswordCredential {
secret: "hello world".to_string(),
..Default::default()
})]),
aliases: List::from_iter([EmailAlias {
name: "jdoe".to_string(),
domain_id,
enabled: true,
..Default::default()
}]),
quotas: VecMap::from_iter([
(StorageQuota::MaxDiskQuota, 1024u64),
(StorageQuota::MaxEmails, 100u64),
]),
..Default::default()
}))
.await;
let account_cache = test.server.account(account_id.document_id()).await.unwrap();
assert_eq!(account_cache.name.as_ref(), "[email protected]");
assert_eq!(
account_cache.description.as_deref(),
Some("John 'Johnny-D' Doe")
);
assert_eq!(account_cache.id, account_id.document_id());
assert_eq!(account_cache.quota_disk, 1024);
assert_eq!(
account_cache
.quota_objects
.as_ref()
.unwrap()
.get(StorageQuota::MaxEmails),
100
);
assert_eq!(
account_cache.addresses,
vec![
EmailAddress {
local_part: "johndoe".into(),
domain_id: domain_id.document_id(),
},
EmailAddress {
local_part: "jdoe".into(),
domain_id: domain_id.document_id(),
}
]
.into_boxed_slice()
);
assert!(account_cache.flags & ACCOUNT_IS_USER != 0);
// Duplicate account names should not be allowed
account
.registry_create_object_expect_err(Account::User(UserAccount {
name: "johndoe".to_string(),
domain_id,
..Default::default()
}))
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
account
.registry_create_object_expect_err(Account::User(UserAccount {
name: "jdoe".to_string(),
domain_id,
..Default::default()
}))
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
account
.registry_create_object_expect_err(Account::Group(GroupAccount {
name: "jdoe".to_string(),
domain_id,
..Default::default()
}))
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
account
.registry_create_object_expect_err(MailingList {
name: "jdoe".to_string(),
domain_id,
..Default::default()
})
.await
.assert_type(SetErrorType::PrimaryKeyViolation);
// Create a group and add it to the account
let group_id = account
.registry_create_object(Account::Group(GroupAccount {
name: "sales".to_string(),
domain_id,
..Default::default()
}))
.await;
let account_cache = test.server.account(group_id.document_id()).await.unwrap();
assert_eq!(account_cache.name.as_ref(), "[email protected]");
assert!(account_cache.flags & ACCOUNT_IS_USER == 0);
account
.registry_update_object(
ObjectType::Account,
account_id,
json!({
Property::MemberGroupIds: {
group_id: true
}
}),
)
.await;
let account_cache = test.server.account(account_id.document_id()).await.unwrap();
assert_eq!(
account_cache.id_member_of.as_ref(),
&[group_id.document_id()]
);
// Linking invalid groups should not be allowed
account
.registry_update_object_expect_err(
ObjectType::Account,
account_id,
json!({
Property::MemberGroupIds: {
account_id: true
}
}),
)
.await
.assert_type(SetErrorType::InvalidForeignKey);
// Remove the group membership and make sure it's gone
account
.registry_update_object(
ObjectType::Account,
account_id,
json!({
Property::MemberGroupIds: {
group_id: false
}
}),
)
.await;
let account_cache = test.server.account(account_id.document_id()).await.unwrap();
assert!(account_cache.id_member_of.as_ref().is_empty());
// Create a masked email
let john = crate::utils::account::Account::new(
"[email protected]",
"hello world",
&[],
"John",
account_id,
);
let response = john
.registry_create_many(
ObjectType::MaskedEmail,
[json!({
Property::EmailPrefix: "test",
})],
)
.await;
let masked = response.created(0);
let masked_id = masked.object_id();
let masked_email = masked.text_field("email").to_string();
assert_eq!(
test.server
.account_id_from_email("[email protected]", true)
.await
.unwrap(),
Some(account_id.document_id())
);
assert_eq!(
test.server
.account_id_from_email(&masked_email, true)
.await
.unwrap(),
Some(account_id.document_id())
);
// Create a mailing list
let list_id = account
.registry_create_object(MailingList {
name: "newsletter".to_string(),
domain_id,
recipients: Map::new(vec!["[email protected]".to_string()]),
..Default::default()
})
.await;
let list_cache = test
.server
.try_list(list_id.document_id())
.await
.unwrap()
.unwrap();
assert_eq!(
&list_cache.recipients,
&Arc::from(Box::from_iter(["[email protected]".into()]))
);
// Update mailing list
account
.registry_update_object(
ObjectType::MailingList,
list_id,
json!({
"recipients/[email protected]": true
}),
)
.await;
let list_cache = test
.server
.try_list(list_id.document_id())
.await
.unwrap()
.unwrap();
assert_eq!(
&list_cache.recipients,
&Arc::from(Box::from_iter([
"[email protected]".into(),
"[email protected]".into()
]))
);
// Verify that alias domains resolve without the primary name warming the cache
for address in [
"[email protected]",
"[email protected]",
"[email protected]",
] {
test.server.invalidate_all_local_caches();
assert!(
test.server.domain("beispiel.de").await.unwrap().is_some(),
"Alias domain failed to resolve on a cold cache"
);
test.server.invalidate_all_local_caches();
assert!(
test.server
.rcpt_id_from_email(address)
.await
.unwrap()
.is_some(),
"Cold cache resolution failed for {address}"
);
}
// Verify RCPT expansion
for (address, expected) in [
(
"[email protected]",
EmailCache::Account(account_id.document_id()),
),
(
"[email protected]",
EmailCache::Account(account_id.document_id()),
),
(
"[email protected]",
EmailCache::Account(account_id.document_id()),
),
(
"[email protected]",
EmailCache::Account(account_id.document_id()),
),
(
"[email protected]",
EmailCache::Account(group_id.document_id()),
),
(
"[email protected]",
EmailCache::Account(group_id.document_id()),
),
(
"[email protected]",
EmailCache::MailingList(list_id.document_id()),
),
(
"[email protected]",
EmailCache::MailingList(list_id.document_id()),
),
] {
assert_eq!(
test.server.rcpt_id_from_email(address).await.unwrap(),
Some(expected),
"Unexpected result for address: {address}"
);
}
assert_eq!(
test.server
.rcpt_id_from_email("[email protected]")
.await
.unwrap(),
None
);
assert_eq!(
test.server
.rcpt_id_from_email("[email protected]")
.await
.unwrap(),
None
);
// MTA rcpt resolve
let domain_2_id = account
.registry_create_object(Domain {
name: "another-example.com".to_string(),
is_enabled: true,
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
sub_addressing: SubAddressing::Custom(SubAddressingCustom {
custom_rule: Expression {
else_: "false".to_string(),
match_: List::from_iter([ExpressionMatch {
if_: "matches('^([^.]+)\\.([^.]+)', rcpt)".to_string(),
then: "$1".to_string(),
}]),
},
}),
..Default::default()
})
.await;
let account_2_id = account
.registry_create_object(Account::User(UserAccount {
name: "subaddresser".to_string(),
domain_id: domain_2_id,
..Default::default()
}))
.await;
assert_eq!(
test.server.rcpt_resolve("unknown", true, 0).await.unwrap(),
RcptResolution::UnknownDomain
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::UnknownDomain
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Accept
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Accept
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Accept
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Expand(Arc::from(Box::from_iter([
"[email protected]".into(),
"[email protected]".into()
])))
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", false, 0)
.await
.unwrap(),
RcptResolution::UnknownRecipient
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", false, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::UnknownRecipient
);
assert_eq!(
test.server
.rcpt_resolve(masked_email.as_str(), true, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
// Catch-all addresses have to be resolved rather than accepted verbatim
let domain_3_id = account
.registry_create_object(Domain {
name: "list-catch-all.com".to_string(),
is_enabled: true,
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
catch_all_address: Some("[email protected]".to_string()),
..Default::default()
})
.await;
let domain_4_id = account
.registry_create_object(Domain {
name: "subaddress-catch-all.com".to_string(),
is_enabled: true,
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
catch_all_address: Some("[email protected]".to_string()),
..Default::default()
})
.await;
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Expand(Arc::from(Box::from_iter([
"[email protected]".into(),
"[email protected]".into()
])))
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", true, 0)
.await
.unwrap(),
RcptResolution::Rewrite("[email protected]".into())
);
assert_eq!(
test.server
.rcpt_resolve("[email protected]", false, 0)
.await
.unwrap(),
RcptResolution::UnknownRecipient
);
// Query tests
assert_eq!(
account
.registry_query_ids(
ObjectType::Domain,
[(Property::Name, "example.com")],
[Property::Name]
)
.await,
vec![domain_id]
);
assert_eq!(
account
.registry_query_ids(
ObjectType::Account,
[
(Property::Name, "johndoe"),
(Property::Type, AccountType::User.as_str()),
(Property::Text, "johnny")
],
[Property::Name]
)
.await,
vec![account_id]
);
// Delete everything
john.registry_destroy(ObjectType::MaskedEmail, [masked_id])
.await
.assert_destroyed(&[masked_id]);
account
.registry_destroy(ObjectType::MailingList, [list_id])
.await
.assert_destroyed(&[list_id]);
account
.registry_destroy(ObjectType::Account, [group_id, account_id, account_2_id])
.await
.assert_destroyed(&[group_id, account_id, account_2_id]);
account
.registry_destroy(
ObjectType::Domain,
[domain_id, domain_2_id, domain_3_id, domain_4_id],
)
.await
.assert_destroyed(&[domain_id, domain_2_id, domain_3_id, domain_4_id]);
assert!(
test.server
.try_list(list_id.document_id())
.await
.unwrap()
.is_none()
);
assert!(
test.server
.try_account(account_id.document_id())
.await
.unwrap()
.is_none()
);
assert!(
test.server
.try_account(group_id.document_id())
.await
.unwrap()
.is_none()
);
assert!(test.server.domain("example.com").await.unwrap().is_none());
assert!(
test.server
.domain("another-example.com")
.await
.unwrap()
.is_none()
);
test.cleanup().await;
}