312 lines
9.3 KiB
Rust
312 lines
9.3 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use crate::utils::{imap::ImapConnection, server::TestServer, webdav::DummyWebDavClient};
|
|
use ahash::AHashMap;
|
|
use jmap_client::client::{Client, Credentials};
|
|
use registry::{
|
|
schema::{
|
|
enums::Permission,
|
|
prelude::{ObjectType, Property},
|
|
structs::{
|
|
self, CertificateManagement, Credential, CustomRoles, DkimManagement, DnsManagement,
|
|
Domain, EmailAlias, GroupAccount, PasswordCredential, Permissions, PermissionsList,
|
|
Roles, UserAccount,
|
|
},
|
|
},
|
|
types::{list::List, map::Map},
|
|
};
|
|
use serde_json::json;
|
|
use std::time::Duration;
|
|
use types::id::Id;
|
|
|
|
pub struct Account {
|
|
name: &'static str,
|
|
secret: &'static str,
|
|
emails: &'static [&'static str],
|
|
description: &'static str,
|
|
id: Id,
|
|
id_string: String,
|
|
pub http_listener_port: u16,
|
|
}
|
|
|
|
impl TestServer {
|
|
pub async fn create_user_account(
|
|
&self,
|
|
using_account: &str,
|
|
name: &'static str,
|
|
secret: &'static str,
|
|
aliases: &'static [&'static str],
|
|
description: &'static str,
|
|
) -> Account {
|
|
self.account(using_account)
|
|
.create_user_account(name, secret, description, aliases, vec![])
|
|
.await
|
|
}
|
|
|
|
pub async fn create_admin_account(&self, name: &'static str) -> Account {
|
|
let admin = self
|
|
.create_user_account(
|
|
"admin",
|
|
name,
|
|
"these_pretzels_are_making_me_thirsty",
|
|
&[],
|
|
"Admin",
|
|
)
|
|
.await;
|
|
self.account("admin")
|
|
.assign_roles_to_account(admin.id(), &["user", "system"])
|
|
.await;
|
|
admin
|
|
}
|
|
|
|
pub fn insert_account(&mut self, account: Account) {
|
|
self.accounts.insert(account.name(), account);
|
|
}
|
|
}
|
|
|
|
impl Account {
|
|
pub fn new(
|
|
name: &'static str,
|
|
secret: &'static str,
|
|
emails: &'static [&'static str],
|
|
description: &'static str,
|
|
id: Id,
|
|
) -> Self {
|
|
Self {
|
|
name,
|
|
secret,
|
|
emails,
|
|
description,
|
|
id,
|
|
id_string: id.to_string(),
|
|
http_listener_port: 8899,
|
|
}
|
|
}
|
|
|
|
pub fn update_secret(&mut self, new_secret: &'static str) {
|
|
self.secret = new_secret;
|
|
}
|
|
|
|
pub fn id(&self) -> Id {
|
|
self.id
|
|
}
|
|
|
|
pub fn id_string(&self) -> &str {
|
|
&self.id_string
|
|
}
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
self.name
|
|
}
|
|
|
|
pub fn description(&self) -> &'static str {
|
|
self.description
|
|
}
|
|
|
|
pub fn secret(&self) -> &'static str {
|
|
self.secret
|
|
}
|
|
|
|
pub fn emails(&self) -> &'static [&'static str] {
|
|
self.emails
|
|
}
|
|
|
|
pub async fn find_or_create_domain(&self, name: &'static str) -> Id {
|
|
let ids = self
|
|
.registry_query_ids(
|
|
ObjectType::Domain,
|
|
[(Property::Name, name)],
|
|
Vec::<&str>::new(),
|
|
)
|
|
.await;
|
|
|
|
match ids.len() {
|
|
0 => self.create_domain(name).await,
|
|
1 => ids[0],
|
|
_ => panic!("Multiple domains with name {name} found"),
|
|
}
|
|
}
|
|
|
|
pub async fn create_user_account(
|
|
&self,
|
|
name: &'static str,
|
|
secret: &'static str,
|
|
description: &'static str,
|
|
aliases: &'static [&'static str],
|
|
extra_permissions: Vec<Permission>,
|
|
) -> Account {
|
|
let mut domains = AHashMap::from_iter(
|
|
aliases
|
|
.iter()
|
|
.copied()
|
|
.chain([name].into_iter())
|
|
.map(|email| {
|
|
let domain = email.split('@').nth(1).expect("Invalid email address");
|
|
(domain, Id::singleton())
|
|
}),
|
|
);
|
|
for (name, id) in &mut domains {
|
|
*id = self.find_or_create_domain(name).await;
|
|
}
|
|
let (account_name, domain_id) = name
|
|
.rsplit_once('@')
|
|
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
|
.unwrap();
|
|
let account_aliases = aliases.iter().filter(|email| **email != name).map(|email| {
|
|
let (name, domain_id) = email
|
|
.rsplit_once('@')
|
|
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
|
.unwrap();
|
|
EmailAlias {
|
|
name,
|
|
domain_id,
|
|
enabled: true,
|
|
..Default::default()
|
|
}
|
|
});
|
|
|
|
let account_id = self
|
|
.registry_create_object(structs::Account::User(UserAccount {
|
|
name: account_name,
|
|
domain_id,
|
|
credentials: List::from_iter([Credential::Password(PasswordCredential {
|
|
secret: secret.to_string(),
|
|
..Default::default()
|
|
})]),
|
|
aliases: List::from_iter(account_aliases),
|
|
description: description.to_string().into(),
|
|
permissions: Permissions::Merge(PermissionsList {
|
|
disabled_permissions: Default::default(),
|
|
enabled_permissions: Map::new(extra_permissions),
|
|
}),
|
|
..Default::default()
|
|
}))
|
|
.await;
|
|
|
|
let mut account = Account::new(name, secret, aliases, description, account_id);
|
|
account.http_listener_port = self.http_listener_port;
|
|
account
|
|
}
|
|
|
|
pub async fn create_group_account(
|
|
&self,
|
|
name: &'static str,
|
|
description: &'static str,
|
|
aliases: &'static [&'static str],
|
|
) -> Account {
|
|
let mut domains = AHashMap::from_iter(
|
|
aliases
|
|
.iter()
|
|
.copied()
|
|
.chain([name].into_iter())
|
|
.map(|email| {
|
|
let domain = email.split('@').nth(1).expect("Invalid email address");
|
|
(domain, Id::singleton())
|
|
}),
|
|
);
|
|
for (name, id) in &mut domains {
|
|
*id = self.find_or_create_domain(name).await;
|
|
}
|
|
let (account_name, domain_id) = name
|
|
.rsplit_once('@')
|
|
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
|
.unwrap();
|
|
let account_aliases = aliases.iter().map(|email| {
|
|
let (name, domain_id) = email
|
|
.rsplit_once('@')
|
|
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
|
.unwrap();
|
|
EmailAlias {
|
|
name,
|
|
domain_id,
|
|
enabled: true,
|
|
..Default::default()
|
|
}
|
|
});
|
|
|
|
let account_id = self
|
|
.registry_create_object(structs::Account::Group(GroupAccount {
|
|
name: account_name,
|
|
domain_id,
|
|
aliases: List::from_iter(account_aliases),
|
|
description: description.to_string().into(),
|
|
..Default::default()
|
|
}))
|
|
.await;
|
|
|
|
Account::new(name, "", aliases, description, account_id)
|
|
}
|
|
|
|
pub async fn create_domain(&self, name: &'static str) -> Id {
|
|
self.registry_create_object(Domain {
|
|
is_enabled: true,
|
|
name: name.to_string(),
|
|
certificate_management: CertificateManagement::Manual,
|
|
dns_management: DnsManagement::Manual,
|
|
dkim_management: DkimManagement::Manual,
|
|
..Default::default()
|
|
})
|
|
.await
|
|
}
|
|
|
|
pub async fn assign_roles_to_account(&self, account_id: Id, names: &[&str]) {
|
|
let mut role_ids = Vec::new();
|
|
for name in names {
|
|
let role_id = *self
|
|
.registry_query_ids(
|
|
ObjectType::Role,
|
|
[(Property::Description, *name)],
|
|
Vec::<&str>::new(),
|
|
)
|
|
.await
|
|
.first()
|
|
.unwrap_or_else(|| panic!("Role {name} not found"));
|
|
role_ids.push(role_id);
|
|
}
|
|
|
|
self.registry_update(
|
|
ObjectType::Account,
|
|
[(
|
|
account_id,
|
|
json!({
|
|
Property::Roles: Roles::Custom(CustomRoles { role_ids: Map::new(role_ids) })
|
|
}),
|
|
)],
|
|
)
|
|
.await
|
|
.updated_id(account_id);
|
|
}
|
|
|
|
pub fn webdav_client(&self) -> DummyWebDavClient {
|
|
DummyWebDavClient::new(
|
|
self.id.document_id(),
|
|
self.name(),
|
|
self.secret(),
|
|
self.emails()[0],
|
|
)
|
|
}
|
|
|
|
pub async fn imap_client(&self) -> ImapConnection {
|
|
let mut imap = ImapConnection::connect(b"_x ").await;
|
|
imap.authenticate(self.name(), self.secret()).await;
|
|
imap
|
|
}
|
|
|
|
pub async fn jmap_client(&self) -> Client {
|
|
let mut client = Client::new()
|
|
.credentials(Credentials::basic(self.name(), self.secret()))
|
|
.timeout(Duration::from_secs(3600))
|
|
.accept_invalid_certs(true)
|
|
.follow_redirects(["127.0.0.1"])
|
|
.connect(&format!("https://127.0.0.1:{}", self.http_listener_port))
|
|
.await
|
|
.unwrap();
|
|
client.set_default_account_id(self.id_string());
|
|
client
|
|
}
|
|
}
|